# JWT Verification
This example demonstrates how to verify the Pomerium JWT assertion header (opens new window) using Envoy (opens new window). This is useful for legacy or 3rd party applications which can't be modified to perform verification themselves.
# Requirements
# Overview
Two services are configured in a docker-compose.yaml
file:
pomerium
running an all-in-one deployment of Pomerium on*.localhost.pomerium.io
envoy-jwt-checker
running envoy with a JWT Authn filter
Once running, the user visits verify.localhost.pomerium.io (opens new window), is authenticated through authenticate.localhost.pomerium.io (opens new window), and then the HTTP request is sent to envoy which proxies it to verify.pomerium.com
(opens new window).
Before allowing the request Envoy will verify the signed JWT assertion header using the public key defined by authenticate.localhost.pomerium.io/.well-known/pomerium/jwks.json (opens new window).
# Setup
# 1. Docker Compose
Create a docker-compose.yaml
file containing:
version: "3.8"
services:
pomerium:
image: pomerium/pomerium:latest
ports:
- "443:443"
volumes:
- type: bind
source: ./cfg/pomerium.yaml
target: /pomerium/config.yaml
- type: bind
source: ./certs/_wildcard.localhost.pomerium.io.pem
target: /pomerium/_wildcard.localhost.pomerium.io.pem
- type: bind
source: ./certs/_wildcard.localhost.pomerium.io-key.pem
target: /pomerium/_wildcard.localhost.pomerium.io-key.pem
envoy-jwt-checker:
image: envoyproxy/envoy:v1.17.1
ports:
- "10000:10000"
volumes:
- type: bind
source: ./cfg/envoy.yaml
target: /etc/envoy/envoy.yaml
# 2. Certificates
Using mkcert
(opens new window) generate a certificate for *.localhost.pomerium.io
in a certs
directory:
mkdir certs
cd certs
mkcert '*.localhost.pomerium.io'
# 3. Envoy Configuration
Create a cfg
directory containing the following envoy.yaml
file:
admin:
access_log_path: /dev/null
address:
socket_address: { address: 127.0.0.1, port_value: 9901 }
static_resources:
listeners:
- name: ingress-http
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: verify
virtual_hosts:
- name: verify
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: egress-verify
auto_host_rewrite: true
http_filters:
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
pomerium:
issuer: authenticate.localhost.pomerium.io
audiences:
- verify.localhost.pomerium.io
from_headers:
- name: X-Pomerium-Jwt-Assertion
remote_jwks:
http_uri:
uri: https://authenticate.localhost.pomerium.io/.well-known/pomerium/jwks.json
cluster: egress-authenticate
timeout: 1s
rules:
- match:
prefix: /
requires:
provider_name: pomerium
- name: envoy.filters.http.router
clusters:
- name: egress-verify
connect_timeout: 0.25s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: verify
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: verify.pomerium.com
port_value: 443
transport_socket:
name: tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: verify.pomerium.com
- name: egress-authenticate
connect_timeout: '0.25s'
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: authenticate
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: pomerium
port_value: 443
transport_socket:
name: tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: authenticate.localhost.pomerium.io
Envoy configuration can be quite verbose, but the crucial bit is the HTTP filter:
- name: envoy.filters.http.jwt_authn
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
providers:
pomerium:
issuer: authenticate.localhost.pomerium.io
audiences:
- verify.localhost.pomerium.io
from_headers:
- name: X-Pomerium-Jwt-Assertion
remote_jwks:
http_uri:
uri: https://authenticate.localhost.pomerium.io/.well-known/pomerium/jwks.json
cluster: egress-authenticate
timeout: 1s
rules:
- match:
prefix: /
requires:
provider_name: pomerium
This configuration pulls the JWT out of the X-Pomerium-Jwt-Assertion
header, verifies the iss
and aud
claims and checks the signature via the public key defined at the jwks.json
endpoint. Documentation for additional configuration options is available here: Envoy JWT Authentication (opens new window).
# 4. Pomerium Configuration
Create a pomerium.yaml
file in the cfg
directory containing:
authenticate_service_url: https://authenticate.localhost.pomerium.io
certificate_file: "/pomerium/_wildcard.localhost.pomerium.io.pem"
certificate_key_file: "/pomerium/_wildcard.localhost.pomerium.io-key.pem"
idp_provider: google
idp_client_id: REPLACE_ME
idp_client_secret: REPLACE_ME
cookie_secret: WwMtDXWaRDMBQCylle8OJ+w4kLIDIGd8W3cB4/zFFtg=
shared_secret: WwMtDXWaRDMBQCylle8OJ+w4kLIDIGd8W3cB4/zFFtg=
signing_key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUdxWllpVzJycVo3TUdKTGp4bnNZVWJJcmZxNFdwR044RlgzQVh2UnRjSHdvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFYVd1UkNKMjFrL2JvUjNNRytPOVlHQjNXR0R1anVXMHFLVWhucUVwVS9JKzFoZmhuZEJ0WApDZGFpaGVGb0FOWXVCRUp3MFZhRml6QnVZb3l5RVAzOXBRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
policy:
- from: https://verify.localhost.pomerium.io
to: http://envoy-jwt-checker:10000
allowed_domains:
- pomerium.com
pass_identity_headers: true
You will need to replace the identity provider credentials for this to work.
# Run
You should now be able to run the example with:
docker-compose up
Visit verify.localhost.pomerium.io (opens new window), login and you see the Pomerium verify page. However, visiting Envoy directly via localhost:10000 (opens new window) should return a Jwt is missing
error, thus requiring Pomerium to access Envoy.