26

Running keycloak on standalone mode.and created a micro-service by using node.js adapter for authenticating api calls.

jwt token from the keyclaok is sending along with each api calls. it will only respond if the token sent is a valid one.

  • how can i validate the access token from the micro service?
  • is there any token validation availed by keycloak?
basith
  • 740
  • 4
  • 13
  • 26

6 Answers6

36

To expand on troger19's answer:

Question 1: How can I validate the access token from the micro service?

Implement a function to inspect each request for a bearer token and send that token off for validation by your keycloak server at the userinfo endpoint before it is passed to your api's route handlers.

You can find your keycloak server's specific endpoints (like the userinfo route) by requesting its well-known configuration.

If you are using expressjs in your node api this might look like the following:

const express = require("express");
const request = require("request");

const app = express();

/*
 * additional express app config
 * app.use(bodyParser.json());
 * app.use(bodyParser.urlencoded({ extended: false }));
 */

const keycloakHost = 'your keycloak host';
const keycloakPort = 'your keycloak port';
const realmName = 'your keycloak realm';

// check each request for a valid bearer token
app.use((req, res, next) => {
  // assumes bearer token is passed as an authorization header
  if (req.headers.authorization) {
    // configure the request to your keycloak server
    const options = {
      method: 'GET',
      url: `https://${keycloakHost}:${keycloakPort}/auth/realms/${realmName}/protocol/openid-connect/userinfo`,
      headers: {
        // add the token you received to the userinfo request, sent to keycloak
        Authorization: req.headers.authorization,
      },
    };

    // send a request to the userinfo endpoint on keycloak
    request(options, (error, response, body) => {
      if (error) throw new Error(error);

      // if the request status isn't "OK", the token is invalid
      if (response.statusCode !== 200) {
        res.status(401).json({
          error: `unauthorized`,
        });
      }
      // the token is valid pass request onto your next function
      else {
        next();
      }
    });
  } else {
    // there is no token, don't process request further
    res.status(401).json({
    error: `unauthorized`,
  });
});

// configure your other routes
app.use('/some-route', (req, res) => {
  /*
  * api route logic
  */
});


// catch 404 and forward to error handler
app.use((req, res, next) => {
  const err = new Error('Not Found');
  err.status = 404;
  next(err);
});

Question 2: Is there any token validation availed by Keycloak?

Making a request to Keycloak's userinfo endpoint is an easy way to verify that your token is valid.

Userinfo response from valid token:

Status: 200 OK

{
    "sub": "xxx-xxx-xxx-xxx-xxx",
    "name": "John Smith",
    "preferred_username": "jsmith",
    "given_name": "John",
    "family_name": "Smith",
    "email": "john.smith@example.com"
}

Userinfo response from invalid valid token:

Status: 401 Unauthorized

{
    "error": "invalid_token",
    "error_description": "Token invalid: Token is not active"
}

Additional Information:

Keycloak provides its own npm package called keycloak-connect. The documentation describes simple authentication on routes, requiring users to be logged in to access a resource:

app.get( '/complain', keycloak.protect(), complaintHandler );

I have not found this method to work using bearer-only authentication. In my experience, implementing this simple authentication method on a route results in an "access denied" response. This question also asks about how to authenticate a rest api using a Keycloak access token. The accepted answer recommends using the simple authentication method provided by keycloak-connect as well but as Alex states in the comments:

"The keyloak.protect() function (doesn't) get the bearer token from the header. I'm still searching for this solution to do bearer only authentication – alex Nov 2 '17 at 14:02

kfrisbie
  • 826
  • 10
  • 18
  • Hi. But when I hit the userInfo endpoint. I recieve this response everytime. { "sub": "xxxxxxxxxxx", "email_verified": false, "preferred_username": "service-account-testclient" } – Kshitiz Sharma Jan 20 '20 at 10:13
  • But I have the username as 'user'. Can anyone explain why? – Kshitiz Sharma Jan 20 '20 at 10:14
  • Above code snippet worked in my setup. Tried with kecloak-connect, but it did not work as expected. – Pratap Dessai Mar 01 '21 at 10:16
  • Making a request to keycloak server before each request, isn't it slow down the response and hence the app? – vishal munde Jun 29 '21 at 14:13
  • What is the "well-known-configuration" you are referring to? – Linkx_lair Sep 13 '21 at 13:26
  • @Linkx_lair https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig, e.g. for Keycloak maybe https://URL/auth/realms/REALM/.well-known/openid-configuration. – DavidS Jun 29 '22 at 23:00
  • In my opinion the offline token validation described in Markus's answer is a much better practice. Every security library I've used (Node, Java, C#, Drupal, Moodle) supports it. Calling the `userinfo` or `introspection` endpoint creates unnecessary traffic, and one of the main design goals of JWT authentication is to mitigate the scaling problems of server-side session management. I have seen real-life clustered Keycloak (RedHat SSO) systems impacted by misuse of online token validation. – DavidS Jun 29 '22 at 23:06
  • Hi, I have tried your answer, If I am not providing the access_token in the header it throws the unauthorized response without an error message. And if I provide the valid token in header authorization it throws a forbidden error without an error message, Can you tell me why it throws forbidden even thouth the token is valid? – Bennison J Nov 28 '22 at 07:06
22

There are two ways to validate a token:

  • Online
  • Offline

The variant described above is the Online validation. This is of course quite costly, as it introduces another http/round trip for every validation.

Much more efficient is offline validation: A JWT Token is a base64 encoded JSON object, that already contains all information (claims) to do the validation offline. You only need the public key and validate the signature (to make sure that the contents is "valid"):

There are several libraries (for example keycloak-backend) that do the validation offline, without any remote request. Offline validation can be as easy as that:

token = await keycloak.jwt.verifyOffline(someAccessToken, cert);
console.log(token); //prints the complete contents, with all the user/token/claim information...

Why not use the official keycloak-connect node.js library (and instead use keycloak-backend)? The official library is more focused on the express framework as a middleware and does (as far as I have seen) not directly expose any validation functions. Or you can use any arbitrary JWT/OICD library as validation is a standardized process.

Markus
  • 4,062
  • 4
  • 39
  • 42
  • 8
    Yes, online validation is costly, but if you use purely offline validation, how would we know if the token was not invalidated by a logout? – Ala Abid Feb 13 '20 at 16:18
  • 3
    Hello alabid, you are absolutely right. It is a decision and trade off to make. JWTs should anyway be rather short lived. An alternative is some kind of "logout event" pushed to an in memory invalidation store: So you do check every token, but not to a remote service, only to an process/system internal cache that contains pushed invalidations. But I do not know any library that implements this. – Markus Feb 13 '20 at 23:30
  • 1
    Indeed. I am quiet surprised that there's no library that actually does that, makes a good idea for one imho. Anyway I think a short lived token would be enough. One more question please, when it expires we use the refresh token to generate a new one right? – Ala Abid Feb 13 '20 at 23:37
  • 1
    @alabid: Yes your are absolutely right too! As you have written, you use use the "refresh token" to get a new "access token". I think some servers even return a new refresh token, when you query for a new "access token". This is some kind of "refresh token rotation". – Markus Feb 14 '20 at 00:39
  • 2
    I'm using keycloak-connect library for my node-rest-api, but when I logout from react-app or close all session in keycloak admin console before that the token expire, I still can call rest api backend using the previous token generated at login moment (ex. with postman). Are there some method in keycloak library that validate token ? – Hector Aug 20 '20 at 21:40
  • 1
    @ala Typical JWT lifetime is around 3-5 minutes. I'd have to declare that the majority of systems are not so critical that they need less than 3 minute certainty that the user hasn't logged out somewhere else. – DavidS Jun 29 '22 at 22:56
  • 1
    If the user is not the admin in keyclok, then the following API throws 403 error, https://${keycloakHost}:${keycloakPort}/auth/realms/${realmName}/protocol/openid-connect/userinfo – Bennison J Feb 13 '23 at 18:15
8

I would use this UserInfo endpoint for that, with which you can also check other attributes like email as well as what you defined in mappers. You must send access token in header attributes with Bearer Authorization : Bearer access_token

http://localhost:8081/auth/realms/demo/protocol/openid-connect/userinfo

troger19
  • 1,159
  • 2
  • 12
  • 29
2

You must use the introspect token service

Here is an example with CURL and Postman

curl --location --request POST 'https://HOST_KEYCLOAK/realms/master/protocol/openid-connect/token/introspect' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=oficina-virtual' \
--data-urlencode 'client_secret=4ZeE2v' \
--data-urlencode 

'token=eyJhbGciKq4n_8Bn2vvy2WY848toOFxEyWuKiHrGHuJxgoU2DPGr9Mmaxkqq5Kg'

Or with Postman

enter image description here

NOTE: Use the variables exp, iat and active to validate the AccessToken

Ronald Coarite
  • 4,460
  • 27
  • 31
1

@kfrisbie Thanks for you response, with your example I could refactor your code using the keycloak connect adapter:

// app.js
app.use(keycloakConfig.validateTokenKeycloak); // valid token with keycloak server

// add routes
const MyProtectedRoute = require('./routes/protected-routes'); // routes using keycloak.protect('some-role')
app.use('/protected', MyProtectedRoute);

So when authorization header is sended, I can verify that token is still valid against keycloak server, so in case of any logout from admin console, or front spa before expire token, my rest api throws 401 error, in other cases keycloak protect method is used.

// keycloak.config.js
let memoryStore = new session.MemoryStore();
let _keycloak = new Keycloak({ store: memoryStore });

async function validateTokenKeycloak(req, res, next) {
    if (req.kauth && req.kauth.grant) {        
        console.log('--- Verify token ---');
        try {
            var result = await _keycloak.grantManager.userInfo(req.kauth.grant.access_token);
            //var result = await _keycloak.grantManager.validateAccessToken(req.kauth.grant.access_token);
            if(!result) {
                console.log(`result:`,  result); 
                throw Error('Invalid Token');
            }                        
        } catch (error) {
            console.log(`Error: ${error.message}`);
            return next(createError.Unauthorized());
        }
    }
    next();  
}

module.exports = {
    validateTokenKeycloak
};
Hector
  • 691
  • 2
  • 14
  • 28
1

To solve this problem, Keycloak implements JWKS.

Below is an example code snippet in typescript that uses this feature.

import jwt, { JwtPayload, VerifyCallback } from 'jsonwebtoken';
import AccessTokenDecoded from './AccessTokenDecoded';
import jwksClient = require('jwks-rsa');

function getKey(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback): void {
  const jwksUri = process.env.AUTH_SERVICE_JWKS || '';
  const client = jwksClient({ jwksUri, timeout: 30000 });

  client
    .getSigningKey(header.kid)
    .then((key) => callback(null, key.getPublicKey()))
    .catch(callback);
}

export function verify(token: string): Promise<AccessTokenDecoded> {
  return new Promise(
    (
      resolve: (decoded: AccessTokenDecoded) => void,
      reject: (error: Error) => void
    ) => {
      const verifyCallback: VerifyCallback<JwtPayload | string> = (
        error: jwt.VerifyErrors | null,
        decoded: any
      ): void => {
        if (error) {
          return reject(error);
        }
        return resolve(decoded);
      };

      jwt.verify(token, getKey, verifyCallback);
    }
  );
}

import AccessTokenDecoded from './AccessTokenDecoded'; is just an interface of the decoded token.

AUTH_SERVICE_JWKS=http://localhost:8080/realms/main/protocol/openid-connect/certs is an environment variable with the URI of certificates provided by Keycloak.