Skip to Content
Clerk logo

Clerk Docs

Ctrl + K
Go to clerk.com

Manual JWT verification

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 with it). If these validations pass, then it means that the user is authenticated to your application and you should consider them signed in.

Retrieve the session token

Retrieve the session token from either __session cookie for a same origin request or from the Authorization header for cross origin requests.

Get your instance's public key

There are three ways to obtain your public key:

  1. Using the Backend API in JSON Web Key Set (JWKS) format at the following endpoint https://api.clerk.dev/v1/jwks(opens in a new tab).

  2. Using the Frontend API in JSON Web Key Set (JWKS) format at the following endpoint https://<YOUR_FRONTEND_API>/.well-known/jwks.json. This can be obtained from the Clerk Dashboard on the API Keys(opens in a new tab) page. Scroll down and click on Advanced and in the JWT public key section, copy the JWKS URL.

The 'API Keys' page in the Clerk Dashboard. There is a red box around the 'JWT public key' section, with a red arrow pointing to the 'JWKS URL' tab of the code example.
  1. Using the PEM public key provided in the Clerk Dashboard on the API Keys(opens in a new tab) page. Scroll down and click on Advanced and in the JWT public key section, copy the PEM public key. This option should only be used as a fallback for when the first two options are not available.

Verify the token signature

To verify the token signature, you should:

  1. Use the above Public Key to verify the token's signature.
  2. Validate that the token is not expired by checking the exp (Expiration time(opens in a new tab)) and nbf (Not before(opens in a new tab)) claims.
  3. The azp claim in the Clerk Session JWT stands for authorized parties. If the azp claim exists, validate that it 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. Verifying the azp claim on the server side ensures that the session token is generated from the expected frontend application. For example, if you are permitting tokens retrieved from http://localhost:3000, then the azp claim should equal http://localhost:3000. You can also pass an array of strings like so: ['http://localhost:4003', 'https://clerk.dev']. If the azp claim does not exist, then you can skip this step.

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

Example usage

In the example below, the jsonwebtoken(opens in a new tab) library is used to verify the token signature. The cookies(opens in a new tab) library is used to retrieve the __session cookie.

pages/api/token-example.ts
import type { NextApiRequest, NextApiResponse } from "next"; import Cookies from "cookies"; import jwt from "jsonwebtoken"; export default async function (req: NextApiRequest, res: NextApiResponse) { const publicKey = process.env.CLERK_PEM_PUBLIC_KEY; const cookies = new Cookies(req, res); const sessToken = cookies.get("__session"); const token = req.headers.authorization; if (sessToken === undefined && token === undefined) { res.status(401).json({ error: "not signed in" }); return; } try { let decoded = ""; if (token) { decoded = jwt.verify(token, publicKey); res.status(200).json({ sessToken: decoded }); return; } else { decoded = jwt.verify(sessToken, publicKey); res.status(200).json({ sessToken: decoded }); return; } } catch (error) { res.status(400).json({ error: "Invalid Token", }); return; } }

If the token is valid, the response will return a JSON object that looks similar to the one below. You can use this to validate the exp and azp claims, as mentioned in the steps above. Once the token is completely validated, you then have a valid session ID (sid) and user ID (sub) that you can use in your application.

{ "sessToken": { "azp": "http://localhost:3000", "exp": 1687906422, "iat": 1687906362, "iss": "https://magical-marmoset-51.clerk.accounts.dev", "nbf": 1687906352, "sid": "sess_2Ro7e2IxrffdqBboq8KfB6eGbIy", "sub": "user_2RfWKJREkjKbHZy0Wqa5qrHeAnb" } }

Last updated on February 1, 2024

What did you think of this content?

Clerk © 2024