crystal icon indicating copy to clipboard operation
crystal copied to clipboard

[SOLVED] How to add a custom map of JWT claims to database settings

Open pmdumuid opened this issue 4 years ago • 4 comments
trafficstars

I'm submitting a ...

  • [x] feature request

PostGraphile version: 4.11.0

I am using an external oauth solution (keycloak) to produce JWT, and I am unable to specify a single claim for the role. As such, I would like to provide a function to map the JWT claims to the role to use in the database, and other settings in the database during the transaction.

Currently there are two suggested methods to implement JWTs via an external party: (1) Turning off postgraphile's internal JWT verification and use of express-jwt as per [https://www.graphile.org/postgraphile/jwk-verification/]; and (2) Via using the option, jwtPublicKey.

When using method (1) a property called, user can be accessed within the function, pgSettings that can be used to determine a role and settings based on the JWT claims. Unfortunately, method (1) only works for HTTP requests and not for Websockets. (because Headers cannot be set for websocket requests, and the authorization header needs to be sent within a message). As such method (1) does not work for websocket connections.

When using method (2), the function, pgSettings is executed before the JWT claims are extracted, and thus the claims are not accessible from within a function called pgSettings.

I have developed a patch, (using patch-package) to enable a new option, pgSettingsForJwtClaims that determines settings and a role to apply to the database based on the JWT claims.

postgraphile+4.11.0.patch.gz

pmdumuid avatar Sep 03 '21 08:09 pmdumuid

Actually method (1) should work with websocket requests because we build a "fake" request object for websocket requests, we even populate the authorization "header" automatically for you:

https://github.com/graphile/postgraphile/blob/b4e5e7ef7b37a71be6008f5ce5e7b18e45a23fce/src/postgraphile/http/subscriptions.ts#L217-L226

You will need to supply some middleware to process the header via the websocketMiddlewares option - typically you'd use the same middleware instance as you do for the HTTP server; e.g.:

const app = express();
const websocketMiddlewares = [];

// ...

const jwtMiddleware = jsonwebtoken(...);
app.use(jwtMiddleware);
websocketMiddlewares.push(jwtMiddleware);

// ...

app.use(postgraphile(DB, SCHEMA, {
  subscriptions: true,
  websocketMiddlewares,
  pgSettings(req) {
    return req.user ? {'jwt.claims.user_id': req.user.id} : {};
  }
  // ...
}));

benjie avatar Sep 03 '21 08:09 benjie

Hey Benjie, Thanks!

I didn't see the websocketMiddlewares option (I had read the code you pointed to and noted you were injecting the header object). I'm currently preferring the use of method 2 on the basis that it seems to be using more of postgraphile's internal mechanisms and now have it working.

Can I suggest you update the page, https://www.graphile.org/postgraphile/jwk-verification/ to include the recommendation to set the websocketMiddlewares option. I'm happy to make a PR for the change to that page if you want.

There is a small bug whereby when jwtPublicKey is used, jwtSecret needs to be set to a truthy value to ensure the JWT token is extracted as a result of a line that could be changed as follows within createPostGraphileHttpRequestHandler.js as follows:

-        const jwtToken = jwtSecret ? getJwtToken(req) : null;
+        const jwtVerificationSecret = jwtPublicKey || jwtSecret;
+        const jwtToken = jwtVerificationSecret ? getJwtToken(req) : null;

pmdumuid avatar Sep 04 '21 02:09 pmdumuid

Please go ahead and file both those PRs ❤️🙌

benjie avatar Sep 04 '21 08:09 benjie

Will do, I'll get to it one night this week.

pmdumuid avatar Sep 06 '21 23:09 pmdumuid

[semi-automated message] Hi, there has been no activity in this issue for a while so I'm closing it to keep the issues/pull requests manageable. If this is still an issue, please re-open with additional details.

benjie avatar Sep 16 '23 15:09 benjie