Search code examples
keycloakopenid-connect

I can't get access and refresh tokens through my authorization code with Keycloak APIs


I want to authenticate my application with Keycloak. I have managed to get an authorization code but I want to get the access and refresh tokens. Here is my code:

async function exchangeAuthorizationCodeForTokens(authorizationCode, clientId, redirectUri, realmName, keycloakUrl) {
    
    const tokenEndpoint = `${keycloakUrl}/realms/${realmName}/protocol/openid-connect/token`;

    const codeVerifier = generateCodeVerifier();
    const data = {
        grant_type: 'authorization_code',
        client_id: clientId,
        redirect_uri: redirectUri,
        code: authorizationCode,
        code_verifier: codeVerifier
    };

    try {
        const response = await axios.post(tokenEndpoint, new URLSearchParams(data), {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }), // Ignore self-signed certificate
        });

        console.log('Token exchange successful');
        console.log(response.data);
        return {
            access_token: response.data.access_token,
            refresh_token: response.data.refresh_token,
        };
    } catch (error) {
        console.error('Token exchange failed:', error.response ? error.response.data : error.message);
        return false;
    }
}

But I always get the following error, even if my user and my client are registered in the Keycloak server:

Token exchange failed: {
    error: 'unauthorized_client',
    error_description: 'Invalid client or Invalid client credentials'
}

Solution

  • You need to send client_secret too.

    async function exchangeAuthorizationCodeForTokens(authorizationCode, clientId, redirectUri, realmName, keycloakUrl) {
        
        const tokenEndpoint = `${keycloakUrl}/realms/${realmName}/protocol/openid-connect/token`;
    
        const codeVerifier = generateCodeVerifier();
        const data = {
            grant_type: 'authorization_code',
            client_id: clientId,
            client_secret: 'Srgc2z4UWrx4RGNg0GZWijLa2uLeR9Yl',
            redirect_uri: redirectUri,
            code: authorizationCode,
            code_verifier: codeVerifier
        };
    
        try {
            const response = await axios.post(tokenEndpoint, new URLSearchParams(data), {
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }), // Ignore self-signed certificate
            });
    
            console.log('Token exchange successful');
            console.log(response.data);
            return {
                access_token: response.data.access_token,
                refresh_token: response.data.refresh_token,
            };
        } catch (error) {
            console.error('Token exchange failed:', error.response ? error.response.data : error.message);
            return false;
        }
    }
    

    It should be copy from UI enter image description here

    Demo code

    Save as 'token.js' file name

    const express = require('express');
    const axios = require('axios');
    const crypto = require('crypto');
    
    // Express setup
    const app = express();
    const port = 3000;
    
    // Function to generate a code verifier for PKCE
    function generateCodeVerifier() {
        return crypto.randomBytes(32).toString('base64')
            .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
    }
    
    // Your existing function
    async function exchangeAuthorizationCodeForTokens(authorizationCode, clientId, redirectUri, realmName, keycloakUrl) {
        
        const tokenEndpoint = `${keycloakUrl}/realms/${realmName}/protocol/openid-connect/token`;
    
        const codeVerifier = generateCodeVerifier();
        const data = {
            grant_type: 'authorization_code',
            client_id: clientId,
            client_secret: 'Srgc2z4UWrx4RGNg0GZWijLa2uLeR9Yl',
            redirect_uri: redirectUri,
            code: authorizationCode,
            code_verifier: codeVerifier
        };
    
        try {
            const response = await axios.post(tokenEndpoint, new URLSearchParams(data), {
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                httpsAgent: new (require('https').Agent)({ rejectUnauthorized: false }), // Ignore self-signed certificate
            });
    
            console.log('Token exchange successful');
            console.log(response.data);
            return {
                access_token: response.data.access_token,
                refresh_token: response.data.refresh_token,
            };
        } catch (error) {
            console.error('Token exchange failed:', error.response ? error.response.data : error.message);
            return false;
        }
    }
    
    // TODO: Replace these with your actual configuration values
    const clientId = 'my_client';
    const redirectUri = 'http://localhost:3000/auth/callback';
    const realmName = 'my-realm';
    const keycloakUrl = 'http://localhost:8080/auth';
    const responseType = 'code'; 
    
    app.get('/login', (req, res) => {
        // Construct the Keycloak login URL
        const keycloakLoginUrl = `${keycloakUrl}/realms/${realmName}/protocol/openid-connect/auth?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=${encodeURIComponent(responseType)}&scope=openid`;
    
        // Redirect the user to the Keycloak login page
        res.redirect(keycloakLoginUrl);
    });
    app.get('/auth/callback', async (req, res) => {
        const authorizationCode = req.query.code;
        if (!authorizationCode) {
            return res.status(400).send('Authorization code is required');
        }
    
        // Exchange authorization code for tokens
        const tokens = await exchangeAuthorizationCodeForTokens(authorizationCode, clientId, redirectUri, realmName, keycloakUrl);
        
        if (tokens) {
            res.send(`Access Token: ${tokens.access_token}<br>Refresh Token: ${tokens.refresh_token}`);
        } else {
            res.status(500).send('Failed to exchange authorization code for tokens');
        }
    });
    
    app.listen(port, () => {
        console.log(`Server running on http://localhost:${port}`);
    });
    

    Redirect URL & Authorization setting enter image description here

    Running server

    node token.js
    

    Login URL

    http://localhost:3000/login
    

    Result enter image description here