Search code examples
node.jskeycloakkeycloak-rest-apikeycloak-nodejs-connect

Use keycloak.protect() in a function that will be used as a middleware in nodejs/expressjs


The route look like this where verifytoken is a middleware.

router.get('/v1/endpoint', verifytoken, apis.getData);

In order to protect this route we will generally use keycloak.protect(); but I want to use verifytoken which is a middleware and always go to apis.getDatat irrespective of whether the route is protected or not. But the middleware function will attach a string based on if the user is authenticated or not.

router.get('/v1/endpoint', keycloak.protect(), apis.getData);

This code should run and protect the route and based on if it's authorized or not I want to add verified string that will be used by apis.getData to send the correct amount of data.

const keycloak = require('../../keycloak').getKeycloak();

/**
 * @param {Object} request - request object with authorization header.
 * @param {Object} response - response object.
 * @param {Object} next - calls the next function with user payload.
 */
module.exports = function(request, response, next) {
    // authorization token.
    const token = request.headers.authorization;

    // if token is not sent the authorization fails.
    if (!token) {
        return response.status(401).send('Access Denied, missing authorization token!');
    }

    // check if the token is valid or not.
    try {
        const verified = {};
        if (keycloak.protect()) {
            verified.verified = 'verified';
        }
        console.log('Token is verified', verified);
        response.locals.user = verified;
        next();
    } catch (err) {
        console.log('Token invalid!!!');
        response.locals.user = 'unknown';
        next();
    }
};

Code for configuring keycloak

const session = require('express-session');
const Keycloak = require('keycloak-connect');
const keycloakConfig = require('./keycloak.json');

let _keycloak;

function initKeycloak() {
    if (_keycloak) {
        console.warn('Trying to init Keycloak again!');
        return _keycloak;
    }

    console.log('Initializing Keycloak...');
    const memoryStore = new session.MemoryStore();
    _keycloak = new Keycloak({ store: memoryStore }, keycloakConfig);
    return _keycloak;
}

function getKeycloak() {
    if (!_keycloak) {
        console.error('Keycloak has not been initialized. Please called init first.');
    }
    return _keycloak;
}

module.exports = {
    initKeycloak,
    getKeycloak,
};

Solution

  • I will shows how to use custom token verify My demo base on example of keycloak-nodejs-connect

    I just modified code only. it is not full example code.

    UI in view/index.html

    <div class="nav">
        <ul>
            <li><a href="/login">Login</a></li>
            <li><a href="/protected/resource">Protected Resource</a></li>
            <li><a href="/verify">Token Verify</a></li>
            <li><a href="/logout">Logout</a></li>
        </ul>
    </div>
    

    javascript in index.js

    function my_token_verify(token, req, res) {
      // no token, it makes access denied
      if (!token || !token.token) {
        return false;
      }
      console.log(token.token);
      console.log(token.header);
      console.log(token.content);
      console.log(token.signature);
      console.log(token.clientId);
      // whatever your own logic for verify token
      if (token.header.alg == 'RS256' && token.header.typ == 'JWT')
        return true;
      return false;
    }
    
    app.get('/verify', keycloak.protect(my_token_verify), function (req, res) {
      res.render('index', {
        result: JSON.stringify(JSON.parse(req.session['keycloak-token']), null, 4),
        event: 'verify token: true'
      })
    })
    

    Result of browser, if return true from my_token_verify(), will shows this screen.

    enter image description here

    if return false from my_token_verify(), will shows access denied.

    enter image description here VS code debug configuration

    {
        "version": "0.2.0",
        "configurations": [
            {
                "command": "npm start",
                "name": "Run npm start",
                "request": "launch",
                "type": "node-terminal",
                "resolveSourceMapLocations": [
                    "${workspaceFolder}/**",
                    "!**/node_modules/**"
                ]
            }
        ]
    }
    

    Terminal output in VS code

    Debugger attached.
    Example app listening at http://:::3000
    eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJGSjg2R2NGM2pUYk5MT2NvNE52WmtVQ0lVbWZZQ3FvcXRPUWVNZmJoTmxFIn0.eyJleHAiOjE2NjMwOTcxMjksImlhdCI6MTY2MzA2NDc0MSwiYXV0aF90aW1lIjoxNjYzMDYxMTI5LCJqdGkiOiIzNWM0MWIyZi0xMDc3LTQ1ZjMtYmFjOC04N2YxYWUyMDY1MGIiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgxODAvYXV0aC9yZWFsbXMvbm9kZWpzLWV4YW1wbGUiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiOTlhYTBlZDMtMjljMS00NTdhLTg2OGYtMjVjZTlhOTMxZDAzIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoibm9kZWpzLWNvbm5lY3QiLCJzZXNzaW9uX3N0YXRlIjoiNGZmNGFjNGItZTkzOS00YWYzLWJhNDMtZjU4NjlmYTM4NmNkIiwiYWNyIjoiMCIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZGVmYXVsdC1yb2xlcy1ub2RlanMtZXhhbXBsZSJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI0ZmY0YWM0Yi1lOTM5LTRhZjMtYmE0My1mNTg2OWZhMzg2Y2QiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluLXVzZXIifQ.F-u7QIuku81f5e6EAPddKllE_8xGw7zF0GrlPsqUYOAdIlwLvIrCjdLh2l1PmcpE4J6wWS9jLk8swA5hIySy2Z-X9k8OzxqY4nWXSbeOmIKY-fXqRZmV7_nNuo4b3veQv3JPfbpUhP96yFun4jDeTbbJFycvr_u0wg4KZoqrXhc
    {
      alg: 'RS256',
      typ: 'JWT',
      kid: 'FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE'
    }
    {
      exp: 1663097129,
      iat: 1663064741,
      auth_time: 1663061129,
      jti: '35c41b2f-1077-45f3-bac8-87f1ae20650b',
      iss: 'http://localhost:8180/auth/realms/nodejs-example',
      aud: 'account',
      sub: '99aa0ed3-29c1-457a-868f-25ce9a931d03',
      typ: 'Bearer',
      azp: 'nodejs-connect',
      session_state: '4ff4ac4b-e939-4af3-ba43-f5869fa386cd',
      acr: '0',
      realm_access: {
        roles: [
          'offline_access',
          'uma_authorization',
          'default-roles-nodejs-example'
        ]
      },
      resource_access: { account: { roles: [Array] } },
      scope: 'openid email profile',
      sid: '4ff4ac4b-e939-4af3-ba43-f5869fa386cd',
      email_verified: false,
      preferred_username: 'admin-user'
    }
    <Buffer 17 eb bb 40 8b a4 bb cd 5f e5 ee 84 00 f7 5d 2a 59 44 ff cc 46 c3 bc c5 d0 6a e5 3e ca 94 60 e0 1d 22 5c 0b bc 8a c2 8d d2 e1 da 5d 4f 99 ca 44 e0 9e ... 78 more bytes>
    nodejs-connect
    

    Debugging in VS code enter image description here