Skip to content

JKU and JWK — Dynamic Key Resolution for JWT Verification

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about JKU and JWK. We cover key concepts, practical examples, and best practices to help you master this topic.

JKU (JWK Set URL) and JWK (JSON Web Key) are JWT header parameters that enable dynamic resolution of signing keys, supporting key rotation without reconfiguring verifiers.

What You'll Learn

How JKU and JWK headers work, JWK Set format, when to use dynamic key resolution, and the security risks (JKU header injection).

Why It Matters

Static signing keys require reconfiguration when rotated. JKU/JWK allow servers to publish their current keys at a well-known URL. Verifiers fetch the latest keys automatically. This is essential for multi-service architectures.

Real-World Use

Google's OAuth2 endpoints publish JWKs at https://www.googleapis.com/oauth2/v3/certs. Auth0, Firebase, and most OIDC providers use JWK Sets. Clients fetch the appropriate key using the kid header.

flowchart LR
    A["Token with\nkid header"] --> B["Verifier"]
    B -->|"Fetch JWKS"| C["https://auth.example.com/.well-known/jwks.json"]
    C -->|"Return keys"| B
    B -->|"Select key by kid"| D["Public Key"]
    D -->|"Verify signature"| E["Valid or Invalid"]
    style A fill:#dbeafe,stroke:#2563eb
    style B fill:#fef3c7,stroke:#d97706
    style C fill:#dcfce7,stroke:#16a34a
    style E fill:#fef3c7,stroke:#d97706

JWK Set Format

A JWK Set is a JSON object with a keys array:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "key-id-1",
      "n": "0vx7ago...",
      "e": "AQAB",
      "alg": "RS256",
      "use": "sig"
    },
    {
      "kty": "RSA",
      "kid": "key-id-2",
      "n": "sX7p...",
      "e": "AQAB",
      "alg": "RS256",
      "use": "sig"
    }
  ]
}

Code Example: JWK Verification with Python

import jwt
import requests
from jwt import PyJWKClient

# JWKS endpoint
jwks_url = "https://auth.example.com/.well-known/jwks.json"

# Create a JWK client (caches keys)
jwks_client = PyJWKClient(jwks_url)

token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1pZC0xIn0..."

# Get the signing key from the JWKS using the kid in the header
signing_key = jwks_client.get_signing_key_from_jwt(token)

# Verify using the fetched key
payload = jwt.decode(
    token,
    signing_key.key,
    algorithms=["RS256"],
    audience="https://api.example.com",
    options={"verify_exp": True}
)

print(f"Verified: {payload['sub']}")

Code Example: Hosting a JWK Set

from flask import Flask, jsonify
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
import base64, json

app = Flask(__name__)

# Generate an RSA key pair
private_key = rsa.generate_private_key(
    public_exponent=65537, key_size=2048
)
public_key = private_key.public_key()

# Extract public key components
public_numbers = public_key.public_numbers()
n = base64.urlsafe_b64encode(
    public_numbers.n.to_bytes(256, 'big')
).rstrip(b'=').decode()
e = base64.urlsafe_b64encode(
    public_numbers.e.to_bytes(3, 'big')
).rstrip(b'=').decode()

@app.route("/.well-known/jwks.json")
def jwks():
    return jsonify({
        "keys": [{
            "kty": "RSA",
            "kid": "my-key-1",
            "n": n,
            "e": e,
            "alg": "RS256",
            "use": "sig"
        }]
    })

# Sign a token using the private key
def sign_jwt(payload):
    header = {"kid": "my-key-1"}
    return jwt.encode(
        payload, private_key,
        algorithm="RS256",
        headers=header
    )

JKU Header Injection Attack

The jku header tells the verifier where to fetch the JWK Set. An attacker can set jku to their own server hosting a key they control:

{
  "alg": "RS256",
  "jku": "https://attacker.com/keys.json",
  "kid": "malicious-key"
}

If the verifier fetches keys from the attacker-controlled URL, it will accept tokens signed with the attacker's key.

Common Mistakes

1. Not Validating the JKU URL

Always validate that the JKU URL points to a trusted domain (e.g., https://auth.yourdomain.com/.well-known/jwks.json).

2. Not Using kid with Multiple Keys

When you have multiple keys (common during rotation), the kid header tells the verifier which key to use. Without it, the verifier must try all keys.

3. Fetching JWKS on Every Request

JWK Sets change infrequently. Cache the response (with reasonable TTL) to avoid unnecessary network calls.

4. Not Handling JWKS Fetch Failures

If the JWKS endpoint is down, new verifications fail. Cache previous keys and fall back to them if fetch fails.

5. Including Private Keys in JWKS

The JWKS endpoint must only contain public keys. Never expose private keys.

Practice Questions

  1. What does JKU stand for and what does it do?
  2. What is the difference between JWK and JWK Set?
  3. How does the kid header help with key rotation?
  4. What is the JKU header injection attack?
  5. How should JWKS responses be cached?

Answers:

  1. JWK Set URL — a header parameter that tells the verifier where to fetch the signing keys.
  2. A JWK is a single key. A JWK Set (JWKS) is a JSON object containing an array of JWKs, typically served at a well-known URL.
  3. The kid in the JWT header matches the kid in the JWKS, selecting the specific key used for signing. During rotation, both old and new keys are in the JWKS.
  4. An attacker sets the jku header to their own server hosting a malicious key. If the verifier trusts the JKU without validation, it accepts forged tokens.
  5. Cache with a reasonable TTL (e.g., 1 hour). Cache-Control headers from the JWKS endpoint can guide this. Have a fallback to stale cache if fetch fails.

Challenge: Build a JWT verification system that fetches keys from a JWKS endpoint, caches them, handles key rotation (multiple keys), and validates the JKU URL against a whitelist.

FAQ

Is JKU required for JWT?

No. Many systems use pre-shared keys or keys configured in code. JKU is optional and useful for dynamic key distribution.

How often should keys be rotated?

Every 3-6 months. Emergency rotation if a key is compromised. JWK Sets support overlapping keys during rotation.

What is the difference between jku and jwk?

jku is a URL pointing to a JWK Set. jwk embeds the key directly in the header (rare, increases token size).

Can I use JWK with HS256?

Yes, but since HS256 uses a shared secret, the JWKS would expose the secret to anyone who fetches it. Use RS256 or ES256 for JWK-based verification.

How do OpenID Connect providers use JWK?

OIDC providers publish their JWKS at the discovery URL. Clients download the keys once and use them to verify ID tokens.

Mini Project

Create a Flask application that both issues JWTs (using RS256 with JWKS endpoint) and verifies JWTs (fetching keys from the JWKS). Include key rotation by publishing two keys simultaneously.

What's Next

Now learn about JWT Algorithm Confusion Attack — a critical vulnerability that can bypass JWT verification.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro