Reputation: 830
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.
Upvotes: 30
Views: 108161
Reputation: 669
For express.js and keycloak 23.0.6 i use this middleware to validate (and decode, if needed) my JWTs from keycloak
// keycloak JWT which can be obtained from keycloak like this:
// curl -X POST 'http://localhost:8080/realms/<REALM>/protocol/openid-connect/token' -H 'Content-Type: application/x-www-form-urlencoded' -d 'client_id=<CLIENT_ID>' -d 'username=<USER>' -d 'password=<PASS>' -d 'grant_type=password' -d 'scope=email profile'
if (req.headers.authorization) {
const token: string = req.headers.authorization?.split(' ')[1];
if (!req.app.locals.publicKey || req.app.locals.publicKeyExpire < Date.now()) {
try {
const response: AxiosResponse = await axios.get(process.env.KEYCLOAK_CLIENT_ISSUER); // http://localhost:8080/realms/<REALM>
const pubKey: string = `-----BEGIN PUBLIC KEY-----\n${response.data.public_key}\n-----END PUBLIC KEY-----`;
req.app.locals.publicKey = pubKey;
req.app.locals.publicKeyExpire = Date.now() + 5 * 60 * 1000; // 5 min
} catch (error) {
console.error('Failed to fetch public key', error);
return res.status(500).json({error, logout: true});
}
}
try {
jwt.verify(token, req.app.locals.publicKey, {algorithms: ['RS256']});
// const decoded: {} = jwt.decode(token);
next();
} catch (error) {
res.status(401).json(error);
}
} else {
return res.status(401).json({
error: 'Unauthorized',
logout: true,
});
}
Upvotes: 0
Reputation: 11
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.
Upvotes: 1
Reputation: 4736
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
NOTE: Use the variables exp, iat and active to validate the AccessToken
Upvotes: 4
Reputation: 851
@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
};
Upvotes: 2
Reputation: 4242
There are two ways to validate a token:
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.
Upvotes: 25
Reputation: 896
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": "[email protected]"
}
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
Upvotes: 41
Reputation: 1319
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
Upvotes: 8