# 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
Copied!
# 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'
Copied!
# 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
Copied!
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
Copied!
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= routes: - from: https://verify.localhost.pomerium.io to: http://envoy-jwt-checker:10000 policy: - allow: or: - domain: is: pomerium.com pass_identity_headers: true
Copied!
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
Copied!
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.