-
Notifications
You must be signed in to change notification settings - Fork 113
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
Custom JWT Claims #49
Comments
Yes, that was my biggest pain point as well. I had two custom fields, which contained some calculated IDs. Now I have to navigate with 2-3 joins down in my permission queries to get to |
I agree with all this, to be able to set custom claims is important. Here are the current ideas we can continue to discuss to find a good solution: 1 Custom GraphQL query@beepsoft's idea to provide a custom GraphQL query for Hasura Auth to execute when generating the JWT token. Can it be an issue that the query can return multiple rows? 2. Special
|
Which solution is the best is a question of tradeoffs, as always 😀
I haven't really thought about it, but practically the graphql should result in a single scalar value as this is what we can use in the permission queries later. However, there maybe cases where the claim is not to be used only in Hasura permissions queries in which case it would be nice to allow users to store whatever is returned by the graphql queries. |
Is there any timeline for when custom JWT will be available in nhost v2? |
@artistic-differences No timeline as of now. We first need to decide on the optimal solution. THanks for pointing out how AWS Cognito handles this. 👍 Another alternative that Firebase uses: Reference: https://firebase.google.com/docs/auth/admin/custom-claims From my understanding, you can set claims to a user and those claims stay the way they are until they are changed. No extra data fetching request every time a JWT token is created. To do this, we could add a new column to the Then when Hasura Auth creates a new JWT token it will fetch custom claims from the user's The downside of this is that the |
Wow quick feedback indeed. For a different project we use AWS Cognito and Hasura add add these claims as highlighted. Just evaluating nhost against that. One of the main claims is orgainsation_id which we use for multi-tenant permissioning within the app. I.E. Nearly all tables have organisation_id and all tables are secured again JWT.x-hasura-organisation-id to ensure a tenant only has access to their data. |
Yes there is a wide agreement on a solution for this and we do want this in place as soon as possible. This is a prioritized issue for us. Unfortunately, I don't expect us to do some quick fix like the webhook auth alternative you mentioned. We just have too much other stuff going on right now. To keep everyone in this thread on the same page here's the different alternatives we've discussed:
Given that claims don't change very often I kind of leaning towards the |
Just to be clear if you go down the customClaims column route. When this column is converted into a JWT the JSONB needs to parsed and split into key, value pairs. I.E needs to become separate key value pairs that are then available for use in Hasura for linking through to permissions and not left as
Hope that makes sense |
Sorry just now catching up on this ... but could we just use an existing HASURA hook to a custom webhook.. as I put in this issue here.. (nhost/hasura-backend-plus#643) And if the HASURA_GRAPHQL_AUTH_HOOK environment variable is already in use.. then adding an NHOST_GRAPHQL_CUSTOM_AUTH_HOOK environment variable and if set it would call that web hook as well Yes we would have to write the function to take care of sending back the custom claims.. but you would be unrestricted basically in what graphql query and/or custom logic that could be applied. It would also allow and easier path forward for users to address more complex scenarios without the need of the NHost team to have to configure additional logic or complex parsing solutions Further info https://hasura.io/docs/latest/graphql/core/auth/authentication/webhook.html |
Isn't the Hasura auth webhook gets called for every graphql query? This doesn't seem to scale well. |
It might be in some scenarios cause issues. If sticking with JWT .... if you add an NHOST_CUSTOM_AUTH_ACTION we could possibly look at updating this hasura-auth/src/utils/tokens.ts Line 47 in 104da12
to include a call out to a Hasura action passing in the userId ... would remove the need to know within auth the location of the users custom api setup .. It would just need to return the array of key:value pairs for the custom claims. I may try to see if I can pull down the code and maybe do a simple example.... and possibly put up a PR ... |
Prototyping:
The
In this example, an additional column
{
'group-id': 'profile.group.id',
'first-name': 'profile.first_name'
} The custom claim templates follow a basic but extensible Jsonata syntax. query ($userId: uuid!) {
user (id: $userId) {
profile {
group {
id
}
first_name
}
}
}
{
profile: {
group: { id: '07b6e25e-20ac-4036-b6ae-cba1ad02e845' },
first_name: 'Pilou'
}
}
{
'x-hasura-group-id': '07b6e25e-20ac-4036-b6ae-cba1ad02e845',
'x-hasura-first-name': 'Pilou'
}
Note: maybe |
This commit illustrates the concept |
I'll just add my thoughts after some thinking. I generally like the
|
So, the When would it be set? Should we have an event trigger on |
Yes, that was the idea.
Good question. I think we can't allow claims to be arbitrarily set during sign-up (security issues). So all custom claims should be set post sign up. Either via event triggers, manually, or something else. BUTAfter discussing with @plmercereau the approach of specifying something like: {
"email": "email",
"group-id": "profile.group.id",
"organization-id": "organization.id"
} and having Hasura Auth get the values via GraphQL relationships (starting from the user in the We (mostly @plmercereau 😅 ) will do more investigation :) |
+1 for the webhook approach (input: user's id (or the entire user row with email and id), output: custom claims json). Apart from being the most flexible, it's pretty common (e.g. cognito and auth0 both use something similar). |
OK, so eventually you will do a GraphQL (Hasura) query to collect the claim data, right? And this is pretty much what was my original proposal as well. 😃 Except that in your case it will be much more controlled and will look for just specific fields, but you will have the same issues as running a user defined GraphQL query (run a query, handle errors, timeouts, etc.). And if that's the case, then we could actually have both solutions for the same price: the controlled approach as above for those, who don't want to bother with writing GraphQL or a free form GraphQL query for advanced use cases. And again: a free form GraphQL query in this case can also be considered much like a webhook as well considering that you can now defined an action transform, which can call any REST endpoints. |
@kratam Good input! I think this is the reference from Auth0: https://auth0.com/docs/get-started/authentication-and-authorization-flow/customize-tokens-using-hooks-with-client-credentials-flow @beepsoft Not saying we'll do one or the other :>. Just that it was appealing :D |
@elitan I will be happy with any solution at the end, don't get me wrong. 😃 I just really think that the GraphQL approach is the most versatile and has no worse performance or implementation impact than any other solution. For the syntax I may update my proposal this way, so that it would not include the full query and these claim queries could be merged into a single GraphQL query easier. auth:
custom_claims:
x-hasura-organization-id: >
profiles(where:{userId:{_eq:$userId}}) {
organizationId
}
x-hasura-service-account-id: >
profiles(where:{userId:{_eq:$userId}}) {
serviceAccountId
} The Hasura query generated (need to convert kebab-case to snake_case for graphql aliases): query customClaims($userId: uuid!) {
x_hasura_organization_id: profiles(where:{userId:{_eq:$userId}}) {
organizationId
}
x_hasura_service_account_id: profiles(where:{userId:{_eq:$userId}}) {
serviceAccountId
}
} And the result would be: {
"data": {
"x_hasura_organization_id": [
{
"organizationId": "123"
}
],
"x_hasura_service_account_id": [
{
"serviceAccountId": "456"
}
]
}
} And here we would just need to parse to leaf values of the JSON ("123", "456") and convert back the claim names from snake_case to kebab-case, and done. Of course there would be need for some sanity check regarding the graphql expressions (eg. syntax). |
I'll just highlight this point from @beepsoft
I just want to highlight that this is a top priority issue for us. Given that there are a few different possible solutions we want to take our time to evaluate what approach would suit best. We'll update you with learnings and insights during our process and hope for the same kind of valuable feedback :) |
Introduces a `AUTH_JWT_CUSTOM_CLAIMS`. Attaches custom `x-hasura-` JWT claims in querying data related to the current user e.g. user.organisation.id. `AUTH_USER_SESSION_VARIABLE_FIELDS` is an equivalent and is deprecated. closes #49
Introduces a `AUTH_JWT_CUSTOM_CLAIMS`. Attaches custom `x-hasura-` JWT claims in querying data related to the current user e.g. user.organisation.id. `AUTH_USER_SESSION_VARIABLE_FIELDS` is an equivalent and is deprecated. closes #49
@beepsoft the PR simplifies your example to: {
"x-hasura-organization-id": "profile.organisationId",
"x-hasura-service-account-id": "profile.serviceAccountId"
} GraphQL query is automatically generated and result is transformed with JSONata. In this case, no need to set a webhook nor to code a graphql query, or to transform the data fetched from this query. What you would only need is to create the profile table and define the right I agree a webhook would offer more flexibility. Yes, webhooks are used by other competitors. But we also have to keep in mind that
This offers us an opportunity to make things way simpler that in other architectures, and we don't want to create an additional dependency to an external service (the webhook) unless it is strictly necessary. The webhook approach could be a "plan B" approach when our implementation is not enough, hence I'm reopening the issue and rename it so we can continue our discussion. |
Thanks @plmercereau, I actually like your approach! And you are right, it is semantically the same as was my suggestion but in a more controlled way. My gut feeling is that we will need more flexibility in the future in case the claim calculation cannot be statically declared and need to involve an external system. Anyways, I'm very happy to have custom claims back! 😃 |
I just want to highlight the current approach:
|
nhost and HBP used to support custom JWT claim, however hasura-auth doesn't support them. As @elitan wrote: "For JWT custom fields, we've temporary dropped support for it in v2 because we don't want users to edit the new auth.users table. Everything in the auth schema is managed by Hasura Auth."
I would like to suggest an alternative solution, which would not require us developers to touch
auth.users
and would not require hasura-auth to have any knowledge of the developers schema and tables.I would suggest to have a nhost configuration option, which allows us to define a hasura query for custom claim values. A separate query for each claim. The query should have a single
$userId: uuid!
parameter and the query result, whatever it is - a single value or a complete json - would be set in the custom claim field.For example:
This would result in a
x-hasura-organization-id: 123
and ax-hasura-service-account-id: 456
custom claim in the JWT.This solution would not require accessing/modifying
auth.user
by the developers andhasura-auth
should not take care of any specific tables in thepublic
schema: it is the duty of the developer to query and return an appropriate value from its own schema based on a$userId
.The text was updated successfully, but these errors were encountered: