--- title: JWT Verification lang: en-US meta: - name: keywords content: pomerium identity-access-proxy envoy jwt description: >- This example demonstrates how to verify the Pomerium JWT assertion header using Envoy. --- # JWT Verification This example demonstrates how to verify the [Pomerium JWT assertion header](https://www.pomerium.io/reference/#pass-identity-headers) using [Envoy](https://www.envoyproxy.io/). This is useful for legacy or 3rd party applications which can't be modified to perform verification themselves. ## Requirements - [Docker](https://www.docker.com/) - [Docker Compose](https://docs.docker.com/compose/) - [mkcert](https://github.com/FiloSottile/mkcert) ## 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](https://verify.localhost.pomerium.io), is authenticated through [authenticate.localhost.pomerium.io](https://authenticate.localhost.pomerium.io), and then the HTTP request is sent to envoy which proxies it to [`verify.pomerium.com`](https://verify.pomerium.com). 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](https://authenticate.int.example.com/.well-known/pomerium/jwks.json). ## Setup ### 1. Docker Compose Create a `docker-compose.yaml` file containing: ```yaml 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`](https://github.com/FiloSottile/mkcert) generate a certificate for `*.localhost.pomerium.io` in a `certs` directory: ```bash mkdir certs cd certs mkcert '*.localhost.pomerium.io' ``` ### 3. Envoy Configuration Create a `cfg` directory containing the following `envoy.yaml` file: ```yaml 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: ```yaml - 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](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/jwt_authn_filter#config-http-filters-jwt-authn). ### 4. Pomerium Configuration Create a `pomerium.yaml` file in the `cfg` directory containing: ```yaml 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: ```bash docker-compose up ``` Visit [verify.localhost.pomerium.io](https://verify.localhost.pomerium.io), login and you see the Pomerium verify page. However, visiting Envoy directly via [localhost:10000](http://localhost:10000) should return a `Jwt is missing` error, thus requiring Pomerium to access Envoy.