convex-backend
convex-backend copied to clipboard
Support at+jwt auth token
It would be great to support JWT with the at+jwt type (OIDC standard).
For example : Logto access tokens are at+jwt tokens.
Can you give an example or provide more information about this OIDC standard token type?
Supporting logto from Convex seems like a great idea.
Have you tried it? Convex supports any provider supporting OIDC https://docs.convex.dev/auth/advanced/custom-auth
What happens when you try it with Logto?
The access token is a JWT token. Its main advantage is that it contains the "scopes" (in addition to the user ID and other information that can be added manually). The "scopes" a string with all the permissions linked to a user based on their role (e.g., "read:logs, write:logs, read:users, write:users").
The ID token is the standard JWT that only contains the user's information. The access token has a shorter expiration time, which requires, for security reasons, more frequent refreshing (usually every hour).
For more information about access tokens: https://blog.logto.io/understanding-tokens-in-oidc and https://auth-wiki.logto.io/access-token.
With Logto, I can create permissions (scopes) and assign them to roles. Then each user can be assigned one or more roles and thus have the corresponding permissions.
Logto allows separating scopes by API resources. Regarding integration with Convex, I consider my Convex database (an excellent product, by the way, thank you very much!!) as an API resource. My goal would be to send the access token directly. This way, in my Convex functions, I can directly verify whether a user has access to a function via the access token without any fetch.
Moreover, with this solution, there is no need to store permissions in Convex via a webhook.
The access token serves both authentication and authorization purposes on the Convex side.
Regarding integration with Logto, I have indeed succeeded with the ID token (the standard JWT with the user's basic information). It integrates very well with Convex (see https://discord.com/channels/1019350475847499849/1184230782370320396). The only thing missing, which would be fantastic, is support for the at+jwt type of JWT.
The reason I chose Logto is that it can be self-hosted (very important, like Convex), is straightforward to use, and has a management API. I didn’t want to use Clerk or Auth0 because they are not self-hostable. As for Convex Auth, I had to set it aside because I use SvelteKit, not React.
I tried using Better Auth, but the current plugins are all in alpha for now, and Logto’s solution is much more stable and simple today.
I strongly recommend adding Logto integration to the documentation.
But above all, support for access tokens with at least acceptance of this type of token.
Hi @nipunn1313, What's your opinion about that ?
Broadly seems reasonable to add support for a new JWT token format - but I am struggling to find any documentation about what this at+jwt token is? Neither of the links you sent referenced it.
It would likely have to go in here https://github.com/get-convex/convex-backend/blob/main/crates/authentication/src/lib.rs#L430 - would you like to try to implement it and test it out?
If you can implement it and link to documentation explaining the at+jwt type, that seems reasonable.
@pierre-H we're starting to support things like this; could you take a look at https://docs.convex.dev/auth/advanced/custom-jwt and see if that fits?
@nipunn1313 : here is the RFC : https://datatracker.ietf.org/doc/html/rfc9068#section-4 . The RFC 9068 is about Access tokens. I am not sure if I could implement it as I don't know anything about Rust, but I will try with some LLM helps ...
@thomasballinger : I tried, and I still have the error: Unsupported: unexpected or unsupported JWT type at+jwt.
@pierre-H you'll still need to use the type "customJwt", but you may be able to make this work by setting the other fields; would love to hear how this goes!
Hey @thomasballinger. Trying to set up the custom jwt provider with better-auth, but the example config from the docs fails to deploy:
Ideas?
Hi @juliusmarminge,
I don't think this is related to the at+jwt token. You should create a new issue for that.
@pierre-H what does an at+jwt JWT look like? I bet with custom jwt support this will be possible.
I chatted with @juliusmarminge separately, more docs coming on custom jwt auth soon.
@thomasballinger it' something like :
{
"email": "[email protected]",
"username": "pierre-H",
"jti": "xxx",
"sub": "xxx",
"iat": 1747205352,
"exp": 1747208952,
"scope": "order:read order:write stock:read",
"client_id": "xxx",
"iss": "https://instance.convex.com/oidc",
"aud": "https://instance.convex.com",
}
The most important part are:
scopewhich contains all current authorization of the useraudwhich is the name of the API
In logto, we configure this in the Api Resources section.
It looks like this would work with customJwt, although not sure that exposes scope yet. I'm documenting this new flow this week, if you get a chance to try this let me know and I can explicitly mention it.
Thank you, @thomasballinger! As soon as the documentation is updated, I will try it.
Docs at https://docs.convex.dev/auth/advanced/custom-jwt have been updated, there are several changes in flight to improve this support. Could you try again when you have a change?
I think this convex/auth.config.ts would look something like this based on your token (guessing at the jwks and algorithm, providing a guess to make sure we're on the same page)
export default {
providers: [
{
type: "customJwt",
issuer: "https://instance.convex.com/oidc",
jwks: "https://instance.convex.com/oidc/.well-known/jwks.json",
algorithm: "ES256",
},
],
};
@thomasballinger with this config, I have this error:
[
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"appAuth",
0,
"domain"
],
"message": "Required"
}
]
@thomasballinger any idea about this error ?
@thomasballinger : I tried with the latest docker image 478d197d54ee6e873f06cf9e9deae1eb4aa35bb5 and I still have the error ...
Do you know why ?
I noticed that when we comment out specifically the "jwks" key from the object, we get ✔ Convex functions ready!.
It looks like you might be using components @pierre-H? This looks like a bug with the npm package fixed in https://github.com/get-convex/convex-js/commit/bbfe95e005fc72990d59f2f7b9d8fe737caf6eb1. Support for customJWT with components should be in [email protected], published just now — let me know if you have trouble with this one.
Hi @thomasballinger, Thank you ! Indeed, that was the problem.
I tried with the last version, and now I have a new problem. The jwt is not decoded.
In the backend log, I have this error : No auth provider found matching the given token.
I checked on the convex-backend code, and it seems that the issue comes from the audience (aud) field in the jwt.
In the Logto JWT, the aud has the API Identifier of the API Resource ( doc - rfc ). This API Identifier should be an URL that I can set myself as I can create as many as API Resource I want.
And it seems that Convex use hard coded url for that : https://github.com/get-convex/convex-backend/blob/a805f0b8d9692faf767bfddcc20fb1ab52d45e3a/crates/authentication/src/lib.rs#L60
I tried to create a new API Resource with "https://console.convex.dev/api/" and I still have this error ...
@pierre-H, the aud field needs to match the applicationID property of your convex/auth.config.ts file, if that property is there. If that property isn't present it doesn't need to match. "https://console.convex.dev/api/" is not related.
@thomasballinger : it worked 🥳 !!! Thank you so much !
So, for anyone trying to use Logto access token with Convex (which I love so much !!), here are the requirements.
For Logto :
- you have to use the RSA algorithm for your private keys (for self-hosted version, don't forget to restart your docker service)
- create an API resource with for example https://my-api.com as the API Identifier
- create permissions (scopes) and user roles with these permissions
- you can customize your access token by adding, for example, the user email or the username like this :
const getCustomJwtClaims = async ({ token, context, environmentVariables, api }) => {
return {
email: context.user.primaryEmail,
username: context.user.username
};
}
For convex, here is the config needed in your auth.config.ts :
export default {
providers: [
{
type: 'customJwt',
applicationID: 'https://my-api.com', // This is the API Identifier of your Logto API Resource
issuer: process.env.LOGTO_ENDPOINT + '/oidc', // Check the URL in you Logto application
jwks: process.env.LOGTO_ENDPOINT + '/oidc/jwks', // Check the URL in your Logto application
algorithm: 'RS256'
}
]
};
And now, in your Convex functions, if you have :
const user = await ctx.auth.getUserIdentity();
console.log({ user });
You will get :
{
"user": {
"tokenIdentifier": "https://xxx",
"issuer": "https://xxx/oidc",
"subject": "xxx",
"client_id": "xxx",
"email": "[email protected]",
"scope": "order:create order:delete product:read",
"username": "my_username"
}
}
So you can check the user permission by checking its presence in the scope property !
Thank you again so much @thomasballinger ! Convex is so amazing ! Having SvelteKit + Logto + Convex is so great !!! Thank you !!!
Great to hear, thanks for your patience while we ironed this out.
@thomasballinger : I am back 😃! I'm sorry to bother you again...
I managed to successfully use the Logto access token for a basic case (user roles, permissions). However, I noticed a limitation in a slightly more complex scenario.
Logto allows user distribution by Organization as well. This is very useful for SaaS to have a multi-tenant architecture.
A user can belong to multiple organizations and can have different roles for each organization.
Thus, I can have different access tokens per organization, with each token containing the user's permissions based on the organization.
Except in this case, the tokens generated by Logto contain aud with: urn:logto:organization:the_org_id where the_org_id is the organization ID.
Would it be possible to configure dynamic aud values?
I might be able to configure multiple providers (one provider per organization), but that wouldn't be dynamic.
I don't think I can run an async function in auth.config.ts to fetch existing organization IDs via API, right?
Hi @thomasballinger Do you have an idea about that ⤴ ? 😃 Thank you!
You can use multiple aud values by removing applicationID from your convex/auth.config.ts, but that places no restrictions on what the audience can be. Do you need to specify a pattern? What's an example value that you see in the audience? Is the issue that the .audience is not exposed on the user identity?
Thank you @thomasballinger : removing applicationId works.
Here is an example of the audience : urn:logto:organization:xxxxxx where xxxxxx is the ID of the user organization.
Does removing applicationId reduce the API's security?
Would adding a regex check be possible and sufficient?
Yeah removing applicationID is not safe for many providers, it's only safe if your issuer is specific to your app. Adding a pattern for accepting the aud sounds useful, we're veering from the spec here so we need to think through the implications. Another option is allowing the authentication but exposing aud so you can check it in code, but that's less convenient.
Thank you @thomasballinger !
Since I self-host Logto, I don't have this problem.
But it's indeed an issue for those using Logto Cloud.
Thanks so much for your help! Having access to the audience would be useful, but since I would have to fetch the Logto API to check whether the org exists or not, I will be able to check only with custom action.
@thomasballinger @pierre-H Why is it more secure when self hosting Logto? Trying to understand consequences of doing the same as a replacement for Clerk in our SaaS products.