# 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`. ## Server setup Create an endpoint that validates user-provided credentials and returns JWTs with the appropriate Spaces capabilities: ### Javascript ``` // 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 ``` # 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 ``` // 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 ``` # 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 ``` // 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 ``` Map headerClaims = new HashMap<>(); headerClaims.put("typ", "JWT"); headerClaims.put("alg", "HS256"); headerClaims.put("kid", keyName); long currentTimeInSeconds = System.currentTimeMillis() / 1000; Map 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 ``` // Server-side JWT with space-scoped capabilities var header = new Dictionary { { "typ", "JWT" }, { "alg", "HS256" }, { "kid", keyName } }; var currentTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var claims = new Dictionary { { "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 ``` ## Client setup ### Javascript ``` 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 ``` 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 ( ); } ``` 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: ### Javascript ``` // Re-authenticate to pick up updated capabilities await realtimeClient.auth.authorize(); ``` - To immediately remove access, [revoke issued tokens](https://ably.com/docs/auth/revocation.md). - If your capability JSON is too large for JWT or must remain confidential, use native [Ably Tokens](https://ably.com/docs/auth/token/ably-tokens.md). ## Related Topics - [SDK setup](https://ably.com/docs/spaces/setup.md): Install, authenticate and instantiate the Spaces SDK. - [React Hooks](https://ably.com/docs/spaces/react.md): Incorporate Spaces into your React application with idiomatic and user-friendly React Hooks. ## Documentation Index To discover additional Ably documentation: 1. Fetch [llms.txt](https://ably.com/llms.txt) for the canonical list of available pages. 2. Identify relevant URLs from that index. 3. Fetch target pages as needed. Avoid using assumed or outdated documentation paths.