Skip to main content

JWT Authentication

This document describes how Pomerium supports JWT authentication in upstream services with JSON web tokens (JWTs).

Overview

JWTs provide a secure and efficient means to authenticate and authorize users before they can access upstream services behind Pomerium. When configured for JWT authentication, Pomerium sends its own JWT to the upstream service. By verifying the Pomerium JWT, the upstream service can:

  • Confirm that the Pomerium Proxy service handled the client request before forwarding it.
  • Make application-level authorization decisions based on the user's associated identity information.

Why JWT authentication?

Identity verification

JWT authentication through Pomerium enables an upstream service to verify a user's identity based on claims contained in the JWT. Pomerium signs and issues a new JWT based on the ID token received from the service's configured identity provider.

Request verification

Pomerium places the newly minted JWT in a JWT assertion header. The upstream service should only accept the incoming request if it satisfies all JWT validation conditions.

By validating the JWT, the upstream service can assert that:

  • The request originated from Pomerium.
  • The user was authenticated.
  • The request was authorized in accordance with the route's authorization policy.
note

The Pomerium JWT does not contain any path information for an upstream service. If you've configured multiple routes with different paths for the same upstream service (such as an /admin route that grants access to a limited set of users), the application can't determine which Pomerium route the JWT corresponds to.

Single Sign-on (SSO)

You can configure upstream services to accept the Pomerium JWT to achieve an SSO authentication flow. This capability is completely free and relatively easy to configure depending on the upstream service and your identity provider.

Implement SSO with Pomerium

See our Grafana guide for a real-world example of how configuring both Pomerium and and an upstream service can provide easy SSO access for your end users.

JWT authentication flow

A diagram that shows how Pomerium forwards JWTs to an upstream application

Identity provider authentication

Pomerium requires users to authenticate against an OIDC-compliant identity provider before authorizing or denying a request to an upstream service.

After successful authentication, Pomerium mints a new Pomerium JWT based on the ID token generated by the identity provider. (This is Pomerium's default behavior, even if you haven't configured Pomerium to support JWT authentication.)

JWT assertion header

Pomerium signs its JWT with a signing key. If the pass identity headers setting is enabled, Pomerium will place the JWT into a special HTTP header called the JWT assertion header. Pomerium includes the JWT assertion header in every request it forwards to the upstream service.

JWT assertion header field

Pomerium passes the JWT in the X-Pomerium-Jwt-Assertion HTTP header, and encodes it according to RFC7519.

JWT validation

The upstream service receives the X-Pomerium-Jwt-Assertion-Header with the encrypted JWT. To validate a JWT, the service should check the following items:

JWT signature

The upstream service should validate that the JWT was signed by the issuing authority.

Pomerium issues and signs the new JWT with a private signing key. To validate the signature, the upstream service must fetch the corresponding public key from Pomerium's JSON web key set (JWKS) endpoint.

To configure an upstream service to fetch the public key:

  1. Get the hostname from the JWT's iss claim
  2. Append the /.well-known/pomerium/jwks.json path to the hostname
  3. Prepend the https:// scheme to the URL
  4. Set the Accept: application/json header in the request

For example:

curl https://service.corp.example.com/.well-known/pomerium/jwks.json \
-H 'Accept: application/json'

The returned JWK key set contains Pomerium's public keys. Use the kid claim provided in the Pomerium JWT header to identify the correct key in the returned key set.

JWKS response
{
"keys": [
{
"use": "sig",
"kty": "EC",
"kid": "ccc5bc9d835ff3c8f7075ed4a7510159cf440fd7bf7b517b5caeb1fa419ee6a1",
"crv": "P-256",
"alg": "ES256",
"x": "QCN7adG2AmIK3UdHJvVJkldsUc6XeBRz83Z4rXX8Va4",
"y": "PI95b-ary66nrvA55TpaiWADq8b3O1CYIbvjqIHpXCY"
}
]
}

If the JWT signature can't be validated, the JWT is invalid and can't be trusted.

Aud and iss claims

The upstream service should verify that the aud and iss claims match the domain used to serve your application.

The aud claim identifies the recipient the JWT is intended for. In the context of a service behind Pomerium, the aud claim should always be set as the upstream service's domain name.

Since v0.22, Pomerium sets the iss claim also to the domain of the target upstream service. (In previous versions, this was instead set to the authenticate service domain.)

If the domain provided in the aud and iss claims doesn't match the upstream service's domain name, the JWT is invalid and can't be trusted.

Valid aud and iss claims
{
"aud": "verify.pomerium.app",
"iss": "verify.pomerium.app"
}

JWT timestamps

The upstream service should verify that the Pomerium JWT has not expired.

The iat claim informs you at what time the JWT was issued. The exp claim specifies the expiration time on or after which the JWT must be considered invalid. By default, Pomerium sets the exp claim to expire 5 minutes after the time it was issued.

By comparing the current time with the timestamps in the exp and iat claims, you can verify if the JWT has expired or not. We recommend allowing up to a 1-minute leeway when comparing the exp and iat timestamps to account for clock skew between Pomerium and the upstream service.

If the JWT has expired, it is invalid and can't be trusted.

JWT Verification with Pomerium SDKs

Pomerium's JWT Verification guide shows you how to use our custom JWT libraries to parse and validate the Pomerium JWT in an upstream service.

After the upstream service validates the JWT, it can accept the request and trust other claims present in the JWT.

The Pomerium JWT

Pomerium generates a new Pomerium JWT based on the claims data contained in the original ID token. In addition to including standard claims as defined in RFC7519, Pomerium also injects its own claims into the Pomerium JWT as well. (See JWT claims data below for more details.)

note

The original ID token sourced from an identity provider is never modified or leaked to end users or upstream services.

Pomerium JWT claims data

When Pomerium is configured for JWT authentication with the pass identity headers setting, the user's associated identity information will be included in the JWT assertion header in each upstream request.

The Pomerium JWT contains at least the following claims:

JWT ClaimDescription
jtiA randomly generated UUID that represents the JWT ID.
expExpiration time in seconds since the UNIX epoch. Set to expire 5 minutes after iat time.
iatIssued-at time in seconds since the UNIX epoch.
audThe domain for the upstream application (for example, httpbin.corp.example.com).
issSame as the aud claim.
subThe user's ID, as specified by the identity provider.
emailThe user's email address.
groupsThe user's group memberships (if supported for the identity provider).
nameThe user's full name, as specified by the identity provider.
Prevent session replay attempts

The jti claim (the JWT ID) contains a unique identifier assigned to each Pomerium JWT. If you can implement a system that checks the jti value in real time, you can prevent session replay attempts. Or, if you persist the jti value in your logs, you can detect replayed JWTs after the fact.

JWT Settings

Use these settings to configure Pomerium to forward the Pomerium JWT to upstream services:

If your identity provider provides other claims not included in the Pomerium JWT that you would like to pass to your application, you can use the JWT Claims Headers option to include them in the JWT as well.