Token Introspection vs Local JWT Validation in Keycloak
Last updated: June 2026
There are two ways to validate a Keycloak access token: local JWT validation, where your API verifies the token’s signature against Keycloak’s public keys (JWKS) and checks claims offline — fast, no network call, but unable to detect instant revocation; and token introspection (RFC 7662), where your API calls Keycloak’s introspection endpoint to ask whether the token is still active — authoritative and revocation-aware, but adds latency and load. Most APIs should use local validation backed by short token lifetimes; switch to introspection when immediate revocation matters or when you are dealing with opaque tokens.
How Local JWT Validation Works
A Keycloak access token is a signed JSON Web Token (RFC 7519). When Keycloak issues a token, it signs it with a private key using RS256 (or ES256 in some configurations). Your API can validate that token entirely offline by fetching Keycloak’s public key set and verifying the signature locally.
Step 1: Fetch Keycloak’s JWKS
Keycloak exposes its public keys at a well-known JWKS URI:
GET /realms/{realm}/protocol/openid-connect/certs
For example, if your realm is myrealm on a locally running Keycloak instance:
curl https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs
The response is a JSON object containing one or more public keys (a JWK Set). Your library caches these keys and rotates them automatically when Keycloak performs a key rotation.
Step 2: Verify the Signature and Claims
Once your library has the public keys, it verifies every incoming token by:
- Parsing the JWT header to identify the
kid(key ID) and algorithm - Locating the matching public key in the cached JWKS
- Verifying the RS256 signature
- Checking the standard claims:
exp— token has not expirediss— matches your Keycloak realm URL (e.g.,https://keycloak.example.com/realms/myrealm)aud— matches your client ID or resource server audienceazp— authorized party matches expected client
Here is a minimal example using Node.js with the jose library:
import { createRemoteJWKSet, jwtVerify } from 'jose';
const JWKS = createRemoteJWKSet(
new URL('https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs')
);
async function validateToken(accessToken) {
const { payload } = await jwtVerify(accessToken, JWKS, {
issuer: 'https://keycloak.example.com/realms/myrealm',
audience: 'my-api-client',
});
return payload; // contains sub, roles, custom claims, etc.
}
The jose library caches the JWKS and re-fetches it when it encounters an unknown kid, which handles Keycloak key rotations transparently. For a deeper look at the claims and signing algorithms involved, see our guide on JWT best practices for developers.
Why Local Validation Is Fast
No network call is made per request. After the initial JWKS fetch (which is cached), validation is pure in-memory cryptography — typically under 1 ms. This makes local validation suitable for high-throughput APIs and microservices that handle thousands of requests per second.
How Token Introspection Works
Token introspection, defined in RFC 7662, lets a resource server ask the authorization server — Keycloak in this case — whether a given token is currently active. Unlike local validation, introspection is a live network call made for every request (unless you cache the result).
The Keycloak Introspection Endpoint
Keycloak exposes the introspection endpoint at:
POST /realms/{realm}/protocol/openid-connect/token/introspect
The request must include the token being checked and valid client credentials (client ID and secret, or a client assertion):
curl -X POST
https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token/introspect
-H "Content-Type: application/x-www-form-urlencoded"
-d "token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
-d "client_id=my-resource-server"
-d "client_secret=s3cr3t"
Keycloak responds with a JSON object. The only field guaranteed by the RFC is active:
{
"active": true,
"sub": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"username": "alice",
"client_id": "frontend-app",
"exp": 1750694400,
"iat": 1750690800,
"iss": "https://keycloak.example.com/realms/myrealm",
"scope": "openid email profile",
"realm_access": {
"roles": ["user", "admin"]
}
}
If the token is expired, revoked, or otherwise invalid, Keycloak responds with { "active": false }. You should treat any response where active is not true as an unauthorized request.
Introspection With a Confidential Client in Keycloak 26
In Keycloak 26.x, ensure your resource server is registered as a confidential client with the Service Account Roles capability enabled. The service account needs at least the view-users role from the realm-management client if you need to resolve user attributes during introspection. The introspection endpoint itself does not require special roles beyond valid client credentials.
The Core Trade-off: Performance vs. Revocation Visibility
The fundamental tension between the two approaches comes down to one question: how quickly do you need to react to a revoked token?
Local validation and the revocation gap
When you validate a token locally, you are trusting the token’s exp claim. If a token was issued with a 15-minute lifetime and a user’s account is suspended at minute 2, your API will continue to accept that token for another 13 minutes — because your local validation cannot know the token has been revoked on the Keycloak side.
Keycloak does not push revocation events to your APIs. When a user logs out, changes their password, or an administrator disables the account, the existing access tokens remain cryptographically valid until they expire. Keycloak supports a logout event mechanism (backchannel logout via OIDC) for session-aware clients, but stateless APIs processing bearer tokens do not participate in this flow by default.
The practical mitigation is short access token lifetimes. In Keycloak’s realm settings, set your access token lifespan to 5 minutes or less for sensitive APIs. You can configure this under Realm Settings > Tokens > Access Token Lifespan in the Keycloak admin console. Short lifetimes shrink the revocation gap to an acceptable window for most use cases. See our guide on JWT token lifecycle management for the full picture on expiration and refresh strategy.
Introspection and real-time revocation
Token introspection eliminates the revocation gap entirely. Keycloak checks its own session store and token state on every introspection call, returning active: false the moment a token is revoked — whether because the user logged out, an administrator killed the session, or a client secret was rotated.
The cost is a synchronous HTTP call on every API request. In a low-latency internal network, this might add 5–20 ms per request. Across a public internet connection or through multiple hops, the overhead can be significant. More importantly, your API’s reliability now depends on Keycloak’s availability: if the introspection endpoint is unreachable, you cannot serve requests unless you implement fallback logic.
Comparison: Local Validation vs. Token Introspection
| Dimension | Local JWT Validation | Token Introspection |
|---|---|---|
| Latency per request | Near-zero (in-memory crypto) | 5–50 ms network round-trip |
| Revocation detection | Delayed until token expiry | Immediate |
| Keycloak dependency | Only for JWKS fetch (cached) | Every request |
| Scalability | Excellent — no Keycloak load | Adds load to Keycloak per request |
| Opaque token support | No — requires JWT format | Yes — works with any token format |
| Implementation complexity | Low — most frameworks have built-in support | Moderate — requires caching to be production-safe |
| When to use | Short-lived JWTs, high-throughput APIs, microservices | Sensitive operations, opaque tokens, real-time revocation requirements |
Caching Introspection Results
If you need introspection’s revocation guarantees but cannot afford a network call per request, caching is the standard mitigation. Cache the introspection response keyed by token (or its hash) with a TTL shorter than the token’s remaining lifetime.
import { createHash } from 'crypto';
import NodeCache from 'node-cache';
const introspectionCache = new NodeCache({ stdTTL: 30 }); // 30-second TTL
async function introspectToken(token, keycloakBaseUrl, clientId, clientSecret) {
const cacheKey = createHash('sha256').update(token).digest('hex');
const cached = introspectionCache.get(cacheKey);
if (cached !== undefined) return cached;
const response = await fetch(
`${keycloakBaseUrl}/protocol/openid-connect/token/introspect`,
{
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
token,
client_id: clientId,
client_secret: clientSecret,
}),
}
);
const result = await response.json();
introspectionCache.set(cacheKey, result);
return result;
}
With a 30-second cache TTL, your API makes one introspection call per token per 30-second window rather than one per request. This dramatically reduces Keycloak load while keeping the revocation gap small. Adjust the TTL based on your security requirements — the lower the TTL, the closer you get to real-time revocation at the cost of higher Keycloak traffic.
Opaque Tokens vs. JWT Access Tokens
Keycloak issues JWT access tokens by default in Keycloak 26.x. However, some configurations or integrations use opaque tokens — short random strings that carry no information themselves. Opaque tokens cannot be validated locally because there is no signature to verify and no claims to inspect. Token introspection is the only option for opaque tokens.
If your Keycloak client is configured with “Access Token Type” set to a non-JWT format, or if you are dealing with third-party tokens from an upstream identity provider brokered through Keycloak, you may encounter opaque tokens. Check your client’s Advanced Settings tab in the Keycloak admin console to confirm the token format. For most modern configurations, you will be dealing with JWTs.
Use our JWT Token Analyzer to inspect any token — if it decodes into a valid JSON structure with header, payload, and signature, it is a JWT and local validation is an option. If it does not decode, it is opaque and you must use introspection.
Hybrid Approaches
A common pattern in production systems combines both methods:
- Local validation first: Verify the JWT signature and check
exp,iss,aud. Reject tokens that fail this check immediately, without touching Keycloak. - Selective introspection: For specific high-value operations (payment processing, admin actions, privilege escalation), call the introspection endpoint to confirm the token is still active.
This approach gives you the performance of local validation for the vast majority of requests and the revocation guarantees of introspection where they matter most.
Another hybrid pattern uses short-lived access tokens with refresh token rotation to minimize the revocation gap without relying on introspection. With a 5-minute access token lifetime and refresh token rotation enabled in Keycloak, revoked sessions are effectively cut off within minutes, without any per-request introspection overhead.
Gateway and Proxy Scenarios
API gateways (Kong, Nginx, Traefik, AWS API Gateway, etc.) are a natural place to centralize token validation. Both approaches work at the gateway layer, but the trade-offs shift slightly:
Local validation at the gateway means your microservices receive pre-validated requests. The gateway fetches the JWKS once, validates all incoming tokens, and forwards only valid requests downstream. This is highly efficient and the standard pattern for Keycloak token validation for APIs.
Introspection at the gateway means the gateway calls Keycloak on every request. This is manageable if the gateway implements response caching, but adds Keycloak as a hard dependency in your request path. If Keycloak is unavailable, the gateway blocks all traffic.
For most gateway deployments with JWTs, local validation with short access token lifetimes is the recommended default. Enable introspection at the gateway level only if your use case has hard revocation requirements — for example, financial services, healthcare, or multi-tenant SaaS environments where a compromised token must be invalidated immediately. If you need to adjust CORS settings for your Keycloak OIDC client in these gateway deployments, ensure your introspection endpoint’s origin is also whitelisted.
Frequently asked questions
Should I use token introspection or local JWT validation?
Use local JWT validation for most APIs. It is faster, scales without adding load to Keycloak, and is supported natively by virtually every OAuth 2.0 library. Combine it with short access token lifetimes (5 minutes or less) to keep the revocation gap small. Switch to token introspection — or a hybrid approach with selective introspection — when you have hard requirements for immediate revocation, such as financial transactions, healthcare data access, or privileged administrative operations. Opaque tokens always require introspection regardless of your security requirements.
What is the Keycloak introspection endpoint?
The Keycloak token introspection endpoint follows the path POST /realms/{realm}/protocol/openid-connect/token/introspect. Replace {realm} with your realm name. The request must include the token parameter (the access token to check) and valid client credentials (client_id and client_secret, or a signed client JWT assertion for private key JWT authentication). Keycloak returns a JSON response with an active field that is true if the token is currently valid or false if it is expired, revoked, or otherwise inactive.
How do I check if a Keycloak token is revoked?
There is no direct “is this token revoked” API in Keycloak separate from introspection. The introspection endpoint (/realms/{realm}/protocol/openid-connect/token/introspect) is the authoritative check — a response of { "active": false } means the token is no longer valid, whether because it expired, the session was terminated, the user was disabled, or the token was explicitly revoked. If you are only doing local JWT validation, you cannot detect revocation before the exp claim passes. The practical solution is to keep access tokens short-lived and review your JWT token lifecycle management strategy.
Can I use both local validation and introspection together?
Yes, and this is a recommended pattern for production APIs with mixed security requirements. Validate the JWT signature and standard claims locally on every request — this is a cheap in-memory operation that immediately rejects malformed, expired, or tampered tokens. For requests that touch sensitive resources, add a follow-up introspection call to confirm the token is still active in Keycloak’s session store. This hybrid approach gives you good baseline performance with targeted revocation guarantees where you need them most.
Which token validation method does Keycloak prefer for microservices?
Keycloak’s own documentation and the broader OAuth 2.0 ecosystem strongly favor local JWT validation for microservices. The rationale is that microservices typically handle high request volumes, and adding a synchronous Keycloak call per request creates a performance bottleneck and a single point of failure. The recommended microservices pattern is local validation with short-lived JWTs (5 minutes) and refresh token rotation, with each service independently verifying tokens using the shared JWKS endpoint. This aligns with the zero-trust principle of verifying tokens at every service boundary without introducing cross-service coupling to a central validation endpoint.
Summary
Local JWT validation and token introspection both have a legitimate place in a Keycloak deployment. Local validation is the right default: it is fast, scales linearly with your API traffic, and requires no runtime dependency on Keycloak beyond the cached JWKS. Its one weakness — inability to detect revocation before expiry — is adequately addressed by keeping access tokens short-lived.
Token introspection fills the gap when short token lifetimes are not sufficient. It is the only viable option for opaque tokens and the right choice for operations where a revoked token must be rejected immediately. The cost is a network call per request and increased load on Keycloak, both of which are manageable with response caching.
In most production Keycloak deployments, the answer is not one or the other — it is local validation as the baseline with selective introspection for high-sensitivity paths, paired with short token lifetimes and refresh token rotation to keep the overall attack surface small.
Ready to run Keycloak without managing the infrastructure? Skycloak provides fully managed Keycloak hosting with built-in high availability, automatic upgrades, and the performance headroom to handle introspection at scale.
Ready to simplify your authentication?
Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.