logo

How to verify a Keycloak-issued access token on the backend?

To verify Keycloak-issued access tokens, you need to ensure the token’s signature, expiration, and claims are valid. Keycloak uses JSON Web Tokens (JWTs) for authentication, which can be verified using two main methods:

  • Public Key Verification: Validate the token’s signature locally using Keycloak’s RSA public key. This is faster and avoids network dependency, but it doesn’t check real-time token status.
  • Token Introspection API: Query Keycloak’s server to verify the token’s status and metadata in real time. This is more secure but adds network overhead.

Quick Comparison

Method Network Dependency Real-time Status Performance Impact Security Level Complexity
Public Key Verification No No Minimal High Moderate
Token Introspection Yes Yes Higher Highest Simple

Key Steps for Verification:

  1. Signature Check: Use a public key or introspection to ensure the token’s integrity.
  2. Claim Validation: Verify claims like iss (issuer), exp (expiration), aud (audience), and roles.
  3. Security Best Practices: Regularly refresh public keys and validate algorithms.

Choose the method based on your performance and security needs. For detailed code examples in Java, JavaScript, and Python, refer to the main article.

Keycloak Access Token Validation | Backend JWT Verification

Keycloak

sbb-itb-9d854a3

Access Token Basics

Keycloak creates access tokens as JSON Web Tokens (JWTs), which are made up of three parts: a header, a payload, and a signature.

How Tokens Are Generated

Keycloak follows a three-step process to issue a JWT after authentication:

  1. Authentication: Verifies user credentials using a user store or external identity providers like LDAP, Active Directory, or social logins.
  2. Claims Assembly: Gathers user identity details, assigned roles, and sets an expiration time for the token.
  3. Signing: Secures the header and payload using either an HMAC secret or an RSA key pair.

Breaking Down the Token Structure

A Keycloak JWT includes the following components:

  • Header: Contains metadata like the algorithm (alg) and token type (typ).
  • Payload: Holds user-specific data such as the subject (sub), expiration time (exp), roles, and any custom claims.
  • Signature: A cryptographic signature that ensures the token’s integrity by signing the header and payload.
  • Encoding: The header and payload are Base64Url-encoded and separated by periods. The signature is then applied to both.

Here’s an example of a JWT header and payload:

// Header example
{
  "alg": "RS256",
  "typ": "JWT"
}

// Payload example
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "iat": 1516239022
}

Up next, we’ll dive into backend techniques for verifying these tokens.

Token Verification Methods

When verifying access tokens issued by Keycloak, the method you choose should depend on factors like performance, real-time status requirements, and security considerations.

Public Key Verification

This method uses the realm’s RSA public key to validate tokens locally. Here’s how it works:

  • Retrieve the realm’s public key from your Keycloak server.
  • Convert the key to PEM format to make it compatible with cryptographic tools.
  • Use the key to confirm the token’s signature.

Token Introspection API

The Token Introspection API, an extension of OAuth 2.0, allows a resource server to query Keycloak’s authorization server for real-time token verification. The introspection endpoint is:

http://${host}:${port}/auth/realms/${realm_name}/protocol/openid-connect/token/introspect
``` [6]

To optimize its use:
- Cache responses until the token expires.
- Limit the frequency of API calls to avoid overloading the server.
- Always use TLS to secure requests [5].

Method Selection Guide

Here’s a comparison to help you decide which approach fits your needs:

Feature Public Key Verification Token Introspection
Network Dependency No – Local verification Yes – Requires API calls
Real-time Status Signature only Active status and metadata
Performance Impact Minimal Higher due to network calls
Security Level High (integrity check) Highest (real-time validation)
Implementation Complexity Moderate Simple

Public Key Verification is ideal if:

  • You need fast token validation.
  • Reducing network latency is a priority.
  • Local checks meet your security needs.

Token Introspection is better suited for scenarios where:

  • Real-time token status is essential.
  • Access to additional metadata is required.
  • Centralized validation aligns with your security policies.

Once you’ve chosen a method, you can move on to decoding and validating the token’s payload and claims.


## Token Validation Steps

Start by splitting the token on the `.` character, then Base64Url-decode the first two segments and parse them as JSON. Here's a simple JavaScript function to handle it:

```javascript
function decodeToken(token) {
  const [header, payload] = token.split('.');
  return { header: JSON.parse(atob(header)), payload: JSON.parse(atob(payload)) };
}
```

**Next up: check the signature for integrity.**

### Signature Verification

To verify the signature, follow these steps with your preferred method and the public key:

1.  Retrieve the realm's JWKS from:  
    `https://localhost:8080/auth/realms/master/protocol/openid-connect/certs`
2.  Identify the JWK with the matching `kid` and convert it to PEM format.
3.  Use your preferred JWT library or OpenSSL to validate the token's signature using the PEM public key.

**Once the signature is verified, move on to validating the token claims.**

### Claim Validation

The table below outlines the key token claims and how to validate them:

| Claim | Description | Validation |
| --- | --- | --- |
| `iss` | Token issuer | Must match the Keycloak realm URL |
| `exp` | Expiration timestamp | Must be a future date/time |
| `aud` | Token audience | Must include this service's identifier |
| `roles` | User permissions | Must contain all required roles |

## Code Examples by Framework

Here are backend code examples for implementing signature and claim validation. Each example starts with signature verification and then moves on to claim checks.

### Spring Boot Implementation

```java
public class KeycloakTokenVerifier {
    private final JWKSource<SecurityContext> jwkSource;

    public KeycloakTokenVerifier(@Value("${keycloak.jwks-uri}") String jwksUri) {
        this.jwkSource = new RemoteJWKSet<>(new URL(jwksUri));
    }

    public DecodedJWT verifyToken(String token) throws JOSEException {
        // Step 1: Parse and verify signature
        SignedJWT signedJWT = SignedJWT.parse(token);
        JWSHeader header = signedJWT.getHeader();

        // Retrieve public key using header.getKeyID()
        JWK jwk = jwkSource.get(header.getKeyID())
            .stream()
            .findFirst()
            .orElseThrow(() -> new JOSEException("Signing key not found"));

        if (!signedJWT.verify(jwk)) {
            throw new JOSEException("Invalid token signature");
        }

        JWTClaimsSet claims = signedJWT.getJWTClaimsSet();
        validateClaims(claims);

        return DecodedJWT.fromToken(signedJWT);
    }

    private void validateClaims(JWTClaimsSet claims) {
        Date now = new Date();
        if (claims.getExpirationTime().before(now)) {
            throw new JOSEException("Token has expired");
        }
    }
}
```

### Express.js Implementation

```javascript
const jwt = require('jsonwebtoken');
const jwksClient = require('jwks-rsa');

const client = jwksClient({
    jwksUri: 'https://your-keycloak-server/auth/realms/your-realm/protocol/openid-connect/certs'
});

const fetchPublicKey = async (header) => {
    return new Promise((resolve, reject) => {
        client.getSigningKey(header.kid, (err, key) => {
            if (err) return reject(err);
            resolve(key.publicKey || key.rsaPublicKey);
        });
    });
};

const verifyToken = async (token) => {
    try {
        // Step 2: Validate issuer and audience
        const decodedToken = jwt.decode(token, { complete: true });
        const signingKey = await fetchPublicKey(decodedToken.header);

        return jwt.verify(token, signingKey, {
            issuer: 'https://your-keycloak-server/auth/realms/your-realm',
            audience: 'your-client-id'
        });
    } catch (error) {
        throw new Error(`Unauthorized: ${error.message}`);
    }
};

const authMiddleware = async (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) return res.status(401).json({ error: 'No token provided' });
    try {
        req.user = await verifyToken(token);
        next();
    } catch (err) {
        return res.status(401).json({ error: err.message });
    }
};
```

### Python Implementation

This example uses Authlib's JsonWebToken for decoding, verifying, and validating claims.

```python
from authlib.jose import jwt
from authlib.jose.errors import JoseError
from functools import wraps
import requests

class KeycloakTokenValidator:
    def __init__(self, base_url, realm_name):
        self.base_url = base_url
        self.realm_name = realm_name
        self.jwks = requests.get(f"{base_url}/auth/realms/{realm_name}/protocol/openid-connect/certs").json()

    def verify_token(self, token):
        try:

Step 3: Enforce exp and aud claims

claims_options = { "iss": { "essential": True, "values": [f"{self.base_url}/auth/realms/{self.realm_name}"] }, "exp": {"essential": True}, "aud": {"essential": True} } claims = jwt.decode(token, self.jwks, claims_options=claims_options) claims.validate() return claims except JoseError as e: raise ValueError(f"Token validation failed: {str(e)}")

def require_token(f): @wraps(f) def decorated(*args, **kwargs): token = request.headers.get(‘Authorization’, ”).split(‘ ‘) try: g.user = validator.verify_token(token) return f(*args, **kwargs) except ValueError as e: return jsonify({"error": str(e)}), 401 return decorated


## Security Guidelines

Once signature and claim validation are complete, follow these steps to maintain security:

### Required Validation Steps

| Validation Step | Description | Security Impact |
| --- | --- | --- |
| Algorithm Verification | Confirm the `alg` header aligns with an approved list | Prevents algorithm manipulation |
| Signature Verification | Use the public key from JWKS to verify the token's signature | Ensures token integrity |
| Issuer Validation | Check that the `iss` claim matches your trusted issuer(s) | Prevents token forgery |
| Audience Check | Verify the `aud` claim includes your application | Prevents token misuse |
| Temporal Claims | Validate `exp`, `nbf`, and `iat` while allowing clock skew | Prevents replay attacks |
| Key ID Matching | Re-fetch JWKS if the token's `kid` doesn’t match cached keys | Ensures correct key usage during rotation |

### Public Key Management

Store JWKS locally for better performance, refresh it regularly, and retrieve it again if a token’s `kid` doesn’t match the cached keys.

### Security Protocols

Avoid manually validating JWTs to reduce the risk of mistakes. Instead, rely on a trusted library or middleware to handle decoding, signature verification, and claim checks.

Next: review the article summary to reinforce key takeaways.

## Summary

Token verification protects backend services and requires careful implementation and secure key management.

Here’s a quick breakdown of the key verification steps:

| Priority | Verification Step | Purpose |
| --- | --- | --- |
| Critical | Signature Check | Confirms the token hasn’t been tampered with |
| High | Temporal Claims | Verifies the token is used within its valid timeframe |
| High | Issuer Validation | Confirms the token’s source is trustworthy |
| Medium | Audience Check | Ensures the token is intended for the right recipient |
| Medium | Role Verification | Regulates access based on user permissions |

Refer to the framework examples provided earlier to implement these steps. Make sure to include signature validation, token decoding, claim checks, error handling, and JWKS management in your approach.

## FAQs

::: faq
### What are the pros and cons of using Public Key Verification versus Token Introspection to validate Keycloak access tokens?

Public Key Verification and Token Introspection are two common methods for validating Keycloak access tokens, each with its own strengths and limitations.

**Public Key Verification** is fast and efficient because it allows your backend to verify tokens locally without making network requests. By using Keycloak's public key, you can validate the token's signature and check its claims (like expiration, audience, and roles). However, this approach requires your backend to handle token validation logic and keep the public key updated if it changes.

**Token Introspection**, on the other hand, involves sending the token to Keycloak's introspection endpoint for validation. This method ensures real-time verification, including whether the token has been revoked, but it can introduce latency and depends on network reliability.

Choosing between the two depends on your application's requirements. Public Key Verification is ideal for performance-critical systems, while Token Introspection is better suited for scenarios where real-time validation is essential.
:::

::: faq
### How can I securely handle and update public keys for verifying Keycloak JWTs in my backend application?

To securely handle and update public keys for verifying Keycloak JWTs, you should focus on **key rotation**, **endpoint security**, and **caching** strategies.

Regularly rotate your cryptographic keys to reduce security risks. Keycloak simplifies this process with its JWKS (JSON Web Key Set) support, allowing your application to fetch updated keys automatically from the JWKS URL. Ensure the JWKS endpoint is protected with HTTPS to encrypt data in transit and prevent unauthorized access. Additionally, implement caching mechanisms to balance performance with security. Use local caching for a reasonable duration and configure cache headers properly to ensure your application retrieves updated keys as needed.

By following these practices, you can maintain a secure and efficient system for managing public keys in your backend application.
:::

::: faq
### Which claims should I validate in a Keycloak-issued access token, and why are they important for security?

When verifying a Keycloak-issued access token, it's essential to validate specific claims to ensure security and proper authorization. Key claims to check include:

- **Expiration (`exp`)**: Ensure the token has not expired to prevent unauthorized access.
- **Issuer (`iss`)**: Confirm it matches your Keycloak server's URL to verify the token's origin.
- **Audience (`aud`)**: Validate that the token is intended for your application.
- **Roles and Permissions**: Check the user's roles or permissions to ensure they have the correct access rights.

By validating these claims, you can protect your backend services from unauthorized or malicious requests, ensuring only legitimate users can access your resources.
:::

Related posts

Leave a Comment

© 2025 All Rights Reserved. Made by Yasser