keycloak-nodejs-connect icon indicating copy to clipboard operation
keycloak-nodejs-connect copied to clipboard

Proposal: use `jose` package as alternative to `keycloak-nodejs-connect`

Open valerii15298 opened this issue 2 years ago • 20 comments

Description

Since this library is deprecated I would like to propose one of the possible alternatives => jose It contains quite useful functions: createRemoteJWKSet and jwtVerify as described here: https://github.com/panva/jose/blob/main/docs/functions/jwks_remote.createRemoteJWKSet.md#function-createremotejwkset

Example verification with jose looks like this:

import { JWTPayload, createRemoteJWKSet, jwtVerify } from "jose";

const auth_server_url = "http://keycloak.localhost:8080";
const jwks = createRemoteJWKSet(new URL(`${auth_server_url}/realms/${realmName}/protocol/openid-connect/certs`));
const { payload } = await jwtVerify(token, jwks); // this line will throw on invalid token
console.log(payload.sub);

jose automatically fetches public keys from endpoint if previous ones are not valid.

The JSON Web Key Set is fetched when no key matches the selection process

Jose library seems to cover significant part of keycloak-nodejs-connect functionality. I already using it in my app and DX is even more enjoyable. I have more control and can create express middleware myself, or integrate it in any kind of app bcs it is not tightly coupled to expressjs.

@jonkoops @abstractj I would really appreciate your review about using jose, seems like it can be recommended as keycloak-nodejs-connect alternative or at least one of alternatives. In any case would be very helpful to hear your thoughts on it, whether you think it is good replacement or not. Tagging both of you since you guys seem to be active ones in this repo from maintainers.

Btw, also, as I checked another library named jose is used under the hood in keycloak Java source code too.

valerii15298 avatar Jun 16 '23 14:06 valerii15298

Thanks for this suggestion, I will take a look and see if this could be a library to recommend.

jonkoops avatar Jun 19 '23 09:06 jonkoops

@valerii15298 can you post a link to the code of the app you tested it in?

m1212e avatar Jun 22 '23 19:06 m1212e

@m1212e sure, I used it in Nest.js app but I can easily give you StackBlitz example with express(or Nest.js), what do you prefer?

valerii15298 avatar Jun 22 '23 19:06 valerii15298

Express sounds good, but I thought it may be a public repo you were referring to. Whatever is less work for you <3

m1212e avatar Jun 22 '23 19:06 m1212e

@m1212e I thought that simplest example would be easier to give here, since you cannot start keycloak in StackBlitz without docker-compose anyway...

So the simplest authorization would be like this:

import { createRemoteJWKSet, jwtVerify } from "jose";
import express, { Request } from "express";

const app = express();

const auth_server_url = "http://keycloak.localhost:8080";
const realmName = "master";
const jwks = createRemoteJWKSet(
  new URL(
    `${auth_server_url}/realms/${realmName}/protocol/openid-connect/certs`,
  ),
);
function extractTokenFromHeader(req: Request) {
  const [type, token] = req.headers.authorization?.split(" ") ?? [];
  return type === "Bearer" ? token : undefined;
}
app.use((req, res, next) => {
  const token = extractTokenFromHeader(req);
  if (!token) {
    return res.status(401);
  }
  jwtVerify(token, jwks)
    .then(({ payload }) => {
      // @ts-ignore
      req.authPayload = payload;
      console.log({
        userId: payload.sub,
        useEmail: payload.email,
        userRealmRoles: payload.realm_access.roles,
        // and so on... simply check what is available in payload
      });
      next();
    })
    .catch(() => res.status(401));
});

valerii15298 avatar Jun 22 '23 19:06 valerii15298

May be is a dummy question but how is this implemented currently? Is the token checked in the Keycloak service?. I mean after a logout, for example. In this case I don't see any server side check so anyone with that "old"/"not valid" token could use the protected endpoint. Is it? I guess the bearer token from the frontend app should be checked against the server to ensure is not expired/revoked.

dlaraf avatar Jul 16 '23 22:07 dlaraf

@dlaraf There are to ways to verify keycloak token: Online and Offline, both having their own tradeoffs and benefits.

Online validation is quite expensive, you will need to make requests to your keycloak server every time which could slow down your app, but it will always give you the most precise and valid result.

Offline validation on the other hand is less expensive but if the user logged out, the token will be valid in the remaining time(usually 5 minutes depending which configuration you chose for token to be valid for how long).

When using jose the verification will be valid for most cases except cases like user logout for which jose will verify token without error(while token will be revoked already) until the end of its lifespan.

In the case of token expiration, jwtVerify will throw an error always stating that token is expired which is correct behaviour, since expiration time is encoded in the token itself(so no need to make requests to the server).

And are you asking about implementation with jose or about current implementation of keycloak-nodejs-connect?

valerii15298 avatar Jul 16 '23 22:07 valerii15298

Hi @valerii15298! Thank you for the clarification. Very helpful. I was asking about the current verification in keycloak-nodejs-connect. I guess offline method is the one used with the bearerOnly:true and the other(online) is using the client_secret? If you wants to use both(frontend login with bearer token and check that token online on the API side(backend), you need two clients? one for bearer flow (frontend) and another one using secret code (backend)?

I'm trying to do a secure app with Keycloak so I was following this guide reactjs express rest api Keycloak . Another good example I've seen is this one that I guess uses the offline method as well with Passport Keycloak Nestjs React example

Thank you @valerii15298

dlaraf avatar Jul 16 '23 22:07 dlaraf

@dlaraf You don't need two clients if you just want to verify the token that backend received from frontend. You can do a request on backend to keycloak realm endpoint where you get the public key (jwks endpoint), and with that you can verify the validity of the token.

late-e avatar Jul 28 '23 17:07 late-e

Sorry if my question seems stupid, I'm new to this. Assuming that I'm using jose, should I check the role permissions using some custom code or is there a standardized way to do this? I was thinking of just checking whether the payload.realm_access.roles contains the necessary role to access a certain resource, but I'm unsure whether this is the correct way to go about it.

dav-bisc avatar Aug 14 '23 16:08 dav-bisc

@dav-bisc

Hi,

Yes you can just check if the payload.realm_access.roles contains the necessary role. You can check the code from this library too, it's very simple.

late-e avatar Aug 14 '23 19:08 late-e

Any news about the "official" potential replacement? 🤔

Dany-C avatar Sep 13 '23 22:09 Dany-C

Alternatives recommended or hinted at by the Keycloak team are mentioned in Deprecation of Keycloak adapters.

BasilaryGroup avatar Sep 10 '24 18:09 BasilaryGroup

Alternatives recommended or hinted at by the Keycloak team are mentioned in Deprecation of Keycloak adapters.

From the link you sent btw:

NodeJS We are still looking around for the best candidate for Node.js applications, but it looks like openid-client is a good alternative, that is a lot more feature rich than the Keycloak adapter.

Zysberg avatar Nov 26 '24 02:11 Zysberg

We're currently working on orphaning the Node.js connect adapter from the main Keycloak release cycle, after which I will be starting the evaluation of alternative clients. Once one is selected that matches our expectations we will be writing guides, and quite likely quickstarts on how to use said library with Keycloak.

jonkoops avatar Nov 26 '24 10:11 jonkoops

FYI the expectation here is that we will be dropping support for this library completely starting Keycloak 27. Since we are now supporting major versions of Keycloak for longer periods of time that is still a while away.

jonkoops avatar Nov 26 '24 10:11 jonkoops

FYI the expectation here is that we will be dropping support for this library completely starting Keycloak 27. Since we are now supporting major versions of Keycloak for longer periods of time that is still a while away.

@jonkoops I would like to know why we are dropping support for node js. some context or background for not supporting node js will be appreciated.

Thanks.

vvavdiya avatar Nov 29 '24 13:11 vvavdiya

Because Keycloak is based on standard protocols that have widely available implementations in various languages and frameworks, thus it makes no sense for the Keycloak team to maintain a library that is exclusively built for Keycloak. It's not that we don't want to support Node.js, it is simply that we don't want to maintain software that does not need to exist any longer.

jonkoops avatar Nov 29 '24 14:11 jonkoops

What about Authorization feature set of Keycloak, should then keycloak-js be used?

tskorupka avatar Jan 21 '25 14:01 tskorupka

Also this one should work for keycloak(similar as i proposed): https://github.com/FusionAuth/fusionauth-quickstart-javascript-express-api/blob/main/complete-application/services/verifyJWT.js

It is quite staightforward use jose package to verify tokens and add role checking themselves...

valerii15298 avatar Apr 01 '25 12:04 valerii15298