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

# Spaces authentication

Spaces authentication is handled by the underlying Pub/Sub SDK. You authenticate an Ably Realtime client, then pass that authenticated client into `Spaces`.

## How Spaces maps to channels

A logical space is implemented using underlying Pub/Sub channels. Capability expressions should account for both channel types:

* Main space channel: `your-space` (presence and member locations)
* Cursors channel: `your-space-cursors` (high-frequency cursor updates)

## Authentication flow

1. Your auth server authenticates the user.
2. Your auth server issues an Ably-compatible token (JWT format is recommended for most apps).
3. The client SDK fetches tokens with `authCallback` and refreshes them automatically before expiry.
4. The authenticated Pub/Sub client is passed into `Spaces`.

<Aside data-type="important">
  Never use API keys in client-side code. API keys do not expire and are not suitable for untrusted clients.
</Aside>

## Server setup

Create an endpoint that validates user-provided credentials and returns JWTs with the appropriate Spaces capabilities:

<Code>
  ```javascript theme={null}
  // Server-side JWT with space-scoped capabilities
  import jwt from 'jsonwebtoken';

  const [keyName, keySecret] = process.env.ABLY_API_KEY.split(':');

  const ablyJwt = jwt.sign(
    {
      'x-ably-capability': JSON.stringify({
        'your-space': ['publish', 'subscribe', 'presence', 'history'],
        'your-space-cursors': ['publish', 'subscribe'],
      }),
      'x-ably-clientId': userId,
    },
    keySecret,
    { algorithm: 'HS256', keyid: keyName, expiresIn: '1h' }
  );
  ```

  ```python theme={null}
  # Server-side JWT with space-scoped capabilities
  import jwt
  import json
  import time
  import os

  key_name, key_secret = os.environ['ABLY_API_KEY'].split(':')

  now = int(time.time())
  ably_jwt = jwt.encode(
      {
          'iat': now,
          'exp': now + 3600,
          'x-ably-capability': json.dumps({
              'your-space': ['publish', 'subscribe', 'presence', 'history'],
              'your-space-cursors': ['publish', 'subscribe'],
          }),
          'x-ably-clientId': user_id,
      },
      key_secret,
      algorithm='HS256',
      headers={'kid': key_name}
  )
  ```

  ```go theme={null}
  // Server-side JWT with space-scoped capabilities
  header := map[string]string{
      "typ": "JWT",
      "alg": "HS256",
      "kid": keyName,
  }

  currentTime := time.Now().Unix()
  claims := map[string]interface{}{
      "iat":                currentTime,
      "exp":                currentTime + 3600,
      "x-ably-capability":  `{"your-space":["publish","subscribe","presence","history"],"your-space-cursors":["publish","subscribe"]}`,
      "x-ably-clientId":    userId,
  }

  // Sign using HS256 with your API key secret
  ```

  ```ruby theme={null}
  # Server-side JWT with space-scoped capabilities
  require 'jwt'

  key_name, key_secret = ENV['ABLY_API_KEY'].split(':')

  now = Time.now.to_i
  payload = {
    'iat' => now,
    'exp' => now + 3600,
    'x-ably-capability' => '{"your-space":["publish","subscribe","presence","history"],"your-space-cursors":["publish","subscribe"]}',
    'x-ably-clientId' => user_id
  }

  ably_jwt = JWT.encode(payload, key_secret, 'HS256', { 'kid' => key_name })
  ```

  ```php theme={null}
  // Server-side JWT with space-scoped capabilities
  $header = [
      'typ' => 'JWT',
      'alg' => 'HS256',
      'kid' => $keyName
  ];
  $currentTime = time();
  $claims = [
      'iat' => $currentTime,
      'exp' => $currentTime + 3600,
      'x-ably-capability' => '{"your-space":["publish","subscribe","presence","history"],"your-space-cursors":["publish","subscribe"]}',
      'x-ably-clientId' => $userId
  ];
  $base64Header = base64_encode(json_encode($header));
  $base64Claims = base64_encode(json_encode($claims));
  $signature = hash_hmac('sha256', $base64Header . '.' . $base64Claims, $keySecret, true);
  $jwt = $base64Header . '.' . $base64Claims . '.' . base64_encode($signature);
  ```

  ```java theme={null}
  Map<String, Object> headerClaims = new HashMap<>();
  headerClaims.put("typ", "JWT");
  headerClaims.put("alg", "HS256");
  headerClaims.put("kid", keyName);

  long currentTimeInSeconds = System.currentTimeMillis() / 1000;

  Map<String, Object> claims = new HashMap<>();
  claims.put("iat", currentTimeInSeconds);
  claims.put("exp", currentTimeInSeconds + 3600);
  claims.put("x-ably-capability", "{\"your-space\":[\"publish\",\"subscribe\",\"presence\",\"history\"],\"your-space-cursors\":[\"publish\",\"subscribe\"]}");
  claims.put("x-ably-clientId", userId);

  Algorithm algorithm = Algorithm.HMAC256(keySecret);
  String token = JWT.create()
          .withHeader(headerClaims)
          .withPayload(claims)
          .sign(algorithm);
  ```

  ```csharp theme={null}
  // Server-side JWT with space-scoped capabilities
  var header = new Dictionary<string, object>
  {
      { "typ", "JWT" },
      { "alg", "HS256" },
      { "kid", keyName }
  };

  var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
  var claims = new Dictionary<string, object>
  {
      { "iat", currentTime },
      { "exp", currentTime + 3600 },
      { "x-ably-capability", "{\"your-space\":[\"publish\",\"subscribe\",\"presence\",\"history\"],\"your-space-cursors\":[\"publish\",\"subscribe\"]}" },
      { "x-ably-clientId", userId }
  };

  // Sign using HS256 with your API key secret
  ```
</Code>

## Client setup

<Code>
  ```javascript theme={null}
  import Spaces from '@ably/spaces';
  import { Realtime } from 'ably';

  const realtimeClient = new Realtime({
    authCallback: async (tokenParams, callback) => {
      try {
        const response = await fetch('/api/ably-token', { credentials: 'include' });
        if (!response.ok) throw new Error('Auth failed');
        const jwt = await response.text();
        callback(null, jwt);
      } catch (error) {
        callback(error, null);
      }
    },
  });

  const spaces = new Spaces(realtimeClient);
  ```

  ```react theme={null}
  import { useMemo } from 'react';
  import Spaces from '@ably/spaces';
  import { Realtime } from 'ably';
  import { SpacesProvider, SpaceProvider } from '@ably/spaces/react';

  export function App() {
    const realtimeClient = useMemo(
      () =>
        new Realtime({
          authCallback: async (tokenParams, callback) => {
            try {
              const response = await fetch('/api/ably-token', { credentials: 'include' });
              if (!response.ok) throw new Error('Auth failed');
              callback(null, await response.text());
            } catch (error) {
              callback(error, null);
            }
          },
        }),
      []
    );

    const spaces = useMemo(() => new Spaces(realtimeClient), [realtimeClient]);

    return (
      <SpacesProvider client={spaces}>
        <SpaceProvider name="my-space">
          <YourApp />
        </SpaceProvider>
      </SpacesProvider>
    );
  }
  ```
</Code>

In each example, the authenticated Pub/Sub client is passed into `Spaces` and Spaces uses that connection for authentication and token renewal.

## Spaces capabilities

| Feature area                      | Required capabilities   |
| --------------------------------- | ----------------------- |
| Member locations and avatar stack | `subscribe`, `presence` |
| Live cursors                      | `publish`, `subscribe`  |
| Component locking                 | `subscribe`, `presence` |
| History-aware collaboration       | `history`               |

## Space-scoped capabilities

You can scope capabilities to specific spaces or all spaces:

* `my-space` - a specific space and its associated channels
* `my-namespace:*` - all spaces in the `my-namespace:` namespace
* `*` - all spaces

## Token lifecycle and permission updates

* With `authCallback` or `authUrl`, token refresh is automatic and handled by the SDK.
* To change a user's capabilities during an active session, issue a new token from your auth server and re-authenticate:

<Code>
  ```javascript theme={null}
  // Re-authenticate to pick up updated capabilities
  await realtimeClient.auth.authorize();
  ```
</Code>

* To immediately remove access, [revoke issued tokens](/docs/auth/revocation).
* If your capability JSON is too large for JWT or must remain confidential, use native [Ably Tokens](/docs/auth/token/ably-tokens).
