# 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.