Clerk logo

Clerk Docs

Ctrl + K
Go to clerkstage.dev
Check out a preview of our new docs.

Manual JWT Verification

A guide to manually validate your session tokens

Introduction

When a user is authenticated in your application, a short-lived session token is generated by Clerk.js that depicts the fact and it's sent to your backend. Your backend will typically want to validate that the session token is valid (i.e. that it comes from Clerk, that it hasn't expired etc.)

If you're using the middlewares provided by our Clerk SDKs, this is all handled automatically in every request. If you're not using the middlewares, you can still use the respective helpers provided by the SDKs to validate the tokens. That is all good, but if an SDK does not exist for the application's programming language, you will have to manually validate these tokens.

This guide provides instructions on how to do exactly that; manually validate the Clerk session tokens that your backend receives.

Overview

Your Clerk-generated session tokens are essentially JWTs which are signed using your instance's private key and can be verified using your instance's public key. Depending on your architecture, these tokens will be in your backend requests either via a cookie named __session or via the Authorization header.

For every request, you must validate its token to make sure it hasn't expired and it is authentic (i.e. no malicious user tried to tamper it). If these validations pass, then it means that the user is authenticated to your application and you should consider them signed in.

Instructions

By following the steps below, you can validate the session token on your own and make sure that the user session is still active and valid.

  1. Retrieve the session token from either __session cookie or from the Authorization header
  2. Get your instance's Public Key. There are 3 ways to do it:
    1. Via the Backend API in JSON Web Key Set (JWKS) format at the following endpoint https://api.clerk.dev/v1/jwks(If there is more than one JWKS, decode your session token, get the token kid from the header part and construct the respective public key)
    2. Via the Frontend API in JSON Web Key Set (JWKS) format at the following endpoint https://<YOUR_FRONTEND_API>/.well-known/jwks.json(If there is more than one JWKS, decode your session token, get the token kid from the header part and construct the respective public key)
    3. If you are planning to use Clerk on a Serverless/Edge Runtime where JWKs caching is challenging, you can use the instance Public Key as an environment variable. The key can be found in Dashboard > API Keys > JWT Verification Key. Note that the JWT Verification key is not in PEM format, the header and footer are missing, in order to be shorter and single-line for easier setup.
  3. Use the above Public Key to verify the token's signature
  4. Validate that the token is not expired, by checking the exp and nbf claims
  5. If the azp claim exists, validate that equals any of your known Origins that are permitted to generate those tokens. This is an extra security check that we highly recommend that you do

Steps (3) and (4) should better be done by existing JWT libraries of your favourite language

If the above process is successful, it means that the user is signed in to your application and you can consider him authenticated. You can also retrieve the session ID and user ID out of the token's claims.

Using the JWT Verification Key

As mentioned above, your JWT Verification Key is not in a PEM format. This is to avoid having to use "multiline environment variables" which are difficult to use in many systems/environments. In order to get a properly formatted public key from you will need to transform it slightly.

Transform steps:

  • Split the JWT Verification Key into chunks of 64 characters
  • Join the chunks separated with a newline character \n
  • Append, and prepend the appropriate header and footer
    • Header: -----BEGIN PUBLIC KEY-----\n
    • Footer: \n-----END PUBLIC KEY-----

The output should look something like the following:

1
-----BEGIN PUBLIC KEY-----
2
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXbBOUbeOfvxtIUDNDBb
3
77J66FTRpJhB/o3OwQhjYAgcJ1XLEenzUKiczq8b1zJ/xcqdWT4TYamFthcdv/sD
4
zmS13DbHHlkYVfE6hG1sG0r/ahCWORi3F2WZT9u7rjwli3A647e5eHdy3qD/tvcB
5
E42UmRmcL2mbedaQiVy2i5U5qlIXI6YAC+BYiVdw5YOrGNQWzoAHX8kBmwjMWQTQ
6
4AKvSZHYf3Mtd0ni0LTmZD8WXvGbQr4Ia5caZfyGzFkPPbNFSXzyT9yJZOB281HP
7
hMUlECYRmPlHloBZBArowkQRk5rlLKu+tdpUp0B7hJcHTAcgn7qZuecIZX4EmVWM
8
bwIDAQAB
9
-----END PUBLIC KEY-----

The code below, is an example implementation written in javascript:

1
const splitPem = process.env.CLERK_JWT_VERIFICATION_KEY.match(/.{1,64}/g);
2
const publicKey = "-----BEGIN PUBLIC KEY-----\n" + splitPem.join("\n") + "\n-----END PUBLIC KEY-----"

Putting it all together

1
import type { NextApiRequest, NextApiResponse } from 'next';
2
import Cookies from 'cookies';
3
import jwt from 'jsonwebtoken';
4
5
export default async function (req: NextApiRequest, res: NextApiResponse) {
6
const splitPem = process.env.CLERK_JWT_VERIFICATION_KEY.match(/.{1,64}/g);
7
const publicKey =
8
'-----BEGIN PUBLIC KEY-----\n' +
9
splitPem.join('\n') +
10
'\n-----END PUBLIC KEY-----';
11
12
const cookies = new Cookies(req, res);
13
const sessToken = cookies.get('__session');
14
if (!sessToken) {
15
res.status(401).json({ error: 'not signed in' });
16
}
17
18
try {
19
var decoded = jwt.verify(sessToken, publicKey);
20
} catch (error) {
21
res.status(400).json({
22
error: 'Invalid Token'
23
});
24
return;
25
}
26
27
res.status(200).json({ sessToken: decoded });
28
}
1
import Cookies from 'cookies';
2
import jwt from 'jsonwebtoken';
3
4
export default async function (req, res) {
5
const splitPem = process.env.CLERK_JWT_VERIFICATION_KEY.match(/.{1,64}/g);
6
const publicKey =
7
'-----BEGIN PUBLIC KEY-----\n' +
8
splitPem.join('\n') +
9
'\n-----END PUBLIC KEY-----';
10
11
const cookies = new Cookies(req, res);
12
const sessToken = cookies.get('__session');
13
if (!sessToken) {
14
res.status(401).json({ error: 'not signed in' });
15
}
16
17
try {
18
var decoded = jwt.verify(sessToken, publicKey);
19
} catch (error) {
20
res.status(400).json({
21
error: 'Invalid Token'
22
});
23
return;
24
}
25
26
res.status(200).json({ sessToken: decoded });
27
}

Was this helpful?

Clerk © 2023