Just-In-Time Access With Pomerium
What this guide does
This guide shows how to grant time-limited access to a route with Pomerium. Instead of leaving a user permanently authorized, you attach a policy that allows them only until a set time, after which Pomerium denies the request automatically. You'll see the Pomerium Policy Language (PPL) date matcher that expresses the time window, and the jit-example app that turns a manual request and approve exchange into a self-service workflow backed by the Pomerium Zero API.
When to use this guide
Use it when access to an application should be the exception rather than the default: a contractor who needs an afternoon in an admin console, an on-call engineer granted access for the length of an incident, or any "break-glass" path you want to close on its own. If you only ever need permanent role- or group-based access, a standard policy without a time window is simpler.
Prerequisites
This guide assumes you've completed the Quickstart, so you already have Pomerium running and signing users in through the hosted authenticate service.
You also need:
- A working Pomerium Zero cluster. If you don't have one, follow the Quickstart first. The
datematcher is evaluated by the Pomerium Zero and Enterprise policy engine, so the time-limited grant in this guide is a Zero/Enterprise feature. - A domain you control for the route (this guide uses
jit-example.yourdomain.com). - For the automation section: Docker and Docker Compose.
The time-limited policy
PPL supports time-limited access through the date matcher. Combined with a user or email criterion, it grants a specific person access that expires on its own:
allow:
and:
- email:
is: user@example.com
- date:
before: 2150-01-02T16:20:00
Here user@example.com may reach the route, but only before 2150-01-02T16:20:00. After that moment Pomerium returns a 403 forbidden page. Nothing has to run to revoke the grant: the date matcher does it. To extend access, move the date forward; to grant a window in the future, add after alongside before.
Configure the grant in the Zero Console
For an organization with infrequent requests, an administrator can maintain this by hand. In the Zero Console:
- Create or open the Policy attached to the route you want to protect.
- Add an Allow rule that combines the requester's email with a
datematcher whosebeforevalue is when access should end, as in the snippet above. - Save. Any route using that policy immediately honors the window, and the grant expires automatically when the time passes.
An administrator might receive the request over Slack or email, edit the policy to add the criteria, and save. No follow-up is needed to revoke access later.
This guide uses the hosted authenticate service so you don't have to run an IdP. To run your own instead, follow Keycloak + Pomerium and swap the authenticate_service_url and idp_* settings into your configuration.
Automate the request and approve workflow
Editing policies by hand scales poorly. Pomerium Zero exposes a public API, so a small application can drive the same edits and give users a self-service front door. To demonstrate this, Pomerium provides the jit-example app: a Go web server where users request access and administrators approve or reject it. Approving a request updates a policy in Pomerium Zero, which one or more routes can use.
jit-example authenticates and authorizes through Pomerium itself, reading the Pomerium JWT assertion header to identify the current user. It exposes two endpoints:
/— the index page where users request access./admin— the admin page where administrators approve requests.
Set up two routes in Pomerium Zero so any authenticated user can request access while only administrators can approve:
jit-example.yourdomain.com- From is
https://jit-example.yourdomain.com; To points to yourjit-exampleinstance. - Attach an Any Authenticated User policy.
- On the Headers tab, enable Pass Identity Headers.
- From is
jit-example.yourdomain.com/admin- From is
https://jit-example.yourdomain.com; To points to yourjit-exampleinstance. - Under Path Matching, set Prefix to
/admin; under Path Rewriting, set Prefix Rewrite to/admin. - Attach a policy that restricts access to administrators.
- On the Headers tab, enable Pass Identity Headers.
- From is
With this setup, any user reaches https://jit-example.yourdomain.com, but only administrators can approve requests at /admin.
Run the stack
jit-example has no published image, so clone and build it from the repo. It reads its configuration from the environment:
API_USER_TOKEN— a Pomerium Zero API user token, created at console.pomerium.app/app/management/api-tokens.CLUSTER_ID— the cluster where thejit-examplepolicy is created.ORGANIZATION_ID— your organization ID.JWKS_ENDPOINT— the JSON Web Key Set (JWKS) URL of the Pomerium route in front ofjit-example, used to verify the forwarded assertion, for examplehttps://jit-example.yourdomain.com/.well-known/pomerium/jwks.json.PORT— the port the web server listens on (defaults to8000).
The Compose file below runs Pomerium Zero (driven by your POMERIUM_ZERO_TOKEN) alongside jit-example. Zero is used because the date matcher is a Zero/Enterprise feature; routes and the time-limited policy are managed in the Zero Console as described above, not in a local config file. jit-example is itself the application the date-gated policy protects.
services:
# Pomerium Zero is driven by your cluster token; routes and the time-limited
# date-matcher policy live in the Zero Console, not a local config file.
pomerium:
image: pomerium/pomerium@sha256:e10d1d267af24f581157f485d9b0bc08469e2428675b696a08e42ceb09b2279c # v0.32.7
environment:
- POMERIUM_ZERO_TOKEN=REPLACE_WITH_YOUR_POMERIUM_ZERO_TOKEN
volumes:
- pomerium-cache:/data
ports:
- 443:443
- 80:80
restart: always
# jit-example has no published image; build it from the repo:
# git clone https://github.com/pomerium/jit-example
# This is also the application the date-gated policy protects.
jit-example:
build: ./jit-example
environment:
- PORT=8000
- ORGANIZATION_ID=REPLACE_WITH_YOUR_ORGANIZATION_ID
- CLUSTER_ID=REPLACE_WITH_YOUR_CLUSTER_ID
- API_USER_TOKEN=REPLACE_WITH_YOUR_API_USER_TOKEN
- JWKS_ENDPOINT=https://jit-example.yourdomain.com/.well-known/pomerium/jwks.json
restart: always
volumes:
pomerium-cache:
Clone the app, save the file above next to it as docker-compose.yaml, replace the REPLACE_WITH_* values with your own, then start the stack:
git clone https://github.com/pomerium/jit-example
# save the Compose file above as docker-compose.yaml in this directory
docker compose up -d
Verify the setup
- The route requires authentication. In a fresh browser, open
https://jit-example.yourdomain.com. You should be redirected to sign in, not straight into the app. - An active grant is allowed. While a user's
datewindow is open, signing in lets them reach the route. - An expired grant is denied. Once the
beforetime has passed, the same authenticated user gets a 403 forbidden page with no change to the policy. To see this without waiting, setbeforeto a time a minute or two in the future and refresh after it elapses. - Only admins approve. A non-admin who reaches
/adminis denied by that route's policy.
Common failure modes
unknown policy criterion: dateon startup. Thedatematcher requires Pomerium Zero or Enterprise. Open-source Core does not evaluate it; use a Zero cluster for time-limited grants.- Access doesn't expire. Confirm the
beforetimestamp has actually passed and that the route uses the policy you edited. A typo in the email criterion silently keeps the rule from matching. jit-examplecan't update the policy. ConfirmAPI_USER_TOKEN,ORGANIZATION_ID, andCLUSTER_IDare correct and that the token has permission to edit policies in that cluster.- The app shows the wrong user, or no user. Make sure Pass Identity Headers is enabled on the route so
jit-examplereceives the JWT assertion.
Security considerations
jit-exampletrusts the identity Pomerium forwards in the JWT assertion header, so don't expose it directly — only Pomerium should reach the app. Keep it off published ports and on the internal network.- Scope the
/adminroute to administrators with a real group or email policy; the approval path is what grants other users access. - An API user token that can edit policies is a powerful credential. Store it in a secret manager, not in source control, and scope it to the single cluster the automation manages.
Operations
- Revoke access before it expires. A
dategrant closes on its own, but to cut access immediately, delete thedateallow rule (or remove the user's email criterion) in the Zero Console. The next request the user makes returns a 403. - Rotate the API token. Treat
API_USER_TOKENlike any other long-lived credential: rotate it on a schedule and after any suspected exposure, and re-deployjit-examplewith the new value. Remove tokens you no longer use from the Zero Console. - Tear down. Run
docker compose downto stop the stack; add-vto also drop thepomerium-cachevolume. Policies you created in Zero persist independently and should be cleaned up in the Console.