āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/clerk/clerk-docs/guides/sessions/jwt-templates ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
[!WARNING] This guide is for creating custom JWT templates in order to generate JSON Web Tokens with Clerk. If you are looking for how to customize your Clerk-generated session token, refer to the Customize your session token guide.
Clerk offers the ability to generate JSON Web Tokens (JWTs). Each JWT, or token, represents a user that is currently signed in to your application.
You can control the claims that will go into these tokens by creating custom JWT templates that fit your needs. This enables you to integrate with any third-party services that support authentication with JWTs. An example use case is integrating with a third-party service that is able to consume JWTs, but requires them to be in a particular format.
[!WARNING] When using custom JWTs, there may be increased latency in token generation due to the additional processing required to fetch and include the custom claim data.
JWT templates are essentially JSON objects that specify claims to be included in the generated tokens, along with their respective values.
Claim values can be either static or dynamic.
Static values can be any of the regular JSON data types (strings, numbers, booleans, arrays, objects, null) and will be included as-is in the tokens.
Dynamic values, also called shortcodes, are special strings that will be substituted for their actual values when the tokens are generated. Read more in the Shortcodes section.
The following example shows a template that demonstrates both static values and shortcodes. In this example, the values of the aud and interests claims are static, and the values of the name and email claims are dynamic.
{
"aud": "https://example.com",
"interests": ["hiking", "knitting"],
"name": "{{user.first_name}}",
"surname": "{{user.last_name}}",
"email": "{{user.primary_email_address}}"
}
A token generated using the template above would look something like this:
{
"aud": "https://example.com",
"interests": ["hiking", "knitting"],
"name": "John",
"surname": null,
"email": "john@doe.org"
// ...plus some automatically-included claims
// See the following section section for more information
}
In every generated token, there are certain claims that are automatically included and cannot be overridden by templates. Clerk calls these "default claims" and you can learn more about them in the Session tokens reference documentation.
For custom JWTs, Clerk automatically includes the following default claims:
{
// default claims, included automatically
"azp": "http://localhost:3000",
"exp": 1639398300,
"iat": 1639398272,
"iss": "https://clean-mayfly-62.clerk.accounts.dev",
"jti": "10db7f531a90cb2faea4",
"nbf": 1639398220,
"sub": "user_1deJLArSTiWiF1YdsEWysnhJLLY"
}
However, other default claims - such as sid (session ID) - are only included in session-bound tokens, not in custom JWTs. Custom JWTs are not inherently tied to a session. Instead, they're designed to be flexible tokens that include the default claims listed above, along with any additional claims you explicitly set in your JWT template. For this reason, session-tied claims like sid, v, pla, or fea cannot be included in custom JWTs.
If you need to generate a token that includes both session-bound data (like sid) and any additional claims, the recommended approach is to use a custom session token instead of a JWT template. See the guide on customizing your Clerk session token.
To include dynamic values in your tokens, you can use shortcodes. Shortcodes are strings that Clerk will replace with the actual values of the corresponding user information when the token is generated.
[!NOTE] Even though shortcodes are string values, their type in the generated token depends on the original type of the information that's included. For example,
{{user.public_metadata}}will be substituted for a JSON object, not a string.
While you can use the {{user.public_metadata}} or {{user.unsafe_metadata}} shortcodes to include the complete metadata object in the final token, there might be cases where you only need a specific piece of information.
To keep your tokens lean, you can use the dot notation to access nested fields of the metadata object.
Let's assume the user's public metadata are the following:
{
"interests": ["hiking", "knitting"],
"addresses": {
"Home": "2355 Pointe Lane, 56301 Minnesota",
"Work": "3759 Newton Street, 33487 Florida"
}
}
To access the interests array, you would use the shortcode {{user.public_metadata.interests}}. To access the Home address, you would use {{user.public_metadata.addresses.Home}}. See the following example:
// The template
{
"likes_to_do": "{{user.public_metadata.interests}}",
"shipping_address": "{{user.public_metadata.addresses.Home}}"
}
// The generated token
{
"likes_to_do": ["hiking", "knitting"],
"shipping_address": "2355 Pointe Lane, 56301 Minnesota"
}
Shortcodes can be interpolated inside strings.
For example, you could use interpolation to build the user's full name:
{
"full_name": "{{user.last_name}} {{user.first_name}}"
}
Interpolated shortcodes will always result to string values. For example, if the user does not have a last name associated, the above full name value would be null John.
Conditional expressions use the || operator and can be used to substitute a default fallback value for shortcodes that would otherwise result in null or false values.
The format of a conditional expression is the following:
{
"key": "{{ <operand_1> || <operand_2> || <operand_n> }}"
}
The result of a conditional expression is that of the first operand that does not evaluate to null or false (also known as "falsy"). If all operands of the expression are falsy, the last operand is returned no matter its value. Therefore, you should always place the default value as the last operand. See the following example:
{
"has_verified_contact_info": "{{user.email_verified || user.phone_number_verified}}",
// fallback to a string value
"full_name": "{{user.full_name || 'Awesome User'}}",
// fallback to a number value
"age": "{{user.public_metadata.age || user.unsafe_metadata.age || 30 }}"
}
For this example, in the case that user:
Then, the output of the generated token would be:
{
"has_verified_contact_info": true,
"full_name": "Awesome User",
"age": 30
}
The rules that govern conditional expressions are as follows:
').A template consists of the following four properties:
exp claim (i.e. exp=current_time+lifetime). Default is 60 seconds.nbf claim (i.e. nbf=current_time-allowed_clock_skew). Default is 5 seconds.To create a JWT template:
To generate a token using a template, you can use the getToken() method. See the reference documentation for more information and example usage.
The following example demonstrates the full capabilities of JWT templates, including static claim values, dynamic claim values via shortcodes, and Clerk's "default claims".
Given the following user:
MariaDoehttps://example.com/avatar.jpguser_abcdef123456789maria@example.com{ "profile" : {"interests": ["reading", "climbing"] } }{ "foo" : { "bar": 42 } }And given the following JWT template:
{
// static values
"aud": "https://my-site.com",
"version": 1,
"foo": { "bar": [1, 2, 3] },
// dynamic values
"user_id": "{{user.id}}",
"avatar": "{{user.image_url}}",
"full_name": "{{user.last_name}} {{user.first_name}} ",
"email": "{{user.primary_email_address}}",
"phone": "{{user.primary_phone_address}}",
"registration_date": "{{user.created_at}}",
"likes_to_do": "{{user.public_metadata.profile.interests}}",
"unsafe_meta": "{{user.unsafe_metadata}}",
"invalid_shortcode": "{{user.i_dont_exist}}"
}
The generated token would look like this:
{
"aud": "https://my-site.com",
"version": 1,
"foo": { "bar": [1, 2, 3] },
"user_id": "user_abcdef123456789",
"avatar": "https://example.com/avatar.jpg",
"full_name": "Doe Maria",
"email": "maria@example.com",
"phone": null,
"registration_date": 1227618844,
"likes_to_do": ["reading", "climbing"],
"unsafe_meta": {
"foo": {
"bar": 42
}
},
"invalid_shortcode": null,
// default claims, included automatically
"azp": "http://localhost:3000",
"exp": 1639398300,
"iat": 1639398272,
"iss": "https://clean-mayfly-62.clerk.accounts.dev",
"jti": "10db7f531a90cb2faea4",
"nbf": 1639398220,
"sub": "user_1deJLArSTiWiF1YdsEWysnhJLLY"
}
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā