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

# JWT Token Management

> Issuing and verifying JWT tokens for Para session attestation

export const MethodDocs = ({name, description, parameters = [], returns, deprecated = false, since = null, async = false, static: isStatic = false, tag = null, defaultExpanded = false, preventCollapse = false, id = 'method'}) => {
  const [isExpanded, setIsExpanded] = useState(defaultExpanded || preventCollapse);
  const [isHovered, setIsHovered] = useState(false);
  const [isCopied, setIsCopied] = useState(false);
  const [hoveredParam, setHoveredParam] = useState(null);
  const [hoveredReturn, setHoveredReturn] = useState(false);
  const parseMethodName = fullName => {
    const match = fullName.match(/^([^(]+)(\()([^)]*)(\))$/);
    if (match) {
      return {
        name: match[1],
        openParen: match[2],
        params: match[3],
        closeParen: match[4]
      };
    }
    return {
      name: fullName,
      openParen: '',
      params: '',
      closeParen: ''
    };
  };
  const methodParts = parseMethodName(name);
  const handleCopy = e => {
    e.stopPropagation();
    navigator.clipboard.writeText(name);
    setIsCopied(true);
    setTimeout(() => setIsCopied(false), 2000);
  };
  return <div className={`not-prose rounded-2xl border border-gray-200 overflow-hidden transition-colors duration-200 mb-6 ${isHovered && !preventCollapse ? 'bg-gray-50' : 'bg-white'}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      <button onClick={() => !preventCollapse && setIsExpanded(!isExpanded)} className={`w-full bg-transparent p-6 border-none text-left ${preventCollapse ? 'cursor-default' : 'cursor-pointer'}`}>
        <div className="flex items-start justify-between gap-4">
          <div className="flex-1 flex flex-col gap-2">
            <div className="flex items-center gap-3 flex-wrap">
              <div className="flex items-center gap-2">
                {async && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-purple-200 text-purple-800 rounded-lg">
                    async
                  </span>}
                {isStatic && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-violet-200 text-violet-900 rounded-lg">
                    static
                  </span>}
                {tag && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-teal-200 text-teal-800 rounded-lg">
                    {tag}
                  </span>}
              </div>
              
              <code className="text-lg font-mono font-semibold text-gray-900">
                <span>{methodParts.name}</span>
                <span className="text-gray-500 font-normal">{methodParts.openParen}</span>
                <span className="text-blue-600 font-normal">{methodParts.params}</span>
                <span className="text-gray-500 font-normal">{methodParts.closeParen}</span>
              </code>
              
              {deprecated && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-red-100 text-red-800 rounded-lg flex items-center gap-0.5">
                  ⚠ Deprecated
                </span>}
              {since && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-blue-100 text-blue-800 rounded-lg">
                  Since v{since}
                </span>}
            </div>
            
            <p className="text-sm text-gray-600 leading-6 m-0">
              {description}
            </p>
          </div>
          
          <div className="flex items-center gap-2 flex-shrink-0">
            <button onClick={handleCopy} className="p-2 bg-transparent border-none rounded-md cursor-pointer transition-colors duration-200 text-gray-500 hover:bg-gray-100" title="Copy method signature">
              {isCopied ? <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#16a34a" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <polyline points="20 6 9 17 4 12"></polyline>
                </svg> : <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                </svg>}
            </button>
            
            {!preventCollapse && <span className="text-gray-400">
                {isExpanded ? <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                    <polyline points="18 15 12 9 6 15"></polyline>
                  </svg> : <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                    <polyline points="6 9 12 15 18 9"></polyline>
                  </svg>}
              </span>}
          </div>
        </div>
      </button>

      <div className={`overflow-hidden transition-all duration-300 ease-in-out px-6 border-t border-gray-200 ${isExpanded ? 'max-h-[2000px] opacity-100 pb-6' : 'max-h-0 opacity-0 pb-0'}`}>
        {parameters.length > 0 && <div className="pt-6">
            <div className="flex items-center gap-2 mb-3">
              <h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wider m-0">
                Parameters
              </h3>
              <span className="text-xs text-gray-500">({parameters.length})</span>
            </div>
            <div>
              {parameters.map((param, index) => <div key={index} className={`pl-4 border-l-2 transition-colors duration-200 ${hoveredParam === index ? 'border-gray-300' : 'border-gray-200'} ${index < parameters.length - 1 ? 'mb-3' : ''}`} onMouseEnter={() => setHoveredParam(index)} onMouseLeave={() => setHoveredParam(null)}>
                  <div className="flex items-baseline gap-2 mb-1 flex-wrap">
                    <code className="font-mono text-sm font-medium text-gray-900">
                      {param.name}
                    </code>
                    <span className="text-sm text-gray-500">:</span>
                    {param.typeLink ? <a href={param.typeLink} className="no-underline">
                        <code className="font-mono text-sm text-blue-600 bg-transparent px-1 py-0.5 rounded-md cursor-pointer transition-all duration-200 hover:bg-gray-100 hover:text-blue-700">
                          {param.type}
                        </code>
                      </a> : <code className="font-mono text-sm text-blue-600 bg-transparent px-1 py-0.5 rounded-md">
                        {param.type}
                      </code>}
                    {param.required && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-yellow-100 text-yellow-800 rounded-lg">
                        Required
                      </span>}
                    {param.optional && <span className="px-1.5 py-0.5 text-[0.625rem] font-medium bg-gray-100 text-gray-600 rounded-lg">
                        Optional
                      </span>}
                  </div>
                  {param.description && <p className="text-sm text-gray-600 mt-1 mb-0">
                      {param.description}
                    </p>}
                  {param.defaultValue !== undefined && <p className="text-sm text-gray-500 mt-1">
                      Default: <code className="font-mono text-[0.625rem] bg-gray-100 px-1.5 py-0.5 rounded-lg">{param.defaultValue}</code>
                    </p>}
                </div>)}
            </div>
          </div>}

        {returns && <div className={`${parameters.length > 0 ? 'mt-6' : 'pt-6'}`}>
            <h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wider m-0 mb-3">
              Returns
            </h3>
            <div className={`pl-4 border-l-2 transition-colors duration-200 ${hoveredReturn ? 'border-gray-300' : 'border-gray-200'}`} onMouseEnter={() => setHoveredReturn(true)} onMouseLeave={() => setHoveredReturn(false)}>
              <div className="flex items-baseline gap-2 mb-1">
                {returns.typeLink ? <a href={returns.typeLink} className="no-underline">
                    <code className="font-mono text-sm text-blue-600 bg-transparent px-1 py-0.5 rounded cursor-pointer transition-all duration-200 hover:bg-gray-100 hover:text-blue-700">
                      {returns.type}
                    </code>
                  </a> : <code className="font-mono text-sm text-blue-600 bg-transparent px-1 py-0.5 rounded">
                    {returns.type}
                  </code>}
              </div>
              {returns.description && <p className="text-sm text-gray-600 mt-1 mb-0">
                  {returns.description}
                </p>}
            </div>
          </div>}
      </div>
    </div>;
};

export const Card = ({imgUrl, title, description, href, horizontal = false, newTab = false}) => {
  const [isHovered, setIsHovered] = useState(false);
  const handleClick = e => {
    e.preventDefault();
    if (newTab) {
      window.open(href, '_blank', 'noopener,noreferrer');
    } else {
      window.location.href = href;
    }
  };
  return <div className={`not-prose relative my-2 p-[1px] rounded-xl transition-all duration-300 ${isHovered ? 'bg-gradient-to-r from-[#FF4E00] to-[#874AE3]' : 'bg-gray-200'}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      <a href={href} onClick={handleClick} className={`not-prose flex ${horizontal ? 'flex-row' : 'flex-col'} font-normal h-full bg-white overflow-hidden w-full cursor-pointer rounded-[11px] no-underline`}>
        {imgUrl && <div className={`relative overflow-hidden flex-shrink-0 ${horizontal ? 'w-[30%] rounded-l-[11px]' : 'w-full'}`} onClick={e => e.stopPropagation()}>
            <img src={imgUrl} alt={title} className="w-full h-full object-cover pointer-events-none select-none" draggable="false" />
            <div className="absolute inset-0 pointer-events-none" />
          </div>}
        <div className={`flex-grow px-6 py-5 ${horizontal ? 'w-[70%]' : 'w-full'} flex flex-col ${horizontal && imgUrl ? 'justify-center' : 'justify-start'}`}>
          {title && <h2 className="font-semibold text-base text-gray-800 m-0">{title}</h2>}
          {description && <div className={`font-normal text-gray-500 re leading-6 ${horizontal || !imgUrl ? 'mt-0' : 'mt-1'}`}>
              <p className="m-0 text-xs">{description}</p>
            </div>}
        </div>
      </a>
    </div>;
};

export const Link = ({href, label, newTab = false}) => {
  const [isHovered, setIsHovered] = useState(false);
  return <a href={href} target={newTab ? '_blank' : '_self'} rel={newTab ? 'noopener noreferrer' : undefined} className="not-prose inline-block relative text-black font-semibold cursor-pointer border-b-0 no-underline" onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      {label}
      <span className={`absolute left-0 bottom-0 w-full rounded-sm bg-gradient-to-r from-orange-600 to-purple-600 transition-all duration-300 ${isHovered ? 'h-0.5' : 'h-px'}`} />
    </a>;
};

Once a user is signed in, you can request a Para JWT token. This token will provide attestations for the user's ID, their identity, any wallets they have provisioned via your application, and any connected wallets in their current session.

## Requesting a JWT Token

You can request a JWT token using either the client method or the React hook. Both approaches return the token itself as well as the JWKS key ID (`kid`) for the keypair that signed it.

### Client Method

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { ParaWeb } from '@getpara/web-sdk';

  const para = new ParaWeb('your-api-key');

  const { token, keyId } = await para.issueJwt();
  ```

  ```tsx React Hook theme={null}
  import { useIssueJwt } from '@getpara/react-sdk';

  function JwtTokenManager() {
    const { issueJwt, issueJwtAsync, isPending, error } = useIssueJwt();
    const [tokenInfo, setTokenInfo] = useState<{ token: string; keyId: string } | null>(null);

    const handleIssueToken = async () => {
      try {
        const result = await issueJwtAsync();
        
        setTokenInfo({
          token: result.token,
          keyId: result.keyId
        });

        await sendTokenToBackend(result.token);
      } catch (err) {
        console.error("Failed to issue JWT:", err);
      }
    };

    return (
      <button 
        onClick={handleIssueToken}
        disabled={isPending}
      >
        {isPending ? "Issuing..." : "Issue JWT Token"}
      </button>
    );
  }
  ```
</CodeGroup>

### React Hook

<MethodDocs
  defaultExpanded={true}
  preventCollapse={true}
  name="useIssueJwt()"
  description="Issues a new JWT token for authentication."
  parameters={[
{ name: "keyIndex", type: "number", optional: true, description: "Optional key index for JWT generation." }
]}
  returns={{ type: "{ issueJwt: (params?: { keyIndex?: number }) => void, issueJwtAsync: (params?: { keyIndex?: number }) => { token: string, keyId: string }, data: { token: string, keyId: string } | undefined }", description: "An object with functions to issue a JWT and the resulting data." }}
  async={true}
/>

<Info>The token's expiry will be determined by your customized session length, or else will default to 30 minutes. Issuing a token, like most authenticated API operations, will also renew and extend the session for that duration.</Info>

<Tip>The token's `aud` field will be set to your API key's unique ID, linking it specifically to your application.</Tip>

## Token Structure

Depending on the user in question, a decoded token payload might resemble the following:

<CodeGroup>
  ```json Email theme={null}
  {
    "data": {
      "userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
      "wallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "connectedWallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "email": "email@example.com",
      "authType": "email",
      "identifier": "email@example.com",
      "oAuthMethod": "google" // or: undefined | "x" | "discord" | "facebook" | "apple"
    },
    "iat": 1745877709,
    "exp": 1745879509,
    "aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
    "sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
  }
  ```

  ```json Phone theme={null}
  {
    "data": {
      "userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
      "wallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "connectedWallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "phone": "+13105551234",
      "authType": "phone",
      "identifier": "+13105551234"
    },
    "iat": 1745877709,
    "exp": 1745879509,
    "aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
    "sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
  }
  ```

  ```json Telegram theme={null}
  {
    "data": {
      "userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
      "wallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "connectedWallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "telegramUserId": "1234567890",
      "authType": "telegram",
      "identifier": "1234567890"
    },
    "iat": 1745877709,
    "exp": 1745879509,
    "aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
    "sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
  }
  ```

  ```json Farcaster theme={null}
  {
    "data": {
      "userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
      "wallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "connectedWallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "farcasterUsername": "FarcasterUsername",
      "authType": "farcaster",
      "identifier": "FarcasterUsername"
    },
    "iat": 1745877709,
    "exp": 1745879509,
    "aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
    "sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
  }
  ```

  ```json External Wallet theme={null}
  {
    "data": {
      "userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
      "wallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "connectedWallets": [
        {
          "id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
          "type": "EVM",
          "address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
          "publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
        },
        {
          "id": "d70f64e4-266a-457e-9cea-eeb42341a975",
          "type": "SOLANA",
          "address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
          "publicKey": ""
        }
      ],
      "externalWalletAddress": "0xaD6b78193b78e23F9aBBB675734f4a2B3559598D",
      "authType": "externalWallet",
      "identifier": "0xaD6b78193b78e23F9aBBB675734f4a2B3559598D",
      "externalWallet": {
        "address": "0xaD6b78193b78e23F9aBBB675734f4a2B3559598D",
        "type": "EVM",
        "provider": "MetaMask"
      }
    },
    "iat": 1745877709,
    "exp": 1745879509,
    "aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
    "sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
  }
  ```
</CodeGroup>

## JWKS Verification

Para's JSON Web Keys Set (JWKS) file(s) are available at the following URLs:

| Environment | JWKS URL                                             |
| ----------- | ---------------------------------------------------- |
| BETA        | `https://api.beta.getpara.com/.well-known/jwks.json` |
| PROD        | `https://api.getpara.com/.well-known/jwks.json`      |

## Best Practices

* **Session Verification**: For security-critical operations, verify JWT tokens on both client and server sides
* **Token Expiry**: Be aware that tokens expire based on your session configuration and plan accordingly
* **Secure Storage**: Never store JWT tokens in insecure locations like localStorage for sensitive applications
