> ## 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.

# JWT Scopes & Access Lists

> Read access list / gate scopes from the user's JWT to gate parts of your app.

When you configure [Access Lists](/overview/access-control/access-lists) or [Gates](/overview/access-control/gates) (NFT/token gating) in the dashboard, Dynamic encodes the user's permissions as **scopes** on the session JWT. Reading those scopes lets you conditionally render or block parts of your app.

<Note>
  These session scopes are different from the `TokenScope` enum used for **elevated access tokens** (e.g. `wallet:export`). Elevated scopes are short-lived and granted via step-up auth — see [Step-Up Authentication](/javascript/authentication-methods/step-up-auth/overview).
</Note>

## How it works

Dynamic emits scopes as a standard JWT `scope` claim — a single whitespace-separated string per [RFC 8693](https://datatracker.ietf.org/doc/html/rfc8693#section-4.2). To check them client-side:

1. Read the JWT from `client.token`.
2. Decode the payload (a Base64URL JSON blob).
3. Split the `scope` claim into a string array.

The SDK doesn't export a hook for this in vanilla JavaScript — it's a small utility you can drop in:

```typescript theme={"system"}
import { getDefaultClient } from '@dynamic-labs-sdk/client';

type JwtPayload = {
  exp: number;
  iat: number;
  scope?: string;
  sub: string;
  [key: string]: unknown;
};

const decodeJwt = (jwt: string): JwtPayload => {
  const [, payload] = jwt.split('.');
  const base64 = payload.replaceAll('-', '+').replaceAll('_', '/');
  const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
  return JSON.parse(atob(padded)) as JwtPayload;
};

export const getUserScopes = (client = getDefaultClient()): string[] => {
  if (!client.token) return [];
  return decodeJwt(client.token).scope?.split(/\s+/).filter(Boolean) ?? [];
};
```

## Checking scopes

Wrap the helper above in a `userHasScopes` function with `AND` / `OR` semantics matching the React hook:

```typescript theme={"system"}
type LogicOperator = 'AND' | 'OR';

export const userHasScopes = (
  required: string | string[],
  logicOperator: LogicOperator = 'OR'
): boolean => {
  const userScopes = getUserScopes();
  const requiredList = Array.isArray(required) ? required : [required];

  return logicOperator === 'AND'
    ? requiredList.every((scope) => userScopes.includes(scope))
    : requiredList.some((scope) => userScopes.includes(scope));
};
```

## Usage

<Tabs>
  <Tab title="JavaScript">
    ```javascript theme={"system"}
    import { userHasScopes } from './scopes';

    if (userHasScopes('signing')) {
      // user can sign
    }

    if (userHasScopes(['signing', 'creating'], 'AND')) {
      // user can sign AND create
    }

    if (userHasScopes(['signing', 'creating'], 'OR')) {
      // user can sign OR create
    }
    ```
  </Tab>

  <Tab title="React">
    Re-evaluate when the JWT changes by depending on the token from `useUser` (or wrap in `useOnEvent` for `tokenChanged`).

    ```tsx theme={"system"}
    import { useUser } from '@dynamic-labs-sdk/react-hooks';
    import { userHasScopes, getUserScopes } from './scopes';

    function GatedFeature() {
      const { data: user } = useUser();
      const canSign = userHasScopes('signing');

      if (!user) return <p>Please sign in</p>;
      return canSign ? <SigningWidget /> : <p>You don't have signing access</p>;
    }
    ```

    Want a custom hook? Subscribe to `tokenChanged` once and memoize:

    ```tsx theme={"system"}
    import { useOnEvent } from '@dynamic-labs-sdk/react-hooks';
    import { useState } from 'react';
    import { getUserScopes } from './scopes';

    export function useUserScopes() {
      const [scopes, setScopes] = useState<string[]>(() => getUserScopes());

      useOnEvent({
        event: 'tokenChanged',
        listener: () => setScopes(getUserScopes()),
      });

      return scopes;
    }
    ```
  </Tab>
</Tabs>

## Server-side validation

**Always validate scopes on the server when gating sensitive resources.** A determined client can hide UI but cannot fake a JWT signature — verify the JWT against your project's JWKS in your backend, then check the `scope` claim there too. See [Cookie Authentication](/javascript/authentication-methods/cookie-authentication) for a server-aware setup.

## Limitations

* **No mixing of `AND` / `OR` in one call.** The helper above mirrors the React hook — for nested predicates, compose two calls.
* **Cookie-based sessions hide the token.** In cookie-auth mode, `client.token` is `null` because the token lives in an HTTP-only cookie. Read scopes server-side and pass them back to the client through your own API.
* Access list / gate evaluation runs at sign-in. If you change a list while the user is signed in, the scopes update only on the next [`refreshUser`](/javascript/authentication-methods/refresh-user) or `refreshAuth`.

## Related

* [Access Lists overview](/overview/access-control/access-lists)
* [Gates (NFT/token gating)](/overview/access-control/gates)
* [Step-Up Authentication](/javascript/authentication-methods/step-up-auth/overview)
* [Refresh Auth](/javascript/authentication-methods/refresh-auth)
* [Subscribing to client events](/javascript/reference/client/on-event)
