diff --git a/src/pages/docs/auth/index.mdx b/src/pages/docs/auth/index.mdx
index 9e8118a4fe..68b7f90809 100644
--- a/src/pages/docs/auth/index.mdx
+++ b/src/pages/docs/auth/index.mdx
@@ -75,6 +75,8 @@ Ably supports two authentication mechanisms:
1. **[Token authentication](/docs/auth/token)**: Short-lived Ably Tokens that expire and can be revoked. Recommended for clients. Use [JWTs](/docs/auth/token/jwt) for most applications, or native [Ably Tokens](/docs/auth/token/ably-tokens) when JWTs aren't suitable.
2. **[Basic authentication](/docs/auth/basic)**: Uses your API key directly. Use only on trusted servers.
+When using token authentication, clients fetch tokens from your server using either `authUrl` or `authCallback`. See [choosing how the client fetches tokens](/docs/auth/token#auth-mechanism) for guidance on which to use.
+
When deciding which method to use, apply the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege): a client should only possess the credentials and rights it needs. If credentials are compromised, the damage is minimized.
Many applications use a mixed strategy: trusted servers use basic authentication to issue tokens, while browsers and devices use those tokens.
diff --git a/src/pages/docs/auth/token/ably-tokens.mdx b/src/pages/docs/auth/token/ably-tokens.mdx
index bc97bd66e1..74f02b5eee 100644
--- a/src/pages/docs/auth/token/ably-tokens.mdx
+++ b/src/pages/docs/auth/token/ably-tokens.mdx
@@ -125,16 +125,16 @@ final tokenRequest = rest.auth.createTokenRequest(tokenParams: tokenParams);
```
-### Client usage
+### Client usage
-The client SDK automatically handles TokenRequests returned from your auth endpoint:
+The client SDK automatically handles the `TokenRequest` returned from your auth endpoint. These examples use `authCallback`, but `authUrl` is also valid; see [choosing how the client fetches tokens](/docs/auth/token#auth-mechanism) for guidance on which to use.
```realtime_javascript
const realtime = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const tokenRequest = await response.json();
callback(null, tokenRequest);
} catch (error) {
@@ -148,7 +148,7 @@ const realtime = new Ably.Realtime({
const realtime = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const tokenRequest = await response.json();
callback(null, tokenRequest);
} catch (error) {
@@ -163,7 +163,7 @@ import aiohttp
async def get_token_request(*args, **kwargs):
async with aiohttp.ClientSession() as session:
- async with session.get('/api/ably-token') as response:
+ async with session.get('/api/auth/token') as response:
if response.status != 200:
raise Exception(f"Auth failed: {response.status}")
return await response.json()
@@ -340,7 +340,7 @@ The client code for Ably Token direct is the same as for TokenRequest — the SD
const realtime = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const tokenDetails = await response.json();
callback(null, tokenDetails);
} catch (error) {
@@ -354,7 +354,7 @@ const realtime = new Ably.Realtime({
const realtime = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const tokenDetails = await response.json();
callback(null, tokenDetails);
} catch (error) {
@@ -369,7 +369,7 @@ import aiohttp
async def get_ably_token(*args, **kwargs):
async with aiohttp.ClientSession() as session:
- async with session.get('/api/ably-token') as response:
+ async with session.get('/api/auth/token') as response:
if response.status != 200:
raise Exception(f"Auth failed: {response.status}")
return await response.json()
diff --git a/src/pages/docs/auth/token/index.mdx b/src/pages/docs/auth/token/index.mdx
index cc6b68251f..98f5ba4734 100644
--- a/src/pages/docs/auth/token/index.mdx
+++ b/src/pages/docs/auth/token/index.mdx
@@ -16,7 +16,7 @@ Token authentication is the recommended authentication method to use client-side
SDKs for [Chat](/docs/chat) and [Spaces](/docs/spaces) products rely on the underlying Pub/Sub library for their connection and authentication, so there is no need to configure auth separately in those SDKs.
-## Choosing a token mechanism
+## Choosing a token format
Ably supports two token formats: JWT, the primary and recommended format, and Ably tokens, a legacy format. Each format is associated with a different flow for issuing tokens to clients.
@@ -68,6 +68,21 @@ Ably enforces maximum TTL (time-to-live) limits:
Attempting to create a token with a TTL that exceeds these limits will result in an error code [40003](/docs/platform/errors/codes#40003).
+## Choosing how the client fetches tokens
+
+`authUrl` and `authCallback` are alternative ways to enable a client to fetch tokens from your server when required, and they apply to both supported token formats. Choose based on the client environment and how much control you need over the request.
+
+| | `authUrl` | `authCallback` |
+| --- | --- | --- |
+| How it works | The SDK makes an HTTP request to a URL you specify and uses the response as the token. | The SDK calls a function you provide, which returns the token. |
+| Best for | Web clients that can pass cookies or other session context automatically to your auth endpoint. | Native and non-web clients, or any case where you need more control. |
+| Control over the request | Limited to `authMethod`, `authHeaders`, and `authParams` [auth options](/docs/api/realtime-sdk/types#auth-options). | Full: run any logic to obtain the token, set custom headers, and handle errors. |
+| Setup | A single client option pointing at your endpoint. | A function in your client code. |
+
+Use `authUrl` when a simple HTTP request to your server returns the token, particularly for web clients where the browser sends session cookies automatically. Use `authCallback` when the token fetch is non-trivial, for example retrieving the token from native storage, adding custom authorization headers, or handling errors yourself. `authCallback` is also the option for non-web clients, though native SDKs support `authUrl` as well.
+
+See [JWTs](/docs/auth/token/jwt#client-setup) and [Ably Tokens](/docs/auth/token/ably-tokens#client-usage) for per-format client setup examples using each mechanism.
+
## Dynamic channel access control
Token authentication allows you to dynamically change a client's channel access permissions without disconnecting. Use the [`authorize()`](/docs/api/realtime-sdk/authentication#authorize) method to re-authenticate with updated [capabilities](/docs/auth/capabilities).
diff --git a/src/pages/docs/auth/token/jwt.mdx b/src/pages/docs/auth/token/jwt.mdx
index e5b660bc77..5b4880347f 100644
--- a/src/pages/docs/auth/token/jwt.mdx
+++ b/src/pages/docs/auth/token/jwt.mdx
@@ -226,9 +226,9 @@ Ably does not support asymmetric signatures based on a key pair belonging to a t
| `exp` | Yes | Expiration time (Unix timestamp) |
| `iat` | Yes | Issued at time (Unix timestamp) |
-## Client setup
+## Client setup
-`authUrl` is useful for web-based clients that can pass cookies automatically. For non-web clients, `authCallback` provides more control.
+A client fetches its JWT using either `authCallback` or `authUrl`. Both are valid and refresh tokens automatically before expiry; see [choosing how the client fetches tokens](/docs/auth/token#auth-mechanism) for guidance on which to use.
### authCallback
@@ -239,7 +239,7 @@ Use `authCallback` to fetch JWTs from your server. The SDK automatically calls t
const realtime = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token', {
+ const response = await fetch('/api/auth/token', {
credentials: 'include',
});
if (!response.ok) throw new Error('Auth failed');
@@ -256,7 +256,7 @@ import aiohttp
async def get_ably_jwt(*args, **kwargs):
async with aiohttp.ClientSession() as session:
- async with session.get('/api/ably-token') as response:
+ async with session.get('/api/auth/token') as response:
if response.status != 200:
raise Exception(f"Auth failed: {response.status}")
return await response.text()
@@ -362,109 +362,109 @@ You can specify an `authUrl` as an alternative to `authCallback`. The SDK makes
```realtime_javascript
-const realtime = new Ably.Realtime({ authUrl: '/auth' });
+const realtime = new Ably.Realtime({ authUrl: '/api/auth/token' });
```
```realtime_ruby
-realtime = Ably::Realtime.new(auth_url: '/auth')
+realtime = Ably::Realtime.new(auth_url: '/api/auth/token')
```
```realtime_python
- realtime = AblyRealtime(auth_url='/auth')
+ realtime = AblyRealtime(auth_url='/api/auth/token')
```
```realtime_java
ClientOptions options = new ClientOptions();
-options.authUrl = "/auth";
+options.authUrl = "/api/auth/token";
AblyRealtime realtime = new AblyRealtime(options);
```
```realtime_objc
ARTClientOptions *options = [[ARTClientOptions alloc] init];
-options.authUrl = [NSURL URLWithString:@"/auth"];
+options.authUrl = [NSURL URLWithString:@"/api/auth/token"];
ARTRealtime *realtime = [[ARTRealtime alloc] initWithOptions:options];
```
```realtime_swift
let options = ARTClientOptions()
-options.authUrl = NSURL(string: "/auth")
+options.authUrl = NSURL(string: "/api/auth/token")
let realtime = ARTRealtime(options: options)
```
```realtime_csharp
ClientOptions options = new ClientOptions();
-options.AuthUrl = new Uri("/auth");
+options.AuthUrl = new Uri("/api/auth/token");
AblyRealtime realtime = new AblyRealtime(options);
```
```realtime_go
-client, err := ably.NewRealtime(ably.WithAuthURL("/auth"))
+client, err := ably.NewRealtime(ably.WithAuthURL("/api/auth/token"))
```
```realtime_kotlin
val options = ClientOptions()
-options.authUrl = "/auth"
+options.authUrl = "/api/auth/token"
val realtime = AblyRealtime(options)
```
```realtime_flutter
final clientOptions = ably.ClientOptions(
- authUrl: '/auth'
+ authUrl: '/api/auth/token'
);
final realtime = ably.Realtime(options: clientOptions);
```
```rest_javascript
- const rest = new Ably.Rest({ authUrl: '/auth' });
+ const rest = new Ably.Rest({ authUrl: '/api/auth/token' });
```
```rest_ruby
- rest = Ably::Rest.new(auth_url: '/auth')
+ rest = Ably::Rest.new(auth_url: '/api/auth/token')
```
```rest_python
- rest = AblyRest(auth_url='/auth')
+ rest = AblyRest(auth_url='/api/auth/token')
```
```rest_php
- $rest = new Ably\AblyRest(['authUrl' => '/auth']);
+ $rest = new Ably\AblyRest(['authUrl' => '/api/auth/token']);
```
```rest_java
ClientOptions options = new ClientOptions();
- options.authUrl = "/auth";
+ options.authUrl = "/api/auth/token";
AblyRest rest = new AblyRest(options);
```
```rest_kotlin
val options = ClientOptions()
- options.authUrl = "/auth"
+ options.authUrl = "/api/auth/token"
val rest = AblyRest(options)
```
```rest_csharp
- AblyRest rest = new AblyRest(new ClientOptions { AuthUrl = new Uri("/auth") });
+ AblyRest rest = new AblyRest(new ClientOptions { AuthUrl = new Uri("/api/auth/token") });
```
```rest_objc
ARTClientOptions *options = [[ARTClientOptions alloc] init];
- options.authUrl = [NSURL URLWithString:@"/auth"];
+ options.authUrl = [NSURL URLWithString:@"/api/auth/token"];
ARTRest *rest = [[ARTRest alloc] initWithOptions:options];
```
```rest_swift
let options = ARTClientOptions()
- options.authUrl = NSURL(string: "/auth")
+ options.authUrl = NSURL(string: "/api/auth/token")
let rest = ARTRest(options: options)
```
```rest_go
- client, err := ably.NewREST(ably.WithAuthURL("/auth"))
+ client, err := ably.NewREST(ably.WithAuthURL("/api/auth/token"))
```
```rest_flutter
final clientOptions = ably.ClientOptions(
- authUrl: '/auth'
+ authUrl: '/api/auth/token'
);
final rest = ably.Rest(options: clientOptions);
```
@@ -481,7 +481,7 @@ Use properties set with [`AuthOptions`](/docs/api/realtime-sdk/authentication#au
```realtime_javascript
const realtime = new Ably.Realtime({
- authUrl: "/auth",
+ authUrl: "/api/auth/token",
authMethod: "POST",
authParams: {p1: param1, b: param2},
authHeaders: {h1: header1, h2: header2}
@@ -489,7 +489,7 @@ const realtime = new Ably.Realtime({
```
```realtime_python
-realtime = AblyRealtime(auth_url='/auth',
+realtime = AblyRealtime(auth_url='/api/auth/token',
auth_method="GET",
auth_headers={'h1': 'v1'},
auth_params={'param1': 'param2'})
@@ -501,7 +501,7 @@ headers.Set("h1", "header1")
headers.Set("h2", "header2")
client, err := ably.NewRealtime(
- ably.WithAuthURL("/auth"),
+ ably.WithAuthURL("/api/auth/token"),
ably.WithAuthMethod("GET"),
ably.WithAuthHeaders(headers),
ably.WithAuthParams(url.Values{
@@ -515,7 +515,7 @@ if err != nil {
```realtime_flutter
final clientOptions = ably.ClientOptions(
- authUrl: '/auth',
+ authUrl: '/api/auth/token',
authMethod: 'GET',
authParams: {
'p1': 'param1',
@@ -531,7 +531,7 @@ final realtime = ably.Realtime(options: clientOptions);
```realtime_java
ClientOptions options = new ClientOptions();
-options.authUrl = "/auth";
+options.authUrl = "/api/auth/token";
options.authMethod = "POST";
options.authParams = new Param[]{
new Param("p1", "param1"),
@@ -547,7 +547,7 @@ AblyRealtime realtime = new AblyRealtime(options);
```rest_javascript
const rest = new Ably.Rest({
- authUrl: "/auth",
+ authUrl: "/api/auth/token",
authMethod: "POST",
authParams: {p1: param1, b: param2},
authHeaders: {h1: header1, h2: header2}
@@ -555,7 +555,7 @@ const rest = new Ably.Rest({
```
```rest_python
-rest = AblyRest(auth_url='/auth',
+rest = AblyRest(auth_url='/api/auth/token',
auth_method="GET",
auth_headers={'h1': 'v1'},
auth_params={'param1': 'param2'})
@@ -567,7 +567,7 @@ headers.Set("h1", "header1")
headers.Set("h2", "header2")
client, err := ably.NewREST(
- ably.WithAuthURL("/auth"),
+ ably.WithAuthURL("/api/auth/token"),
ably.WithAuthMethod("GET"),
ably.WithAuthHeaders(headers),
ably.WithAuthParams(url.Values{
@@ -581,7 +581,7 @@ if err != nil {
```rest_flutter
final clientOptions = ably.ClientOptions(
- authUrl: '/auth',
+ authUrl: '/api/auth/token',
authMethod: 'GET',
authParams: {
'p1': 'param1',
@@ -597,7 +597,7 @@ final rest = ably.Rest(options: clientOptions);
```rest_java
ClientOptions options = new ClientOptions();
-options.authUrl = "/auth";
+options.authUrl = "/api/auth/token";
options.authMethod = "POST";
options.authParams = new Param[]{
new Param("p1", "param1"),
diff --git a/src/pages/docs/chat/authentication.mdx b/src/pages/docs/chat/authentication.mdx
index e122cef7a7..2dfeb5983b 100644
--- a/src/pages/docs/chat/authentication.mdx
+++ b/src/pages/docs/chat/authentication.mdx
@@ -171,7 +171,7 @@ import { ChatClient } from '@ably/chat';
const realtimeClient = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token', { credentials: 'include' });
+ const response = await fetch('/api/auth/token', { credentials: 'include' });
if (!response.ok) throw new Error('Auth failed');
const jwt = await response.text();
callback(null, jwt);
@@ -196,7 +196,7 @@ export function App() {
new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token', { credentials: 'include' });
+ const response = await fetch('/api/auth/token', { credentials: 'include' });
if (!response.ok) throw new Error('Auth failed');
callback(null, await response.text());
} catch (error) {
diff --git a/src/pages/docs/chat/setup.mdx b/src/pages/docs/chat/setup.mdx
index 7439e34e79..f0aeab3161 100644
--- a/src/pages/docs/chat/setup.mdx
+++ b/src/pages/docs/chat/setup.mdx
@@ -172,7 +172,7 @@ Reference the Pub/Sub SDK and the Chat SDK within your HTML file:
const realtime = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const token = await response.text();
callback(null, token);
} catch (error) {
@@ -264,7 +264,7 @@ import { LogLevel } from '@ably/chat'
const realtimeClient = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const token = await response.text();
callback(null, token);
} catch (error) {
@@ -282,7 +282,7 @@ import { LogLevel } from '@ably/chat'
const realtimeClient = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const token = await response.text();
callback(null, token);
} catch (error) {
@@ -360,7 +360,7 @@ val chatClient = ChatClient(realtimeClient)
```
-Your auth server endpoint (`/api/ably-token`) should authenticate the user and return a token. See the [token authentication](/docs/auth/token) documentation for server implementation examples.
+Your auth server endpoint (`/api/auth/token`) should authenticate the user and return a token. See the [token authentication](/docs/auth/token) documentation for server implementation examples.
### Server-side authentication
@@ -398,7 +398,7 @@ import jwt from 'jsonwebtoken';
const [keyName, keySecret] = process.env.ABLY_API_KEY.split(':');
-app.get('/api/ably-jwt', async (req, res) => {
+app.get('/api/auth/token', async (req, res) => {
// Your existing auth middleware validates the user
const userId = req.user.id;
@@ -425,7 +425,7 @@ app.get('/api/ably-jwt', async (req, res) => {
const realtimeClient = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-jwt');
+ const response = await fetch('/api/auth/token');
const jwt = await response.text();
callback(null, jwt);
} catch (error) {
@@ -511,7 +511,7 @@ Set the `logHandler` and `logLevel` properties when [instantiating a client](#in
// Using token authentication (recommended for client-side)
const ably = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
callback(null, await response.text());
},
});
@@ -526,7 +526,7 @@ import { ChatClientProvider } from '@ably/chat/react';
// Using token authentication (recommended for React apps)
const ably = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
callback(null, await response.text());
},
});
diff --git a/src/pages/docs/pub-sub/guides/dashboards-and-visualizations.mdx b/src/pages/docs/pub-sub/guides/dashboards-and-visualizations.mdx
index c8ff405cd9..b2937de143 100644
--- a/src/pages/docs/pub-sub/guides/dashboards-and-visualizations.mdx
+++ b/src/pages/docs/pub-sub/guides/dashboards-and-visualizations.mdx
@@ -355,7 +355,7 @@ function generateViewerToken() {
// Client: Connect with viewer token
const realtime = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
- const token = await fetch('/api/ably-token').then(r => r.text());
+ const token = await fetch('/api/auth/token').then(r => r.text());
callback(null, token);
}
});
diff --git a/src/pages/docs/spaces/authentication.mdx b/src/pages/docs/spaces/authentication.mdx
index b6d78d0911..3ceab0a2d9 100644
--- a/src/pages/docs/spaces/authentication.mdx
+++ b/src/pages/docs/spaces/authentication.mdx
@@ -182,7 +182,7 @@ import { Realtime } from 'ably';
const realtimeClient = new Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token', { credentials: 'include' });
+ const response = await fetch('/api/auth/token', { credentials: 'include' });
if (!response.ok) throw new Error('Auth failed');
const jwt = await response.text();
callback(null, jwt);
@@ -207,7 +207,7 @@ export function App() {
new Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token', { credentials: 'include' });
+ const response = await fetch('/api/auth/token', { credentials: 'include' });
if (!response.ok) throw new Error('Auth failed');
callback(null, await response.text());
} catch (error) {
diff --git a/src/pages/docs/spaces/setup.mdx b/src/pages/docs/spaces/setup.mdx
index b421bc5f9a..65a1347eeb 100644
--- a/src/pages/docs/spaces/setup.mdx
+++ b/src/pages/docs/spaces/setup.mdx
@@ -93,7 +93,7 @@ Use token authentication for browsers and mobile apps. Your auth server endpoint
const client = new Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const token = await response.text();
callback(null, token);
} catch (error) {
@@ -107,7 +107,7 @@ const spaces = new Spaces(client);
const client = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-token');
+ const response = await fetch('/api/auth/token');
const token = await response.text();
callback(null, token);
} catch (error) {
@@ -119,7 +119,7 @@ const spaces = new Spaces(client);
```
-Your auth server endpoint (`/api/ably-token`) should authenticate the user and return a token with the user's `clientId`. See the [token authentication](/docs/auth/token) documentation for server implementation examples.
+Your auth server endpoint (`/api/auth/token`) should authenticate the user and return a token with the user's `clientId`. See the [token authentication](/docs/auth/token) documentation for server implementation examples.
### Server-side authentication
@@ -151,7 +151,7 @@ import jwt from 'jsonwebtoken';
const [keyName, keySecret] = process.env.ABLY_API_KEY.split(':');
-app.get('/api/ably-jwt', async (req, res) => {
+app.get('/api/auth/token', async (req, res) => {
// Your existing auth middleware validates the user
const userId = req.user.id;
@@ -178,7 +178,7 @@ app.get('/api/ably-jwt', async (req, res) => {
const client = new Realtime({
authCallback: async (tokenParams, callback) => {
try {
- const response = await fetch('/api/ably-jwt', {
+ const response = await fetch('/api/auth/token', {
headers: { 'Authorization': `Bearer ${yourAppJwt}` },
});
const jwt = await response.text();