Last updated: July 2026
A JWKS, or JSON Web Key Set, is a JSON document that publishes a set of cryptographic public keys, defined by RFC 7517, so clients can verify the signatures of JWTs without any manual key exchange. An identity provider like Keycloak serves its JWKS at a well-known HTTPS endpoint; in Keycloak that is /realms/{realm}/protocol/openid-connect/certs. Verifiers fetch the set, pick the key whose kid matches the token header, and validate the signature, which makes automatic key rotation possible.
What problem does a JWKS solve?
A JWKS solves the key distribution problem. When an identity provider signs a JWT (RFC 7519) with a private key, every API that accepts that token needs the matching public key to verify the signature. Without a standard way to publish that key, you end up copying PEM files into config repos by hand.
That approach breaks down fast. Every rotation means updating and redeploying every verifier at once, and if one service lags behind, it starts rejecting valid tokens. In practice, teams respond by never rotating keys at all, which is worse.
A JWKS replaces all of that with a single HTTPS URL. The identity provider publishes its current public keys there, and every verifier fetches them at runtime. When the provider rotates a key, it publishes the new one alongside the old one, and verifiers pick up the change automatically. Zero redeploys, zero downtime. That’s why every serious OIDC provider exposes a JWKS endpoint, and why nearly every JWT library can consume one out of the box.
What does a JWKS document look like?
A JWKS is a JSON object with a single top-level member: keys, an array of JWK objects. Each JWK describes one public key. Here is a trimmed but realistic example of what Keycloak returns for a realm:
{
"keys": [
{
"kid": "GnxpDNfPB3fL8cSlYnVs8OGX3Jkzn3PweUeuUnE1MZY",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "sYcTZLKuVBGHx0v3K1mP9c4dQ7hN...",
"e": "AQAB",
"x5c": ["MIICmzCCAYMCBgGNw9Qb1TANBgkq..."],
"x5t": "3ZmY0fN2kR8wLq5xJvC7hT1sPd4",
"x5t#S256": "1kD5pW9rTn3xVb8mQzL6yHc2eF0aJg7uS4iO_wEkNqM"
},
{
"kid": "b2FkzT8qLm4wXc1vRn7pYd0sHe6jUi3g",
"kty": "RSA",
"alg": "RSA-OAEP",
"use": "enc",
"n": "wR5tYm2nQx8pLv4cKd1sHf9jZe...",
"e": "AQAB"
}
]
}
Notice that Keycloak publishes more than one key: a signing key (use: "sig"), an encryption key (use: "enc"), and during a rotation two signing keys at once. Only public key material appears here; private keys and HMAC or AES secrets never leave the server.
Every JWKS field explained
All of these parameters are defined in RFC 7517 (the RSA-specific members come from its companion, RFC 7518):
| Field | Meaning | Notes |
|---|---|---|
kty |
Key type | The cryptographic family: RSA, EC (elliptic curve), OKP, or oct. The only required member of a JWK (RFC 7517, section 4.1). |
use |
Public key use | sig for signature verification, enc for encryption. Tells verifiers which keys to consider (section 4.2). |
key_ops |
Key operations | A more granular alternative to use, listing allowed operations such as verify or encrypt (section 4.3). Rarely needed alongside use. |
alg |
Algorithm | The algorithm this key is intended for, such as RS256 or ES256 (section 4.4). Verifiers should check it matches the token header. |
kid |
Key ID | An identifier used to match a JWT to the key that signed it (section 4.5). The linchpin of rotation. |
n |
RSA modulus | The modulus of an RSA public key, base64url-encoded (RFC 7518, section 6.3.1.1). |
e |
RSA exponent | The public exponent. Almost always AQAB, which is 65537 (RFC 7518, section 6.3.1.2). |
x5c |
X.509 certificate chain | The certificate (and optionally its chain) containing this key, as base64-encoded DER (section 4.7). Lets PKI-aware clients validate against a CA. |
x5t |
X.509 SHA-1 thumbprint | A fingerprint of the certificate (section 4.8). The SHA-256 variant is x5t#S256 (section 4.9). |
For everyday token verification, the fields that matter are kty, use, alg, kid, n, and e. The x5c and x5t members only come into play when you validate keys through an existing PKI.
How does verification use the kid?
The kid is the routing mechanism that makes a key set useful. Every JWT carries a JOSE header, defined by RFC 7515, and when a provider signs with a keyset it includes the kid of the signing key in that header:
{
"alg": "RS256",
"typ": "JWT",
"kid": "GnxpDNfPB3fL8cSlYnVs8OGX3Jkzn3PweUeuUnE1MZY"
}
Verification then follows a simple loop:
- Decode the JWT header (no verification yet) and read
algandkid. - Look up the key with that
kidin the cached JWKS. - Confirm the key’s
alganduseare appropriate for signature verification. - Verify the signature with that public key.
- Validate the claims:
exp,iss,aud, and whatever else your policy requires.
If no key in the set matches the kid, a well-behaved verifier refetches the JWKS once before rejecting the token, because an unknown kid usually means the provider just rotated keys. Paste any Keycloak token into our JWT Token Analyzer and you’ll see the kid sitting right in the header.
For the full claim-validation side of this process, see our guide on verifying a Keycloak-issued access token on the backend.
Where is the Keycloak JWKS endpoint?
Every Keycloak realm publishes its own JWKS at:
https://{host}/realms/{realm}/protocol/openid-connect/certs
Note the path starts with /realms/. Older tutorials show /auth/realms/..., but that prefix was removed in Keycloak 17. If you’re hitting a 404 with a path from a pre-2022 blog post, that’s why.
You should never hardcode this URL from memory, though. It’s advertised in the realm’s OIDC discovery document under jwks_uri:
curl -s https://auth.example.com/realms/myrealm/.well-known/openid-configuration
| jq -r '.jwks_uri'
# https://auth.example.com/realms/myrealm/protocol/openid-connect/certs
And fetching the key set itself is just as simple:
curl -s https://auth.example.com/realms/myrealm/protocol/openid-connect/certs | jq
The endpoint is public and unauthenticated by design. These are public keys; exposing them is the whole point.
How Keycloak manages realm keys
Behind that endpoint, each realm owns its keys through key providers, documented in the Keycloak server administration guide. Open the admin console and go to Realm settings > Keys to see them: by default a realm has an RSA signing pair, an RSA encryption pair, plus HMAC and AES secrets for internal use. Only the public halves of the asymmetric pairs appear in the JWKS.
Each key has a status and a priority. The active key with the highest priority signs new tokens. A passive key signs nothing new but remains published in the JWKS, so tokens it previously signed still verify. That distinction is what makes graceful rotation possible:
- Add a new key provider (for example,
rsa-generated) with a higher priority than the current one. Keycloak immediately starts signing new tokens with it. - Leave the old key in place, passive or simply lower priority. It stays in the JWKS, so tokens issued before the switch keep verifying.
- Once every token signed by the old key has expired, including refresh and offline tokens if they’re JWT-based, delete the old provider.
Skip step 2 and you invalidate every outstanding session at once: mass logouts, a wall of 401s, and a busy on-call channel.
How do you verify tokens against a JWKS in Node.js?
The jose library handles the entire fetch, cache, match-by-kid, and verify cycle with one helper, createRemoteJWKSet:
import { createRemoteJWKSet, jwtVerify } from "jose";
const JWKS = createRemoteJWKSet(
new URL("https://auth.example.com/realms/myrealm/protocol/openid-connect/certs")
);
export async function verifyAccessToken(token) {
const { payload } = await jwtVerify(token, JWKS, {
issuer: "https://auth.example.com/realms/myrealm",
audience: "my-api",
});
return payload;
}
That’s a complete local verifier. createRemoteJWKSet fetches the key set on first use, caches it, selects keys by kid and alg, and refetches automatically when it sees an unknown kid, with a built-in cooldown so a flood of bad tokens can’t turn your API into a denial-of-service relay against Keycloak. Framework-specific setups for Spring, FastAPI, Go, and API gateways follow the same pattern; we cover them in Keycloak token validation for APIs.
One trade-off to keep in mind: JWKS-based local validation can’t detect tokens revoked before expiry. If you need instant revocation, compare it against introspection in token introspection vs local validation.
How often should you rotate signing keys?
There’s no RFC-mandated interval, but Keycloak’s own server administration guide recommends creating new keys every three to six months and deleting the old keys one to two months after the new ones are created, which gives all cookies and tokens time to update. Rotate immediately on any suspicion of compromise. The real requirement isn’t a specific number. It’s that rotation must be routine, automated, and boring, because a rotation you’ve never rehearsed is the one that breaks production.
The JWKS makes the mechanics safe as long as you follow the overlap rule:
- Publish the new key before signing anything with it, so caches have time to pick it up.
- Keep the old key published until the longest-lived token it signed has expired.
- Only then remove it from the set.
Keycloak’s active/passive key model implements exactly this overlap. Key rotation is one piece of a larger hygiene story; our JWT best practices guide covers the rest, from token lifetimes to storage.
How should clients cache a JWKS?
Fetching the JWKS on every request would be slow and would hammer your identity provider, so caching is mandatory. The standard strategy has three rules:
- Cache the key set in memory. A TTL of five to fifteen minutes is typical;
josedefaults to ten. Respect HTTP cache headers if the endpoint sends them. - Refetch on unknown
kid. When a token arrives with akidnot in your cache, refresh the JWKS once and retry the lookup. This is how verifiers absorb rotations with no coordination. - Rate-limit those refetches. An attacker can mint garbage tokens with random
kidvalues all day. Without a cooldown between forced refreshes, each one triggers an outbound request to your IdP.
Most mature libraries (jose in Node, PyJWKClient in PyJWT, Spring Security’s NimbusJwtDecoder) implement all three behaviors by default. If you’re writing your own JWKS client, you’re probably reinventing a wheel with a security bug in it.
What are the common JWKS failure modes?
When JWKS verification breaks, it’s almost always one of these:
kidmismatch. The token’skiddoesn’t exist in the fetched set. Usual causes: you’re fetching the JWKS from the wrong realm, or someone deleted the old key during rotation instead of leaving it passive.- Stale cache. Keys rotated, but a verifier with a long TTL and no refetch-on-unknown-
kidlogic keeps using the old set and rejects every new token until its cache expires. - Wrong realm URL. Pointing at
/realms/masterinstead of your application realm, or using the removed/auth/prefix, gives you keys that will never match, or a 404. - HTTP instead of HTTPS. The JWKS is the trust anchor for your whole token pipeline. Fetch it over plain HTTP and anyone in the path can substitute their own keys and mint tokens your API will accept. HTTPS is non-negotiable.
- Issuer mismatch. If Keycloak’s hostname configuration doesn’t match the URL your services use, the
issclaim won’t match your verifier’s expected issuer even though the signature is fine.
A useful debugging habit: curl the JWKS, decode the failing token’s header, and compare the kid values side by side. Nine times out of ten the mismatch is staring at you.
JWKS vs static PEM vs x5c and PKI
A JWKS endpoint isn’t the only way to distribute verification keys. Here’s how the options compare:
| Approach | Key distribution | Rotation | Best fit |
|---|---|---|---|
| JWKS endpoint | Fetched over HTTPS at runtime, discovered via jwks_uri |
Automatic; verifiers follow kid changes |
Almost all OIDC and OAuth 2.0 deployments |
| Static PEM in config | Public key copied into each service’s configuration | Manual; every verifier must be updated and redeployed together | Air-gapped environments or services that can’t make outbound calls |
x5c with PKI |
Certificate chain embedded in the JWK, validated against a trusted CA | Reissue certificates under the same CA; trust anchor stays fixed | Enterprises with existing PKI, mutual TLS, and CA-centric compliance rules |
If you’re running Keycloak, the JWKS endpoint should be your default. Reach for static PEM only when the network genuinely prevents runtime fetching, and accept that you now own the rotation problem the JWKS was designed to remove.
Frequently asked questions
What is a JWKS endpoint?
A JWKS endpoint is a public HTTPS URL where an identity provider publishes its current public keys as a JSON Web Key Set. Clients and APIs fetch it to verify JWT signatures without any manual key exchange. It’s advertised as jwks_uri in the provider’s OIDC discovery document.
Where is the Keycloak JWKS URL?
Keycloak serves one JWKS per realm at https://{host}/realms/{realm}/protocol/openid-connect/certs. You can confirm it programmatically by reading the jwks_uri field from https://{host}/realms/{realm}/.well-known/openid-configuration. Paths containing /auth/realms/ are from Keycloak versions before 17 and no longer exist.
How often should I rotate JWKS keys?
Roughly every 90 days is a sensible default, plus an immediate rotation whenever you suspect key compromise. More important than the interval is the procedure: publish the new key first, keep the old key in the JWKS until every token it signed has expired, then remove it.
What is the kid in a JWT?
The kid (key ID) is a header parameter that names the key used to sign the token. Verifiers use it to select the matching public key from the JWKS. It’s an opaque identifier, so treat it as a lookup hint, never as something to trust before the signature checks out.
Is it safe to expose a JWKS publicly?
Yes. A JWKS contains only public keys, which are useless for forging tokens; signing requires the private key, which never appears in the set. The endpoint being public and unauthenticated is intentional. The thing you must protect is the transport: always serve and fetch it over HTTPS.
Closing thoughts
A JWKS is a small document doing a big job: it turns key distribution and rotation, historically the most fragile parts of token-based auth, into a runtime detail your libraries handle. Learn the fields, respect the kid, cache sensibly, and rotate with overlap, and token verification will survive every key change without a redeploy.
If you’d rather not babysit realm keys, rotation schedules, and Keycloak upgrades yourself, Skycloak’s managed Keycloak hosting handles the operational side, keys included, so your team can stay focused on the application.