keycloak

The nonce is missing from the id_token in the response


I'm using Keycloak 21 for authentication, and I’m having an issue where the nonce value is not included in the id_token returned after i invoke /token. I’m passing the nonce in the /auth request,


Solution

  • You can get by this method

    URL

    GET ${KEYCLOAK_URL}/realms/${REALM}/protocol/openid-connect/auth?
    

    query parameters

            client_id: CLIENT_ID,
            redirect_uri: REDIRECT_URI,
            response_type: 'code',
            scope: 'openid',
            nonce: nonce,
    

    Then get ID token at callback URL

    Steps

    Step 1 : launching Keycloak v21 by docker compose

    version: '3.9'
    
    services:
      keycloak:
        image: quay.io/keycloak/keycloak:21.1.2
        container_name: keycloak
        command:
          - start-dev
        environment:
          KEYCLOAK_ADMIN: admin
          KEYCLOAK_ADMIN_PASSWORD: admin
          KC_DB: postgres
          KC_DB_URL_HOST: keycloak-db
          KC_DB_URL_DATABASE: keycloak
          KC_DB_USERNAME: keycloak
          KC_DB_PASSWORD: keycloakpassword
          KC_HOSTNAME: localhost
        ports:
          - "8080:8080"
        depends_on:
          - keycloak-db
        volumes:
          - keycloak-data:/opt/keycloak/data
    
      keycloak-db:
        image: postgres:15
        container_name: keycloak-db
        environment:
          POSTGRES_USER: keycloak
          POSTGRES_PASSWORD: keycloakpassword
          POSTGRES_DB: keycloak
        ports:
          - "5432:5432"
        volumes:
          - postgres-data:/var/lib/postgresql/data
    
    volumes:
      keycloak-data:
      postgres-data:
    

    more detail in here

    Step 2. create my-realm, my-cleint and user1

    enter image description here

    Call back URL

    http://localhost:3000/callback
    

    enter image description here

    Client Secret copy from UI

    enter image description here

    username is User1 and password is 1234

    enter image description here

    enter image description here

    Step 3 : running express server

    install dependencies

    npm install express axios querystring jwt-decode@3.1.2 crypto
    

    demo.js

    const express = require('express');
    const axios = require('axios');
    const crypto = require('crypto');
    const querystring = require('querystring');
    const jwtDecode = require('jwt-decode');
    
    const app = express();
    const PORT = 3000; //replace your Redirect PORT
    
    // Keycloak Configuration
    const KEYCLOAK_URL = 'http://localhost:8080'; // Replace with your Keycloak server URL
    const REALM = 'my-realm'; // Replace with your realm name
    const CLIENT_ID = 'my-client'; // Replace with your client ID
    const CLIENT_SECRET = '[your my-client password]'; // Replace if your client is confidential
    const REDIRECT_URI = 'http://localhost:3000/callback'; // Replace with your app's redirect URI
    
    // Generate a random nonce
    const generateNonce = () => crypto.randomBytes(16).toString('base64');
    
    // Store the nonce temporarily (use a proper session store in production)
    let storedNonce;
    
    // Routes
    
    // 1. Start Authorization
    app.get('/login', (req, res) => {
        const nonce = generateNonce();
        storedNonce = nonce; // Save the nonce for validation later
    
        const authUrl = `${KEYCLOAK_URL}/realms/${REALM}/protocol/openid-connect/auth?` + querystring.stringify({
            client_id: CLIENT_ID,
            redirect_uri: REDIRECT_URI,
            response_type: 'code',
            scope: 'openid',
            nonce: nonce,
        });
    
        res.redirect(authUrl); // Redirect user to Keycloak login
    });
    
    // Step 2. Handle Callback for get tokens
    app.get('/callback', async (req, res) => {
        const { code } = req.query;
    
        if (!code) {
            return res.status(400).send('Authorization code not found!');
        }
    
        try {
            const tokenResponse = await axios.post(
                `${KEYCLOAK_URL}/realms/${REALM}/protocol/openid-connect/token`,
                querystring.stringify({
                    grant_type: 'authorization_code',
                    code: code,
                    redirect_uri: REDIRECT_URI,
                    client_id: CLIENT_ID,
                    client_secret: CLIENT_SECRET,
                }),
                { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
            );
    
            const { id_token } = tokenResponse.data;
    
            // Decode and validate the ID token
            const decodedToken = jwtDecode(id_token);
    
            if (decodedToken.nonce !== storedNonce) {
                return res.status(401).send('Invalid nonce!');
            }
    
            res.send({
                message: 'Login successful!',
                id_token: decodedToken,
            });
        } catch (error) {
            console.error('Error exchanging code for tokens:', error);
            res.status(500).send('Error exchanging code for tokens.');
        }
    });
    
    // Start the Express server
    app.listen(PORT, () => {
        console.log(`Server running at http://localhost:${PORT}`);
    });
    

    run it

    node demo.js
    

    enter image description here

    Step 4 Get token by Browser

    http://localhost:3000/login
    

    Result

    enter image description here