> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tokens

> Understanding Dynamic-issued JWTs — structure, claims, storage, verification keys, and how to verify tokens on your backend.

## Introduction

When a user signs in with Dynamic, your client receives multiple [JSON Web Token (JWTs)](https://jwt.io/introduction). After authentication Dynamic issues both an **access token** and an **id token**.

To trust the user's identity on your backend, you must **verify the access token on the server** before using any claims. Client-sent tokens cannot be trusted until they are cryptographically verified.

## Types

### Access Tokens (minJwt)

The **access token** JWT is the session credential — it grants the user access to protected resources like wallets and user data on Dynamic's servers and can be used by customers to validate access on customer servers if utilizing Dynamic as the authentication provider for your app.

### ID Tokens (jwt)

The **ID token** JWT is meant to be used by the client application only. The id token contains claims about the authenticated user which can be verified by the application

## JWT structure

A JWT has three parts: **header**, **payload** (claims), and **signature**. Dynamic signs JWTs with **RS256** (RSA signature with SHA-256). The signing key is unique per environment, so you use the public key for your environment to verify that the token was issued by Dynamic and has not been tampered with.

### Standard claims

Dynamic's JWTs include standard [JWT claims](https://www.rfc-editor.org/rfc/rfc7519#section-4.1):

| Claim   | Description                                                                                                                                                                                                                                                                      |
| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `iss`   | Issuer. Dynamic sets this to the issuer for your environment (e.g. `app.dynamicauth.com/<environment_id>`).                                                                                                                                                                      |
| `aud`   | Audience. Intended audience for the token.                                                                                                                                                                                                                                       |
| `sub`   | Subject. The Dynamic user ID.                                                                                                                                                                                                                                                    |
| `iat`   | Issued at. Unix timestamp when the token was issued.                                                                                                                                                                                                                             |
| `exp`   | Expiration. Unix timestamp when the token expires.                                                                                                                                                                                                                               |
| `sid`   | Session Id. The unique identifier for the Dynamic session                                                                                                                                                                                                                        |
| `scope` | Scopes. Whitespace-separated list of permissions granted to this token (e.g. `user:basic`), conforming to the standard JWT scope claim. Verify by checking the list includes `user:basic` (e.g. `decodedToken.scope.split(' ').includes('user:basic')`), not by strict equality. |

### Dynamic-specific claims

The payload may also include Dynamic-specific claims. These fields are optional and depend on whether you collect the information during onboarding:

| Claim                  | Description                                                                                             |
| ---------------------- | ------------------------------------------------------------------------------------------------------- |
| `alias`                | Alias from customer information capture.                                                                |
| `email`                | Email from customer information capture.                                                                |
| `environment_id`       | Unique ID of the project environment (from the [dashboard API](https://app.dynamic.xyz/dashboard/api)). |
| `given_name`           | First name from customer information capture.                                                           |
| `family_name`          | Last name from customer information capture.                                                            |
| `lists`                | Names of access lists enabled for this user. See [Access Control](/overview/access-control/overview).   |
| `verified_credentials` | List of all verified credentials connected to this user.                                                |
| `verified_account`     | If present, the most recently signed and verified account.                                              |

### Example payload

```json theme={"system"}
{
  "alias": "john",
  "aud": "https://dashboard.hello.xyz",
  "verified_credentials": [
    {
      "address": "0x000123abc",
      "chain": "eip155",
      "id": "af615228-99e5-48ee-905d-4575f0a6bfc9",
      "wallet_name": "metamask"
    }
  ],
  "email": "user@example.com",
  "environment_id": "fb6dd9d1-09f5-43c3-8a8c-eab6e44c37f9",
  "family_name": "bot",
  "given_name": "jon",
  "iss": "app.dynamicauth.com/fb6dd9d1-09f5-43c3-8a8c-eab6e44c37f9",
  "lists": ["Community dashboard access list"],
  "scope": "user:basic",
  "sub": "d261ee91-8ea0-4949-b8bb-b6ab4f712a49",
  "verified_account": {
    "address": "0x000123abc",
    "chain": "eip155",
    "id": "af615228-99e5-48ee-905d-4575f0a6bfc9",
    "wallet_name": "metamask"
  },
  "iat": 1660677597,
  "exp": 1660684797
}
```

## Token storage

The JWT is stored either in a **cookie** or in **in-app storage** (e.g. browser or device storage), depending on your project's auth storage settings:

* **Cookie storage**: The browser sends the token to your backend automatically on same-origin requests. The app cannot read the token directly — your backend should rely on the cookie for auth.
* **In-app storage**: The app can read the token and send it explicitly (e.g. in an `Authorization: Bearer <token>` header). Use this when you need to pass the JWT to your backend or third-party integrations.

## Getting the JWT on the client

How you obtain the JWT depends on your SDK. The backend verification steps are the same for all SDKs.

**React** — Use the [getAuthToken](/react/reference/utilities/getauthtoken) utility:

```tsx theme={"system"}
import { getAuthToken } from "@dynamic-labs/sdk-react-core";

const token = getAuthToken(); // returns JWT or undefined if not logged in
```

**React Native** — Access the token from the dynamic client's auth module:

```ts theme={"system"}
const authToken = dynamicClient.auth.token;
```

**Flutter, Swift, Kotlin, JavaScript** — Use `sdk.auth.token` (or equivalent) from your SDK. See your SDK's authentication or session docs for the exact property or method.

## Getting the verification key

To verify a Dynamic-issued JWT, you need the **public key** for your environment. You can get it in two ways:

1. **Developer Dashboard** — In the [API tab](https://app.dynamic.xyz/dashboard/api) of your developer dashboard, you can find the public key for your environment.

2. **JWKS endpoint** — Dynamic exposes a [JWKS](https://datatracker.ietf.org/doc/html/rfc7517) (JSON Web Key Set) endpoint so your server can fetch the public key(s) used to sign tokens:
   ```
   GET https://app.dynamicauth.com/api/v0/sdk/{environmentId}/.well-known/jwks
   ```
   Replace `{environmentId}` with your environment ID (from the dashboard). Your JWT library can use this URL to resolve the key by the `kid` (key ID) in the JWT header. See the [Find JWKS for public key](/api-reference/sdk/find-jwks-for-public-key) API reference.

Any backend stack can call this endpoint or use the dashboard key; no SDK is required.

## Verifying the JWT

**Regardless of which Dynamic SDK you use on the client** (React, React Native, Flutter, Swift, Kotlin, JavaScript, etc.), your backend verifies the same Dynamic-issued JWT the same way:

1. **Client** gets the JWT from Dynamic (the way you obtain it depends on your SDK — e.g. `getAuthToken()`, `sdk.auth.token`, etc.).
2. **Client** sends the JWT to your backend (e.g. in the `Authorization: Bearer <token>` header).
3. **Backend** verifies the JWT using Dynamic's public key (signature and claims).
4. **Backend** uses the verified claims (e.g. `sub` for user ID, `verified_credentials`) to authorize the request.

<Warning>
  **Critical**: A leaked JWT could lead to access to user funds and wallet by an external party. Before sending JWTs to your backend, review [Backend JWT Security best practices](/overview/security/recommendedpaths#backend-jwt-security) to ensure your backend infrastructure is properly secured.
</Warning>

On your backend, after you receive the JWT:

1. **Verify the signature** — Decode the JWT and verify the signature using the public key (RS256). If verification fails, reject the request.

2. **Validate claims** — Ensure:
   * `iss` matches the issuer for your environment (e.g. `app.dynamicauth.com/<environment_id>`).
   * `exp` is in the future and `iat` is reasonable (token not expired, not from the future).
   * `aud` if you use audience restrictions.

3. **Verify the scope** — The JWT's `scope` claim is a space-separated list of scopes and indicates the user's authentication state. **You must verify that the scope list includes `user:basic`** to confirm the user has fully completed authentication. If `user:basic` is not among the scopes, the user has not completed the authentication flow and the JWT should not be trusted for protected operations. Common non-final scopes include:
   * `requiresAdditionalAuth` — User must complete MFA before the token is fully trusted. See [MFA](/overview/authentication/mfa).
   * Other intermediate scopes — User is still in the authentication flow (e.g., completing additional verification steps).

No Dynamic SDK is required on the server. Use any JWT library that supports RS256 and (if you use the JWKS URL) JWKS key resolution — e.g. in Node (`jsonwebtoken` + `jwks-rsa`), Python (`PyJWT`), Go (`jwt-go`), etc.

### Implementation options

#### Option 1: Leverage [NextAuth](https://next-auth.js.org/)

If you are using Next.js, you can [integrate the NextAuth library with Dynamic](/recipes/frameworks/next-auth) to perform server-side verification and then use a session client-side.

#### Option 2: Leverage [Passport.js](https://www.passportjs.org)

We offer an official [passport-dynamic](https://github.com/dynamic-labs/passport-dynamic) extension.

#### Option 3: Do-It-Yourself Verification

1. **Get the JWT from your Dynamic client** — See [Getting the JWT on the client](#getting-the-jwt-on-the-client) above for SDK-specific examples.
2. **Send the JWT to your server** as a Bearer token in the `Authorization` header (e.g. `Authorization: Bearer <token>`).
3. **On your server**, verify the JWT using Dynamic's public key (RS256). The verification logic is the same regardless of which client SDK sent the token.

**Node.js example**

Install [node-jsonwebtoken](https://github.com/auth0/node-jsonwebtoken) and [jwks-rsa](https://www.npmjs.com/package/jwks-rsa), then verify the JWT by fetching the public key from the [JWKS endpoint](/api-reference/sdk/find-jwks-for-public-key):

```javascript theme={"system"}
import jwt from 'jsonwebtoken';
import { JwksClient } from 'jwks-rsa';

// JWKS endpoint is on app.dynamicauth.com
const jwksUrl = `https://app.dynamicauth.com/api/v0/sdk/${YOUR_DYNAMIC_ENV_ID}/.well-known/jwks`;

const client = new JwksClient({
  jwksUri: jwksUrl,
  rateLimit: true,
  cache: true,
  cacheMaxEntries: 5,
  cacheMaxAge: 600000,
});

const signingKey = await client.getSigningKey();
const publicKey = signingKey.getPublicKey();

const decodedToken = jwt.verify(encodedJwt, publicKey, {
  ignoreExpiration: false,
});

// CRITICAL: Verify the scope list includes 'user:basic' to confirm full authentication
// scope is a space-separated list; any token without user:basic has NOT completed authentication
const scopes = (decodedToken.scope || '').split(' ');
if (!scopes.includes('user:basic')) {
  // Reject: user has not completed the full authentication flow
  // This includes cases like 'requiresAdditionalAuth' (MFA pending) or other intermediate states
  throw new Error('Authentication incomplete - scope does not include user:basic');
}

// Use decodedToken (sub, verified_credentials, etc.)
```

This uses:

* [jwks-rsa](https://www.npmjs.com/package/jwks-rsa) — fetches and parses JWKS for JWT verification.
* [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) — encodes, decodes, and validates JWTs.

For other languages (Python, Go, etc.), use the same flow: fetch the public key from the JWKS URL and verify the JWT with RS256. See the [JWT structure](#jwt-structure) and [Security](#security) sections above for claim details and security guidance.

## Security

JWT security (specifically server side handling) is critical. Follow Dynamic's security guidance for JWT lifetime and storage (e.g. never log or persist user JWTs), cookie-based authentication to reduce session hijacking risk, secure storage of API keys (backend only, never on the client), and protection against common attack vectors. See the [Security overview](/overview/security/overview) and [Best Practices](/overview/security/recommendedpaths) (including auth token length and storage, CORS, and developer credentials). For threat-specific mitigations, see [Mitigating attack vectors](/overview/security/threatvectors).

* Configure JWT lifetime to the shortest acceptable duration to balance security and user experience. The maximum is 30 days. See [dashboard security settings](/overview/developer-dashboard/security).
* Never save or log user JWTs.
* Consider cookie-based authentication to reduce session hijacking risk by making the JWT inaccessible to client-side scripts.
* Store API keys on the backend only — never expose them on the client side.
* Never send Id tokens to the server. Only access tokens.

For more, see the [Security overview](/overview/security/overview) and [Best Practices](/overview/authentication/best-practices).

### API reference

* [Find JWKS for public key](/api-reference/sdk/find-jwks-for-public-key)
