Skip to main content

Custom JWT Provider

Note: This is an advanced feature! We recommend sticking with the supported third-party authentication providers.

A JWT is a string combining three base64-encoded JSON objects containing claims about who a user is valid for a limited period of time like an hour. You can create them with a library like jose after receiving some evidence (typically a cookie) of a user's identity or get them from a third party authentication service like Clerk. The information in a JWT is signed (the Convex deployment can tell the information is really from the issuer and hasn't been modified) but generally not encrypted (you can read it by base64-decoding the token or pasting it into jwt.io.

If the JWTs issued to your users by an authentication service contain the right fields to implement the OpenID Connect (OIDC) protocol, the easiest way to configure accepting these JWTs is adding an OIDC Provider entry in convex/auth.config.ts. If the authentication service or library you're using to issue JWTs doesn't support these fields (for example OpenAuth JWTs missing an aud field because they implement the OAuth 2.0 spec but not OIDC) you'll need to configure a Custom JWT provider in the convex/auth.config.ts file.

Custom JWTs are required only to have header fields kid, alg and typ, and payload fields sub, iss, and exp. An iat field is also expected by Convex clients to implement token refreshing.

Server-side integration

Use type: "customJwt" to configure a Custom JWT auth provider:

convex/auth.config.js
export default {
providers: [
{
type: "customJwt",
applicationID: "your-application-id",
issuer: "https://your.issuer.url.com",
jwks: "https://your.issuer.url.com/.well-known/jwks.json",
algorithm: "RS256",
},
],
};
  • applicationID: Convex will verify that JWTs have this value in the aud claim. See below for important information regarding leaving this field out. The applicationID field is not required, but necessary to use with many authentication providers for security. Read more below before omitting it.
  • issuer: The issuer URL of the JWT.
  • jwks: The URL for fetching the JWKS (JSON Web Key Set) from the auth provider. If you'd like to avoid hitting an external service you may use a data URI, e.g. "data:text/plain;charset=utf-8;base64,ey..."
  • algorithm: The algorithm used to sign the JWT. Only RS256 and ES256 are currently supported. See RFC 7518 for more details.

The issuer property must exactly match the iss field of the JWT used and if specified the applicationID property must exactly match the aud field. If your JWT doesn't match, use a tool like jwt.io to view an JWT and confirm these fields match exactly.

Warning: omitting applicationID is often insecure

Leaving out applicationID from an auth configuration means the aud (audience) field of your users' JWTs will not be verified. In many cases this is insecure because a JWT intended for another service can be used to impersonate them in your service.

Say a user has accounts with https://todos.com and https://banking.com, two services which use the same third-party authentication service, accounts.google.com. A JWT accepted by todos.com could be reused to authenticate with banking.com by either todos.com or an attacker that obtained access to that JWT.

The aud (audience) field of the JWT prevents this: if the JWT was generated for a specific audience of https://todos.com then banking.com can enforce the aud field and know not to accept it.

If the JWTs issued to your users have an iss (issuer) URL like https://accounts.google.com that is not specific to your application, it is not secure to trust these tokens without an ApplicationID because that JWT could have been collected by a malicious application.

If the JWTs issued to your users have a more specific iss field like https://api.3rd-party-auth.com/client_0123... then it may be secure to use no aud field if you control all the services the issuer url grants then access to and intend for access to any one of these services to grants access to all of them.

Custom claims

In addition to top-level fields like subject, issuer, and tokenIdentifier, subfields of the nested fields of the JWT will be accessible in the auth data returned from const authInfo = await ctx.auth.getUserIdentity() like authInfo["properties.id"] and authInfo["properties.favoriteColor"] for a JWT structured like this:

{
"properties": {
"id": "123",
"favoriteColor": "red"
},
"iss": "http://localhost:3000",
"sub": "user:8fa2be73c2229e85",
"exp": 1750968478
}

Client-side integration

Your users' browsers need a way to obtain an initial JWT and to request updated JWTs, ideally before the previous one expires.

See the instructions for Custom OIDC Providers for how to do this.