Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
valerii15298 opened this issue Jun 16, 2023 · 12 comments
Open

Comments

@valerii15298
Copy link

valerii15298 commented Jun 16, 2023

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 valerii15298 changed the title Propose jose package as alternative to this library Proposal: use jose package as alternative to keycloak-nodejs-connect Jun 16, 2023
@jonkoops
Copy link
Contributor

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

@m1212e
Copy link

m1212e commented Jun 22, 2023

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

@valerii15298
Copy link
Author

@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?

@m1212e
Copy link

m1212e commented Jun 22, 2023

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

@valerii15298
Copy link
Author

valerii15298 commented Jun 22, 2023

@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));
});

@dlaraf
Copy link

dlaraf commented Jul 16, 2023

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.

@valerii15298
Copy link
Author

valerii15298 commented Jul 16, 2023

@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?

@dlaraf
Copy link

dlaraf commented Jul 16, 2023

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

@late-e
Copy link

late-e commented Jul 28, 2023

@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.

@dav-bisc
Copy link

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.

@late-e
Copy link

late-e commented Aug 14, 2023

@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.

@Dany-C
Copy link

Dany-C commented Sep 13, 2023

Any news about the "official" potential replacement? 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants