# JWT authentication

Protect AI agent sessions with backend-signed, HS256 JWTs so users can only access their own conversations and actions.

JWT authentication ties every agent request to a backend-verified user identity. Use it on any production deployment so a user can only access their own conversations, history, and actions, and so requests can't be forged from the client.

## Why use JWT

Without authentication, a malicious actor could impersonate a user, read private conversations, or invoke actions on their behalf. JWT authentication guarantees:

- A user can only access *their own* conversations and data.
- Requests to the agent can't be forged from the client side.
- Conversation history and agent actions are protected from identity theft.

## What is a JWT?

A **JSON Web Token (JWT)** is a compact, signed token used to transmit identity information between systems. A JWT:

- Is generated by a trusted backend.
- Is cryptographically signed with a secret key.
- Can be verified by a server without additional database lookups.
- Has a built-in expiration time.

In Foldspace, the JWT acts as **proof of identity** for the user interacting with the agent. Requiring a server-signed token means user identity can't be spoofed, only authenticated users can send messages or retrieve conversations, and tokens expire automatically to limit exposure if compromised.

## How it works

1. You generate a **secret key** in the Foldspace dashboard.
2. Your backend uses the secret to sign a JWT with **HS256**.
3. The signed JWT is sent to the browser.
4. The Foldspace Web SDK includes the JWT in every request.
5. Foldspace verifies the token and user identity on every call.

<Mermaid chart={`sequenceDiagram
    participant B as Your Backend
    participant C as Browser
    participant S as Foldspace SDK
    participant F as Foldspace
    B->>B: Sign JWT (HS256) with secret key
    B->>C: Return signed JWT
    C->>S: Initialize with token
    S->>F: Request + JWT header
    F->>F: Verify signature & expiry
    F-->>S: Authenticated response
    Note over S,F: On expiry, SDK calls onTokenExpired
    S->>C: Request fresh token
    C->>B: Fetch new JWT
    B->>C: New signed JWT
    C->>S: Return token
`} />

## Step 1: Generate a secret key

JWT signing keys are managed under **Settings → Identity Verification**. You can generate one or more **JWT secret keys**.

- These keys sign JWTs in your backend.
- They must be kept **private and secure**.
- They must **never** be exposed in frontend code.

Once generated, copy the secret key and store it securely, for example, as an environment variable.

### Access control

To **copy a secret key** or **change a key's status**, a Foldspace user must have the **Jwt Settings Admin** role. Users without this role can view configuration but can't access or modify secret keys.

## Rotate keys

Foldspace supports key rotation so you can maintain security without service disruption. You manage rotation, and multiple secret keys can coexist for safe transitions in production.

Each secret key has a status that controls how it's enforced.

### Secret key statuses

| Status | Enforcement | Notes |
| --- | --- | --- |
| `INACTIVE` | Not enforced | Default status when a key is created. The key can be copied and safely prepared for deployment. Can be moved to `INACTIVE` from any status except `REVOKED`. |
| `ACTIVE` | Fully enforced | Tokens signed with this key are accepted by Foldspace. |
| `TESTING` | Evaluated, not enforced | Validates JWT signing without enforcing authentication. Only one `TESTING` key is allowed at a time. Verification results are returned in a response header. Does not affect production traffic or agent availability. |
| `DEPRECATED` | Valid and enforced | Used during key rotation. Lets existing backend deployments keep working while a new key is rolled out. |
| `REVOKED` | Rejected | Permanently disabled and deleted. Tokens signed with this key are rejected. Status can't be changed once revoked. |

### Recommended rotation flow

1. Create a new secret key (initially `INACTIVE`).
2. Deploy the new key to your backend.
3. Move the new key to `ACTIVE`.
4. Move the old key to `DEPRECATED`.
5. Once fully migrated, move the deprecated key to `REVOKED`.

This rotates keys with no downtime.

<Mermaid chart={`stateDiagram-v2
    [*] --> INACTIVE: Key created
    INACTIVE --> ACTIVE: Deploy & activate
    INACTIVE --> TESTING: Validate first
    TESTING --> ACTIVE: Promote
    ACTIVE --> DEPRECATED: New key activated
    DEPRECATED --> REVOKED: Migration complete
    REVOKED --> [*]
    note right of TESTING: One TESTING key at a time.<br/>Not enforced — result in header.
    note right of DEPRECATED: Still accepted.<br/>Gives backends time to migrate.
`} />

## Step 2: Sign the JWT in your backend

Create the JWT **only in your backend** and sign it with the **`HS256`** algorithm. Tokens signed with any other algorithm are rejected.

### Required payload

```json
{
  "userId": "<the same user ID you pass to the Foldspace JS SDK>",
  "exp": <expiration time in seconds since epoch>
}
```

### Signing requirements

| Requirement | Value |
| --- | --- |
| Algorithm | `HS256` |
| Secret | `ACTIVE` or `DEPRECATED` Foldspace secret key |
| Location | Backend only |

### Code examples

  
**Node.js**

    ```js

    const FOLDSPACE_SECRET = process.env.FOLDSPACE_JWT_SECRET;

    function generateFoldspaceToken(userId) {
      const expiresInSeconds =
        Math.floor(Date.now() / 1000) + (15 * 60);

      return jwt.sign(
        { userId, exp: expiresInSeconds },
        FOLDSPACE_SECRET,
        { algorithm: "HS256" }
      );
    }
    ```
  
  
**Python**

    ```python
    import jwt
    import time
    import os

    FOLDSPACE_SECRET = os.environ["FOLDSPACE_JWT_SECRET"]

    def generate_foldspace_token(user_id):
        payload = {
            "userId": user_id,
            "exp": int(time.time()) + 900
        }

        return jwt.encode(
            payload,
            FOLDSPACE_SECRET,
            algorithm="HS256"
        )
    ```
  
  
**Ruby**

    ```ruby
    require 'jwt'

    secret = ENV['FOLDSPACE_JWT_SECRET']

    def generate_foldspace_token(user_id, secret)
      payload = {
        userId: user_id,
        exp: Time.now.to_i + 900
      }

      JWT.encode(payload, secret, 'HS256')
    end
    ```
  

## Step 3: Pass the JWT to the Web SDK

Provide the signed JWT to the Foldspace Web SDK through the **`agent`** API:

```js
// 1. Fetch the token from your backend API
const response = await fetch('/api/auth/foldspace-token');
const { token } = await response.json();

// 2. Initialize Foldspace agent via the agent API
const agent = foldspace.agent({
    apiName: 'the-agent-api-name',
    token: token,
    onTokenExpired: async () => {
        const response = await fetch('/api/auth/foldspace-token');
        const { token } = await response.json();
        console.log('token refreshed', token);
        return token;
    }
});

agent.show();
```

Here, the frontend fetches a signed JWT from your backend and passes it via the `token` property. The SDK attaches the token to every request. When the token expires, the SDK calls `onTokenExpired`, and your UI fetches and returns a new token.

## Test your signed JWT

Use a `TESTING` key to validate JWT signing before you enforce authentication in production.

1. Move a secret key to `TESTING` (only one key can be in `TESTING` at a time).
2. Sign a JWT with the `TESTING` key.
3. Send requests using that JWT via the Foldspace SDK.
4. Foldspace evaluates the token and returns the result in a response header.

### Response header

For requests signed with a `TESTING` key, Foldspace returns the verification result in this header:

```text title="Header"
X-Jwt-Testing-Result
```

| Value | Meaning |
| --- | --- |
| `validated` | The JWT is correctly signed and structured. |
| `failed` | The JWT is invalid, expired, or incorrectly signed. |

This lets you verify signature correctness, payload structure, and expiration handling without impacting production users.

:::note
JWTs signed with `ACTIVE` or `DEPRECATED` keys are enforced and must be valid. JWTs signed with a `TESTING` key are evaluated but not enforced: the request proceeds and the result is surfaced only via the response header. This enables production-safe validation before key rotation.
:::

Use a `TESTING` key when you want to validate a new backend JWT implementation, confirm the signing algorithm (`HS256`), verify payload structure and expiration handling, or test new keys before promoting them to `ACTIVE`.

## Choose a secure expiration time

- Use short-lived tokens (5–30 minutes).
- Refresh tokens from your backend when needed.

Short expiration times reduce risk and align with modern security standards.

## Error handling and token lifecycle

**Token expired.** The SDK invokes `onTokenExpired`. Fetch a new JWT with a fresh `exp` from your backend and return it to the SDK.

**Invalid token.** If the token is invalid or can't be verified, all agent requests are rejected, the agent is automatically hidden from the UI, and user data and actions remain protected.

## Related

- [HMAC Identity Verification](/security/hmac/): verify identify calls server-side with HMAC-SHA256.
- [Authentication](/start/authentication/): API keys and scopes.
