Join us for GraphQL Summit, October 10-12 in San Diego. Use promo code ODYSSEY for $400 off your pass.
Docs
Launch GraphOS Studio

JWT Authentication in the Apollo Router


⚠️ This is an Enterprise feature of the Apollo Router. It requires an organization with a GraphOS Enterprise plan.

If your organization doesn't currently have an Enterprise plan, you can test out this functionality by signing up for a free Enterprise trial.

The Apollo supports request authentication and key rotation via the JSON Web Token (JWT) and JSON Web Key (JWK) standards. This support is compatible with popular identity providers (IdPs) like Okta and Auth0.

By enabling JWT authentication, you can block malicious traffic at the edge of your graph instead of relying on header forwarding to propagate tokens to your s.

⚠️ Under all circumstances, your subgraphs should be accessible only via the —not directly by clients. This is especially true if you're relying on JWT authentication in the router. Make sure to secure your subgraphs.

To use this feature:

If you issue JWTs via a popular third-party IdP (Auth0, Okta, PingOne, etc.), adding JWT authentication to your is comparatively straightforward. If you use your own custom IdP, advanced configuration is required.

How it works

These are the high-level steps of JWT-based authentication with the Apollo :

  1. Whenever a client authenticates with your system, your IdP issues that client a valid JSON Web Token (JWT).

  2. In its subsequent requests to your , the authenticated client provides its JWT in a designated HTTP header.

  3. Whenever your receives a client request, it extracts the JWT from the designated header (if present).

    • If no JWT is present, the request proceeds. You can reject requests with no accompanying JWT at a later phase (see below).
  4. Your validates the extracted JWT using a corresponding JSON Web Key (JWK).

    • Your obtains all of its known JWKs from URLs that you specify in its configuration file. Each URL provides its keys within a single JSON object called a JWK Set (or a JWKS).
    • If validation fails, the router rejects the request. This can occur if the JWT is malformed, or if it's been expired for more than 60 seconds (this window accounts for synchronization issues).
  5. The extracts all claims from the validated JWT and includes them in the request's context, making them available to your router customizations, such as Rhai scripts.

  6. Your customizations can handle the request differently depending on the details of the extracted claims, and/or you can propagate the claims to s to enable more granular access control.

Turning it on

You enable JWT authentication for your with the following steps:

  1. Set configuration options for JWT authentication in your 's YAML config file, under the authentication key:

    router.yaml
    authentication:
    router:
    jwt:
    jwks: # This key is required.
    - url: https://dev-zzp5enui.us.auth0.com/.well-known/jwks.json
    issuer: <optional name of issuer>
    # These keys are optional. Default values are shown.
    header_name: Authorization
    header_value_prefix: Bearer

    These options are documented below.

  2. Pass all of the following to the router executable on startup:

    • The path to the 's YAML configuration file (via the --config option)
    • The graph ref for the GraphOS variant your should use (via the APOLLO_GRAPH_REF environment )
    • A graph API key that enables the to authenticate with to fetch its (via the APOLLO_KEY environment )
    APOLLO_GRAPH_REF=docs-example-graph@main APOLLO_KEY="..." ./router --config router.yaml

When the starts up, it displays a log message that confirms which jwks are in use:

2023-02-03T14:05:28.018932Z INFO JWT authentication using JWKSets from jwks=[{ url: "file:///router/jwks.json" }]

Configuration options

The following configuration options are supported:

OptionDescription
jwks

Required. A list of JWK Set (JWKS) configuration options:

  • url: required URL from which the JWKS file will be read. Must be a valid URL.
    • If you use a third-party IdP, consult its documentation to determine its JWKS URL.
    • If you use your own custom IdP, you need to make its JWKS available at a -accessible URL if you haven't already. For more information, see Creating your own JWKS.
  • issuer: optional name of the issuer, that will be compared to the iss claim in the JWT if present. If it does not match, the request will be rejected.
  • algorithms: optional list of accepted algorithms. Possible values are HS256, HS384, HS512, ES256, ES384, RS256, RS384, RS512, PS256, PS384, PS512, EdDSA
header_name

The name of the HTTP header that client requests will use to provide their JWT to the . Must be a valid name for an HTTP header.

The default value is Authorization.

header_value_prefix

The string that will always precede the JWT in the header value corresponding to header_name. This value must not include whitespace.

The default value is Bearer.

Working with JWT claims

After the Apollo validates a client request's JWT, it adds that token's claims to the request's context at this key: apollo_authentication::JWT::claims

  • If no JWT is present for a client request, this context value is the empty tuple, ().
  • If a JWT is present but validation of the JWT fails, the rejects the request.

If unauthenticated requests should be rejected, the can be configured like this:

router.yaml
authorization:
require_authentication: true

Claims are the individual details of a JWT's scope. They might include details like the ID of the associated user, any roles assigned to that user, and the JWT's expiration time. See the spec.

Because claims are added to the context, you can define custom logic for handling each request based on the details of its claims. You can define this logic within a Rhai script or external coprocessor at the service level (for more on these options, see Customizations for the Apollo Router).

Below are 2 example Rhai script customizations that demonstrate actions the can perform based on a request's claims.

Example: Forwarding claims to subgraphs as headers

Below is an example Rhai script that forwards a JWT's claims to individual s via HTTP headers (one header for each claim). This enables each subgraph to define logic to handle (or potentially reject) incoming requests based on claim details. This function should be imported and run in your main.rhai file.

This script should be run in the 's SubgraphService, which executes before the sends a subquery to an individual . Learn more about router services.

⚠️ Explicitly listing claims and always setting headers for them is strongly recommended to avoid possible security issues when forwarding headers to s.

Example: Forwarding claims to subgraphs as GraphQL extensions

Below is an example Rhai script that forwards a JWT's claims to individual s via GraphQL extension. This enables each subgraph to define logic to handle (or potentially reject) incoming requests based on claim details. This function should be imported and run in your main.rhai file.

This script should be run in the 's SubgraphService, which executes before the sends a subquery to an individual . Learn more about router services.

Example: Throwing errors for invalid claims

Below is an example Rhai script that throws distinct errors for different invalid JWT claim details. This function should be imported and run in your main.rhai file.

This script should be run in the 's SupergraphService, which executes before the begins generating the query plan for an . Learn more about router services.

Example main.rhai

In order to use the above Rhai examples, you must import them into your main.rhai like this:

Claim augmentation via coprocessors

You may require information beyond what your JSON web tokens provide. For example, a token's claims may include user IDs, which you then use to look up user roles. For situations like this, you can augment the claims from your JSON web tokens with coprocessors.

Creating your own JWKS (advanced)

⚠️ Most third-party IdP services create and host a JWKS for you. If you use a third-party IdP, consult its documentation to obtain the JWKS URL to pass to your .

Read this section if you use a custom IdP that doesn't currently publish its JWKS at a -accessible URL.

The Apollo obtains each JSON Web Key (JWK) that it uses from the URLs that you specify via the jwks configuration option. Each URL must provide a set of valid JWKs in a single JSON object called a JWK Set (or JWKS).

To provide a JWKS to your , configure your IdP service to do the following whenever its collection of valid JWKs changes (such as when a JWK expires or is rotated):

  1. Generate a valid JWKS object that includes the details of every JWK that the requires to perform token validation.
  2. Write the JWKS object to a location that your can reach via a file:// or https:// URL.
    • ⚠️ If any of your JWKs uses a symmetric signature algorithm (such as HS256), always use a file:// URL. Symmetric signature algorithms use a shared key that should never be accessible over the network.

Again, make sure the IdP is configured to perform these steps every time its collection of JWKs changes!

JWKS format

A JWKS is a JSON object with a single top-level property: keys. The value of keys is an array of objects that each represent a single JWK:

jwks.json
{
"keys": [
{
// These JWK properties are explained below.
"kty": "RSA",
"alg": "RS256",
"kid": "abc123",
"use": "sig",
"n": "0vx7agoebGcQSuu...",
"e": "AQAB"
}
]
}

It's common for the keys array to contain only a single JWK, or sometimes two if your IdP is in the process of rotating a key.

JWK object reference

JWK object properties fall into two categories:

Universal properties

These properties apply to any JWK:

OptionDescription
kty

Short for key type. The high-level type of cryptographic algorithm that the JWK uses (such as RSA, EC, or oct).

alg

Short for algorithm. The exact cryptographic algorithm to use with the JWK, including key size (such as RS256 or HS512).

kid

Short for key identifier. The JWK's unique identifier. Your IdP should generate each JWK's kid at the same time that it generates the JWK itself.

JWTs created with a particular key can include that key's identifier in their payload, which helps the determine which JWK to use for validation.

use

Indicates how a JWK is used. Spec-defined values are sig (signature) and enc (encryption).

For keys you're using to perform JWT authentication, this value should be sig.

Algorithm-specific properties

RSA

See also the JWA spec.

{
// Universal properties
"kty": "RSA",
"alg": "RS256",
"kid": "abc123",
// Algorithm-specific properties
"n": "0vx7agoebGcQSuu...", // Shortened for readability
"e": "AQAB"
}
OptionDescription
n

The RSA public key's modulus value, as the base64-encoded value of the unsigned integer.

e

The RSA public key's exponent value, as the base64-encoded value of the unsigned integer.

This value is often AQAB, which is the base64 encoding for the exponent 65537.

EC (elliptic curve)

See also the JWA spec.

{
// Universal properties
"kty": "EC",
"alg": "ES256",
"kid": "afda85e09a320cf748177874592de64d",
"use": "sig",
// Algorithm-specific properties
"crv": "P-256",
"x": "opFUViwCYVZLmsbG2cJTA9uPvOF5Gg8W7uNhrcorGhI",
"y": "bPxvCFKmlqTdEFc34OekvpviUUyelGrbi020dlgIsqo"
}
OptionDescription
crv

Indicates which cryptographic curve is used with this public key.

Spec-defined curves include:

  • P-256
  • P-384
  • P-521
x

The x-coordinate of the elliptic curve point for this public key, as the base64-encoded value of the coordinate's octet string representation.

y

The y-coordinate of the elliptic curve point for this public key, as the base64-encoded value of the coordinate's octet string representation.

Symmetric key algorithms (such as HMAC)
{
// Universal properties
"kty": "oct",
"alg": "HS256",
"kid": "key1",
"use": "sig",
// Symmetric-algorithm-specific property
"k": "c2VjcmV0Cg==" // ⚠️ This is a base64-encoded shared secret! ⚠️
}
OptionDescription
k

The value of the shared symmetric key, as the base64-encoded value of the key's octet sequence representation.

⚠️ If your JWK uses a symmetric signature algorithm, always provide your JWKS to the router via a file:// URL! Shared keys should never be made available over the network.

JWK matching

To match an incoming JWT with its corresponding JWK, the proceeds through descending "specificity levels" of match criteria until it identifies the first compatible JWK from its JWK Sets:

  1. The JWT and JWK match both kid and alg exactly.
  2. The JWT and JWK match kid, and the JWT's alg is compatible with the JWK's kty.
  3. The JWT and JWK match alg exactly.
  4. The JWT's alg is compatible with the JWK's kty.

This matching strategy is necessary because some identity providers (IdPs) don't specify alg or kid values in their JWKS. However, they always specify a kty, because that value is required by the JWK specification.

Forwarding JWTs to subgraphs

Because the Apollo handles validating incoming JWTs, you rarely need to pass those JWTs to individual s in their entirety. Instead, you usually want to pass JWT claims to subgraphs to enable fine-grained access control.

If you do need to pass entire JWTs to s, you can do so via the Apollo 's general-purpose HTTP header propagation settings.

Observability

If your enables tracing, the JWT authentication plugin has its own tracing span: authentication_plugin

If your enables metrics collection via Prometheus, the JWT authentication plugin provides and exports the following metrics:

  • apollo_authentication_failure_count
  • apollo_authentication_success_count

Those metrics have the following shapes:

# HELP apollo_authentication_failure_count apollo_authentication_failure_count
# TYPE apollo_authentication_failure_count counter
apollo_authentication_failure_count{kind="JWT",service_name="apollo-router"} 1
# HELP apollo_authentication_success_count apollo_authentication_success_count
# TYPE apollo_authentication_success_count counter
apollo_authentication_success_count{kind="JWT",service_name="apollo-router"} 11
Previous
CSRF prevention
Next
Authorization
Edit on GitHubEditForumsDiscord