crystal
crystal copied to clipboard
[SOLVED] How to add a custom map of JWT claims to database settings
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.
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} : {};
}
// ...
}));
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;
Please go ahead and file both those PRs ❤️🙌
Will do, I'll get to it one night this week.
[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.