Skip to main content

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.

When you configure Access Lists or 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.
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.

How it works

Dynamic emits scopes as a standard JWT scope claim — a single whitespace-separated string per RFC 8693. 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:
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:
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

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
}

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 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 or refreshAuth.