diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts
index ad8ca06013..077f5ecd7a 100644
--- a/src/data/nav/chat.ts
+++ b/src/data/nav/chat.ts
@@ -58,6 +58,10 @@ export default {
name: 'SDK setup',
link: '/docs/chat/setup',
},
+ {
+ name: 'Authentication',
+ link: '/docs/chat/authentication',
+ },
{
name: 'Connections',
link: '/docs/chat/connect',
diff --git a/src/data/nav/platform.ts b/src/data/nav/platform.ts
index ce1a3b6682..0428b84a06 100644
--- a/src/data/nav/platform.ts
+++ b/src/data/nav/platform.ts
@@ -301,6 +301,36 @@ export default {
},
],
},
+ {
+ name: 'Authentication',
+ pages: [
+ {
+ name: 'Overview',
+ link: '/docs/platform/auth',
+ index: true,
+ },
+ {
+ name: 'Basic auth',
+ link: '/docs/platform/auth/basic',
+ },
+ {
+ name: 'Token auth',
+ link: '/docs/platform/auth/token',
+ },
+ {
+ name: 'Token revocation',
+ link: '/docs/platform/auth/revocation',
+ },
+ {
+ name: 'Identified clients',
+ link: '/docs/platform/auth/identified-clients',
+ },
+ {
+ name: 'Capabilities',
+ link: '/docs/platform/auth/capabilities',
+ },
+ ],
+ },
{
name: 'Tools',
pages: [
diff --git a/src/data/nav/pubsub.ts b/src/data/nav/pubsub.ts
index 649d00dd04..f56ef304d1 100644
--- a/src/data/nav/pubsub.ts
+++ b/src/data/nav/pubsub.ts
@@ -83,36 +83,6 @@ export default {
},
],
},
- {
- name: 'Authentication',
- pages: [
- {
- name: 'Overview',
- link: '/docs/auth',
- index: true,
- },
- {
- name: 'Basic auth',
- link: '/docs/auth/basic',
- },
- {
- name: 'Token auth',
- link: '/docs/auth/token',
- },
- {
- name: 'Token revocation',
- link: '/docs/auth/revocation',
- },
- {
- name: 'Identified clients',
- link: '/docs/auth/identified-clients',
- },
- {
- name: 'Capabilities',
- link: '/docs/auth/capabilities',
- },
- ],
- },
{
name: 'Connections',
pages: [
diff --git a/src/data/nav/spaces.ts b/src/data/nav/spaces.ts
index bc53734db3..e230c99349 100644
--- a/src/data/nav/spaces.ts
+++ b/src/data/nav/spaces.ts
@@ -21,6 +21,10 @@ export default {
name: 'SDK setup',
link: '/docs/spaces/setup',
},
+ {
+ name: 'Authentication',
+ link: '/docs/spaces/authentication',
+ },
{
name: 'React Hooks',
link: '/docs/spaces/react',
diff --git a/src/pages/docs/auth/identified-clients.mdx b/src/pages/docs/auth/identified-clients.mdx
deleted file mode 100644
index e4754fa566..0000000000
--- a/src/pages/docs/auth/identified-clients.mdx
+++ /dev/null
@@ -1,218 +0,0 @@
----
-title: Identified clients
-meta_description: "Clients can be allocated a client ID to help control their operations and interactions with Ably channels."
----
-
-When a client is authenticated and connected to Ably, it is considered to be an authenticated client. While an authenticated client has a means to authenticate with Ably, they do not necessarily have an identity.
-
-When a client is assigned a trusted identity, that is, a `clientId`, then they are considered to be an identified client. For all operations that client performs with the Ably service, their `clientId` field will be automatically populated and can be trusted by other clients.
-
-For example, assume you are building a chat application and want to allow clients to publish messages and be present on a channel. If each client is assigned a trusted identity by your server, such as a unique email address or UUID, then all other subscribed clients can trust any messages or presence events they receive in the channel as being from that client. No other clients are permitted to assume a `clientId` that they are not assigned in their Ably-compatible token. They are unable to masquerade as another `clientId`.
-
-## ClientId immutability
-
-A connection's `clientId`, once set, is immutable. You cannot change a `clientId` after it has been established for a connection.
-
-### Late initialization
-
-It is possible to have late initialization of `clientId` in certain scenarios:
-
-* AuthCallback discovery: When using `authCallback`, the `clientId` may only become known when the first authCallback completes and returns a token with a `clientId`.
-* Opaque token processing: When the library receives an opaque token string, it only learns the `clientId` when it receives a response from the server that has processed the token.
-
-In both cases, the immutability requirement is satisfied because the `clientId` gets set as part of the client's first interaction with the server. Once this initial authentication is complete, the `clientId` cannot be changed for that connection.
-
-## Assign a clientId
-
-There are three different ways a client can be identified with using a `clientId`:
-
-* A client claims a `clientId` when authenticating with an API key.
-* A client is authenticating with a token issued for a specific `clientId`.
-* A client claims a `clientId` when authenticating with a token that is issued for a wildcard `clientId`.
-
-When a client sets their own ID there is the possibility that they can pretend to be someone else. To prevent this, it is recommended that you embed a `clientId` in the token issued to your clients from your auth server. This ensures that the `clientId` is set by your auth server, and eliminates the chance of a client pretending to be someone else.
-
-
-
-### Basic auth
-
-You can use [basic authentication](/docs/auth/basic) to allow a client to claim any `clientId` when they authenticate with Ably. As the assignation of the `clientId` is not handled by a server, it cannot be trusted to represent the genuine identity of the client.
-
-### Token auth
-
-You can use [token authentication](/docs/auth/token) to set an explicit `clientId` when creating or issuing a token. Clients using that token are restricted to operations for only that `clientId`, and all operations will implicitly contain that `clientId`.
-
-For example, when publishing a message, the `clientId` attribute of the message will be pre-populated with that `clientId`. Entering presence will also implicitly use that `clientId`.
-
-The following example demonstrates how to issue an [Ably TokenRequest](/docs/auth/token#token-request) with an explicit `clientId`:
-
-
-```realtime_javascript
- const realtime = new Ably.Realtime({ key: '{{API_KEY}}' });
- const tokenRequest = await realtime.auth.createTokenRequest({ clientId: 'Bob'});
-```
-
-```realtime_nodejs
- const realtime = new Ably.Realtime({ key: '{{API_KEY}}' });
- const tokenRequest = await realtime.auth.createTokenRequest({ clientId: 'Bob'});
-```
-
-```realtime_ruby
- realtime = Ably::Realtime.new(key: '{{API_KEY}}')
- realtime.auth.createTokenRequest(client_id: 'Bob') do |token_request|
- # ... issue the TokenRequest to a client ...
- end
-```
-
-```realtime_python
- realtime = AblyRealtime(key='{{API_KEY}}')
- token_request = await realtime.auth.create_token_request({'client_id': 'Bob'})
- # ... issue the TokenRequest to a client ...
-```
-
-```realtime_java
- ClientOptions options = new ClientOptions();
- options.key = "{{API_KEY}}";
- AblyRealtime realtime = new AblyRealtime(options);
- TokenParams tokenParams = new TokenParams();
- tokenParams.clientId = "Bob";
- TokenRequest tokenRequest;
- tokenRequest = realtime.auth.createTokenRequest(tokenParams, null);
- /* ... issue the TokenRequest to a client ... */
-```
-
-```realtime_csharp
- AblyRealtime realtime = new AblyRealtime("{{API_KEY}}");
- TokenParams tokenParams = new TokenParams {ClientId = "Bob"};
- string tokenRequest = await realtime.Auth.CreateTokenRequestAsync(tokenParams);
- /* ... issue the TokenRequest to a client ... */
-```
-
-```realtime_objc
- ARTRealtime *realtime = [[ARTRealtime alloc] initWithKey:@"{{API_KEY}}"];
- ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"];
- [realtime.auth createTokenRequest:tokenParams options:nil
- callback:^(ARTTokenRequest *tokenRequest NSError *error) {
- // ... issue the TokenRequest to a client ...
- }];
-```
-
-```realtime_swift
- let realtime = ARTRealtime(key: "{{API_KEY}}")
- let tokenParams = ARTTokenParams(clientId: "Bob")
- realtime.auth.createTokenRequest(tokenParams, options: nil) { tokenRequest, error in
- // ... issue the TokenRequest to a client ...
- }
-```
-
-```realtime_go
-realtime, _ := ably.NewRealtime(
- ably.WithKey("{{API_KEY}}"))
-params := &ably.TokenParams{
- ClientID: "Bob",
-}
-tokenRequest, _ := realtime.Auth.CreateTokenRequest(params)
-```
-
-```realtime_flutter
-final realtime = ably.Realtime(options: ably.ClientOptions(key: '{{API_KEY}}'));
-final tokenRequest = await realtime.auth.createTokenRequest(
- tokenParams: ably.TokenParams(clientId: 'Bob'),
-);
-```
-
-```rest_javascript
- const rest = new Ably.Rest({ key: '{{API_KEY}}' });
- const tokenRequest = await realtime.auth.createTokenRequest({ clientId: 'Bob'});
-```
-
-```rest_nodejs
- const rest = new Ably.Rest({ key: '{{API_KEY}}' });
- const tokenRequest = await realtime.auth.createTokenRequest({ clientId: 'Bob'});
-```
-
-```rest_ruby
- rest = Ably::Rest.new(key: '{{API_KEY}}')
- token_request = rest.auth.create_token_request(client_id: 'Bob')
- # ... issue the TokenRequest to a client ...
-```
-
-```rest_python
- rest = AblyRest(key='{{API_KEY}}')
- token_request = await rest.auth.create_token_request({'client_id': 'Bob'})
- # ... issue the TokenRequest to a client ...
-```
-
-```rest_php
- $rest = new Ably\AblyRest(
- ['key' => '{{API_KEY}}']
- );
- $tokenRequest = $rest->auth->createTokenRequest(
- ['clientId' => 'Bob']
- );
- // ... issue the TokenRequest to a client ...
-```
-
-```rest_java
- ClientOptions options = new ClientOptions();
- options.key = "{{API_KEY}}";
- AblyRest rest = new AblyRest(options);
- TokenParams tokenParams = new TokenParams();
- tokenParams.clientId = "Bob";
- TokenRequest tokenRequest;
- tokenRequest = rest.auth.createTokenRequest(tokenParams, null);
- /* ... issue the TokenRequest to a client ... */
-```
-
-```rest_csharp
- AblyRest rest = new AblyRest(new ClientOptions {Key = "{{API_KEY}}"});
- TokenParams tokenParams = new TokenParams {ClientId = "Bob"};
- string tokenRequest = await rest.Auth.CreateTokenRequestAsync(tokenParams);
- // ... issue the TokenRequest to a client ...
-```
-
-```rest_objc
- ARTRest *rest = [[ARTRest alloc] initWithKey:@"{{API_KEY}}"];
- ARTTokenParams *tokenParams = [[ARTTokenParams alloc] initWithClientId:@"Bob"];
- [rest.auth createTokenRequest:tokenParams options:nil
- callback:^(ARTTokenRequest *tokenRequest, NSError *error) {
- // ... issue the TokenRequest to a client ...
- }];
-```
-
-```rest_swift
- let rest = ARTRest(key: "{{API_KEY}}")
- let tokenParams = ARTTokenParams(clientId: "Bob")
- rest.auth.createTokenRequest(tokenParams, options: nil) { tokenRequest, error in
- // ... issue the TokenRequest to a client ...
- }
-```
-
-```rest_go
-rest, _ := ably.NewREST(
- ably.WithKey("{{API_KEY}}"))
-params := &ably.TokenParams{
- ClientID: "Bob",
-}
-tokenRequest, _ := rest.Auth.CreateTokenRequest(params)
-```
-
-```rest_flutter
-final rest = ably.Rest(options: ably.ClientOptions(key: '{{API_KEY}}'));
-final tokenRequest = await rest.auth.createTokenRequest(
- tokenParams: ably.TokenParams(clientId: 'Bob'),
-);
-```
-
-
-### Wildcard token auth
-
-You can use [token authentication](/docs/auth/token) to set a wildcard `clientId` using a value of `*` when creating a token. Clients are then able to assume any identity in their operations, such as when publishing a message or entering presence.
-
-## Unidentified clients
-
-If no `clientId` is provided when using [token authentication](/docs/auth/token) then clients are not permitted to assume an identity and will be considered an unidentified client in all operations. Messages published will contain no `clientId` and those clients will not be permitted to enter the [presence](/docs/presence-occupancy/presence) set.
diff --git a/src/pages/docs/auth/index.mdx b/src/pages/docs/auth/index.mdx
deleted file mode 100644
index 51949e95e5..0000000000
--- a/src/pages/docs/auth/index.mdx
+++ /dev/null
@@ -1,110 +0,0 @@
----
-title: Authentication overview
-meta_description: "Ably supports two main authentication schemes: basic authentication and token authentication. Token authentication can be implemented using JWTs, Ably tokens, and Ably token requests."
-redirect_from:
- - /docs/rest/authentication
- - /docs/rest/versions/v1.1/authentication
- - /docs/rest/versions/v1.0/authentication
- - /docs/rest/versions/v0.8/authentication
- - /docs/realtime/authentication
- - /docs/realtime/versions/v1.1/authentication
- - /docs/realtime/versions/v1.0/authentication
- - /docs/realtime/versions/v0.8/authentication
- - /docs/core-features/authentication
- - /docs/core-features/versions/v1.1/authentication
- - /docs/core-features/versions/v1.0/authentication
- - /docs/core-features/versions/v0.8/authentication
- - /docs/core-features/authentication.ably-jwt
- - /docs/general/authentication
- - /docs/general/_authentication_capabilities
- - /docs/ids-and-keys
----
-
-Before a client or server can issue requests to Ably, such as subscribe to channels, or publish messages, it must authenticate with Ably. Authentication requires an Ably API key.
-
-## Authentication terminology
-
-The following terminology helps explain authentication, authorization, and identification in the context of the Ably service:
-
-"Authentication" is the process of deciding, based on the presented credentials, whether or not an entity may interact with the Ably service. The credentials may be presented explicitly using [Basic Authentication](/docs/auth/basic) or [Token Authentication](/docs/auth/token), or in some cases the entity authenticating may prove possession of the credentials with a signed Token Request that is subsequently used to generate a valid token to be used for Token Authentication. When authenticating with Ably, the credentials are either an API key or an auth token.
-
-"Authenticated client" is a client of the Ably service that has been successfully authenticated.
-
-"Authorization" is the process of deciding whether or not a given entity (usually authenticated) is allowed to perform a given operation. In Ably, authorization for most operations is based on the [capabilities](/docs/auth/capabilities) associated with the key or token that was used to authenticate a client.
-
-"Identified client" is an authenticated client with a specific claimed client identity, or `clientId`, whose credentials are verified as confirming that identity. See the [identified clients](/docs/auth/identified-clients) documentation for more information.
-
-
-
-## Ably API keys
-
-Every Ably app can have one or more API keys associated with it in order to authenticate directly with Ably, or to issue tokens with. API keys can be created with different [capabilities](/docs/auth/capabilities) and any tokens issued using that API key can only permit a subset of those capabilities.
-
-### API key format
-
-An Ably API key string has the following format: `I2E_JQ.OqUdfg:EVKVTCBlzLBPYJiCZTsIW_pqylJ9WVRB5K9P19Ap1y0`.
-
-The API key is made up of three parts:
-
-1. `I2E_JQ` is the public app ID (the part before the first period)
-2. `OqUdfg` is the public app key ID (the part after the period and before the colon). `I2E_JQ.OqUdfg` is the public API key ID (both the public app ID and app key ID together)
-3. `EVKVTCBlzLBPYJiCZTsIW_pqylJ9WVRB5K9P19Ap1y0` is the API key secret and should never be shared with untrusted parties (the part after the colon)
-
-The API key secret is private and should never be made public. This API key string is used in all Ably SDKs and for authentication with the REST API.
-
-### Create an API key
-
-API keys are created in the [Ably dashboard](https://ably.com/dashboard). You can also create an API key programmatically using the [Control API](/docs/platform/account/control-api).
-
-To create an API key in the Ably dashboard:
-
-1. In your [Ably dashboard](https://ably.com/dashboard) click the API Keys tab.
-2. Click the **Create a new API key** button.
-3. Enter a name for your API key - this will help you identify the specific key when you have many keys created.
-4. Select the [capabilities](/docs/auth/capabilities) you want to apply to the key. Clients connecting with this key will be restricted to these capabilities.
-5. Optionally you can select whether to make tokens generated from this key to be revocable or not.
-6. Optionally select whether you want to restrict the scope of the key to channels, queues, or specific channels and queues using resource names and wildcards.
-
-
-
-## Available authentication mechanisms
-
-The two authentication mechanisms available to authenticate with Ably are:
-
-1. [Basic authentication](/docs/auth/basic), which uses your Ably API key directly.
-2. [Token authentication](/docs/auth/token), which uses short-lived tokens for access. These tokens are periodically renewed, and can be revoked if required.
-
-### Client-side authentication
-
-[Token authentication](/docs/auth/token) is the recommended authentication mechanism on the client-side, as it provides more fine-grained access control and limits the risk of exposing your Ably API key.
-
-In production systems you should never use basic authentication on the client-side as it exposes your Ably API key. API keys don't have an expiry, so once compromised, they could be used indefinitely by an attacker.
-
-Tokens have an expiry, and so there is only a small period of time during which the compromised token can be used. It is also possible to [revoke tokens](/docs/auth/revocation), should that be necessary for security reasons.
-
-### Server-side authentication
-
-Use [basic authentication](/docs/auth/basic) on the server-side. You should never use token authentication server-side, as this results in unnecessary overhead due the server needing to periodically make token requests to authenticate itself.
-
-## Selecting an authentication mechanism
-
-When deciding on which authentication method you will be using, it is recommended you bear in mind the [principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege).
-
-A client should ideally only possess the credentials and rights that it needs to accomplish what it wants. This way, if the credentials are compromised, the rights that can be abused by an attacker are minimized.
-
-The following table provides guidelines on what to consider when choosing your authentication method. Many applications will most naturally use a mixed strategy: one or more trusted application servers will use basic authentication to access the service and issue tokens over HTTPS, whereas remote browsers and devices will use individually issued tokens:
-
-| Scenario | [Basic](/docs/auth/basic) | [Token](/docs/auth/token) | Description |
-|----------|---------------------------|---------------------------|-------------|
-| Your scripts may be exposed | No | Yes | If the script, program or system holding the key is exposed, for example on a user's device, you should not embed an API key and instead use token authentication. If the script is on a secure environment such as your own server, an API key with basic authentication is fine. |
-| Your connection may be insecure | No | Yes | If there is a risk of exposure of the client's credentials, either directly or over an insecure, or insecurely proxied, connection, token authentication should be used. If you are sure the connection is secure and unmediated, basic authentication is acceptable. |
-| You have no server to control access | Yes | No | If you do not have your own server to perform authentication and provide tokens to users, you'll need to use basic authentication. |
-| You require fine-grained access control | No | Yes | If you need to provide [privileges](/docs/auth/capabilities) on a user-by-user basis, you'd be better using token authentication. If you only need a few access control groups, basic authentication is reasonable. |
-| Users need restricted periods of access | No | Yes | If you need users to only have access for a certain period of time, or the ability to revoke access, token authentication is needed. If users are able to always have access, basic authentication is acceptable. |
-| Users need to identify themselves | Partial | Yes | If the user can be trusted to [identify](/docs/auth/identified-clients) itself, basic authentication is fine. If the user cannot be trusted however, token authentication is better as it allows for a trusted token distributor to identify the user instead. |
diff --git a/src/pages/docs/chat/authentication.mdx b/src/pages/docs/chat/authentication.mdx
new file mode 100644
index 0000000000..8794a31605
--- /dev/null
+++ b/src/pages/docs/chat/authentication.mdx
@@ -0,0 +1,113 @@
+---
+title: Chat authentication
+meta_description: "Configure authentication for Chat applications with the required capabilities."
+---
+
+This page covers Chat-specific capabilities and room scoping. For client setup and server implementation, see [SDK setup](/docs/chat/setup).
+
+## Chat capabilities
+
+Capabilities are permissions that control what operations a client can perform. When you create a token for a Chat user, you specify which capabilities they have. Each Chat feature requires specific capabilities:
+
+| Feature | Required Capabilities |
+|---------|----------------------|
+| Send messages | `publish` |
+| Receive messages | `subscribe` |
+| Update messages | `message-update-any` or `message-update-own` |
+| Delete messages | `message-delete-any` or `message-delete-own` |
+| Message history | `subscribe`, `history` |
+| Message reactions | `annotation-publish`, optionally `annotation-subscribe` |
+| Presence | `subscribe`, `presence` |
+| Typing indicators | `publish`, `subscribe` |
+| Room reactions | `publish`, `subscribe` |
+| Occupancy | `subscribe`, `channel-metadata` |
+| **All Chat features** | `publish`, `subscribe`, `presence`, `history`, `channel-metadata`, `annotation-publish`, `annotation-subscribe`, `message-update-own`, `message-delete-own` |
+
+## Room-scoped capabilities
+
+You can restrict tokens to specific chat rooms. Chat rooms use a channel naming format of `your-room::$chat`:
+
+
+```javascript
+// Server-side JWT with room-scoped capabilities
+import jwt from 'jsonwebtoken';
+
+const [keyName, keySecret] = process.env.ABLY_API_KEY.split(':');
+
+const ablyJwt = jwt.sign(
+ {
+ 'x-ably-capability': JSON.stringify({
+ // Only allow access to a specific room
+ 'your-room::$chat': ['publish', 'subscribe', 'presence', 'history'],
+ }),
+ 'x-ably-clientId': userId,
+ },
+ keySecret,
+ { algorithm: 'HS256', keyid: keyName, expiresIn: '1h' }
+);
+```
+
+```python
+# Server-side JWT with room-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({
+ # Only allow access to a specific room
+ 'your-room::$chat': ['publish', 'subscribe', 'presence', 'history'],
+ }),
+ 'x-ably-clientId': user_id,
+ },
+ key_secret,
+ algorithm='HS256',
+ headers={'kid': key_name}
+)
+```
+
+
+
+
+## Client setup
+
+Use `authCallback` to fetch JWTs from your auth server:
+
+
+```javascript
+import * as Ably from 'ably';
+import { ChatClient } from '@ably/chat';
+
+const realtimeClient = new Ably.Realtime({
+ authCallback: async (tokenParams, callback) => {
+ try {
+ const response = await fetch('/api/ably-token');
+ const jwt = await response.text();
+ callback(null, jwt);
+ } catch (error) {
+ callback(error, null);
+ }
+ },
+});
+
+const chatClient = new ChatClient(realtimeClient);
+```
+
+
+See [token authentication](/docs/auth/token#auth-callback) for complete examples in all languages.
+
+## Further reading
+
+- **Token lifecycle**: Tokens expire and the SDK automatically refreshes them via your `authCallback` - see [Token authentication](/docs/auth/token)
+- **Revoke access**: You can instantly revoke tokens for compromised or banned users - see [Token revocation](/docs/auth/revocation)
+- **Dynamic permissions**: Users can get new capabilities by re-authenticating (e.g., when a moderator grants permissions) - see [Capabilities](/docs/auth/capabilities)
+- **Large capability sets**: If your capability JSON exceeds JWT size limits, use an Ably TokenRequest instead - see [Token authentication](/docs/auth/token#token-request)
diff --git a/src/pages/docs/chat/setup.mdx b/src/pages/docs/chat/setup.mdx
index fdb0aafdaf..c14d4cf459 100644
--- a/src/pages/docs/chat/setup.mdx
+++ b/src/pages/docs/chat/setup.mdx
@@ -11,19 +11,23 @@ If you have any feedback or feature requests, [let us know](https://forms.gle/Sm
## Authentication
-An API key is required to authenticate with Ably. API keys are used either to authenticate directly with Ably using basic authentication, or to generate tokens for untrusted clients using [token authentication](/docs/auth/token).
-
-[Sign up](https://ably.com/sign-up) to Ably to create an API key in the dashboard or use the [Control API](/docs/platform/account/control-api) to create an API key programmatically.
+Chat requires an authenticated client with a `clientId` to identify users. The recommended approach is:
+
+1. **Client-side apps** (browsers, iOS, Android): Use [JWT authentication](/docs/auth/token#choosing-jwt) with `authCallback` to fetch JWTs from your server
+2. **Server-side apps** (Node.js, Python, etc.): Use your API key directly
+
+[Sign up](https://ably.com/sign-up) to Ably to create an API key in the [dashboard](https://ably.com/dashboard) or use the [Control API](/docs/platform/account/control-api) to create an API key programmatically. Your server will use this key to issue tokens to clients.
API keys and tokens have a set of [capabilities](/docs/auth/capabilities) assigned to them that specify which operations, such as subscribe or publish can be performed on which resources. To use the Chat SDK, the API key requires the following capabilities depending on which features are being used:
| Feature | Capabilities |
| ------- | ------------ |
-| Send and receive messages | `publish`, `subscribe` |
+| Send messages | `publish` |
+| Receive messages | `subscribe` |
| Update message | `message-update-any` or `message-update-own` |
| Delete message | `message-delete-any` or `message-delete-own` |
| Message history | `subscribe`, `history` |
@@ -35,11 +39,21 @@ API keys and tokens have a set of [capabilities](/docs/auth/capabilities) assign
When setting the capabilities for Chat, you can apply them to specific chat rooms, a group of chat rooms in a common namespace, or all chat rooms:
-* `my-chat-room` or
-* `dms:*` or
-* `*`
+* `my-chat-room::$chat` - a specific room
+* `dms:*::$chat` - all rooms in the `dms:` namespace
+* `*::$chat` - all chat rooms
+
+The `::$chat` suffix matches the internal channel name the Chat SDK uses. When calling `rooms.get('my-room')`, the SDK automatically uses `my-room::$chat` as the underlying channel.
+
+For more guidance, see the [capabilities documentation](/docs/auth/capabilities) and [Chat authentication](/docs/chat/authentication) for room-scoped examples.
+
+### Client identification
+
+Every Chat client must have a `clientId` - this is a **hard requirement**. The Chat SDK uses the `clientId` to identify who sends messages, who is present in a room, and who is typing.
+
+Your auth server sets the `clientId` when creating tokens. This ensures users can't impersonate each other - the identity is controlled server-side, not by the client.
-For more guidance, see the [capabilities documentation](/docs/auth/capabilities).
+If you try to connect without a `clientId`, the connection will fail.
## Install
@@ -82,7 +96,18 @@ Reference the Pub/Sub SDK and the Chat SDK within your HTML file:
```
@@ -137,24 +162,52 @@ For groovy:
## Instantiate a client
+Authentication is configured on the Ably Pub/Sub client, which the Chat client wraps. The Chat SDK itself doesn't handle authentication directly - it uses the authenticated connection from the underlying Pub/Sub client.
+
Instantiate a realtime client using the Pub/Sub SDK and pass the generated client into the Chat constructor. **It is strongly recommended that you initialise the clients outside of the React component tree** to avoid creating unnecessary additional connections to Ably caused by re-renders.
Pass the `ChatClient` into the [`ChatClientProvider`](https://sdk.ably.com/builds/ably/ably-chat-js/main/typedoc/functions/chat-react.ChatClientProvider.html). The `ChatClient` instance will be available to all child components in your React component tree.
+### Client-side authentication (recommended)
+
+Use token authentication for browsers and mobile apps. Your auth server endpoint validates the user and returns an Ably token with the appropriate `clientId`:
+
```javascript
+// Client-side: Token authentication (recommended for browsers)
import { LogLevel } from '@ably/chat'
-const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', clientId: ''});
+const realtimeClient = new Ably.Realtime({
+ authCallback: async (tokenParams, callback) => {
+ try {
+ const response = await fetch('/api/ably-token');
+ const token = await response.text();
+ callback(null, token);
+ } catch (error) {
+ callback(error, null);
+ }
+ },
+});
const chatClient = new ChatClient(realtimeClient, { logLevel: LogLevel.Error });
```
```react
+// Client-side: Token authentication (recommended for React apps)
import { LogLevel } from '@ably/chat'
-const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', clientId: ''});
+const realtimeClient = new Ably.Realtime({
+ authCallback: async (tokenParams, callback) => {
+ try {
+ const response = await fetch('/api/ably-token');
+ const token = await response.text();
+ callback(null, token);
+ } catch (error) {
+ callback(error, null);
+ }
+ },
+});
const chatClient = new ChatClient(realtimeClient, { logLevel: LogLevel.Error });
const App = () => {
@@ -167,22 +220,42 @@ const App = () => {
```
```swift
+// Client-side: Token authentication (recommended for iOS apps)
let realtimeOptions = ARTClientOptions()
-realtimeOptions.key = "{{API_KEY}}"
-realtimeOptions.clientId = ""
+realtimeOptions.authCallback = { tokenParams, callback in
+ // Fetch token from your auth server
+ fetchAblyToken { result in
+ switch result {
+ case .success(let tokenRequest):
+ callback(tokenRequest, nil)
+ case .failure(let error):
+ callback(nil, error)
+ }
+ }
+}
let realtime = ARTRealtime(options: realtimeOptions)
let chatClient = ChatClient(realtime: realtime)
```
```kotlin
+// Client-side: Token authentication (recommended for Android apps)
import com.ably.chat.ChatClient
import io.ably.lib.realtime.AblyRealtime
import io.ably.lib.types.ClientOptions
val realtimeClient = AblyRealtime(
ClientOptions().apply {
- key = "{{API_KEY}}"
- clientId = ""
+ authCallback = { tokenParams, callback ->
+ // Fetch token from your auth server
+ fetchAblyToken { result ->
+ result.onSuccess { tokenRequest ->
+ callback.onSuccess(tokenRequest)
+ }
+ result.onFailure { error ->
+ callback.onError(ErrorInfo(error.message, 40000, 401))
+ }
+ }
+ }
},
)
@@ -190,9 +263,118 @@ val chatClient = ChatClient(realtimeClient)
```
-A [`ClientOptions`](/docs/api/realtime-sdk#client-options) object may be passed to the Pub/Sub SDK instance to further customize the connection, however at a minimum you must set an API key and provide a `clientId` to ensure that the client is [identified](/docs/auth/identified-clients).
+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.
+
+### Server-side authentication
+
+For server-side applications or local development, you can use an API key directly:
+
+
+```javascript
+// Server-side only: API key authentication
+// WARNING: Never use this in client-side code (browsers, mobile apps)
+import { LogLevel } from '@ably/chat'
+
+const realtimeClient = new Ably.Realtime({
+ key: process.env.ABLY_API_KEY,
+ clientId: 'server-process-1',
+});
+const chatClient = new ChatClient(realtimeClient, { logLevel: LogLevel.Error });
+```
+
+
+
+
+A [`ClientOptions`](/docs/api/realtime-sdk#client-options) object may be passed to the Pub/Sub SDK instance to further customize the connection. When using token authentication, the `clientId` is set by your auth server. When using API key authentication server-side, you must provide a `clientId` to ensure that the client is [identified](/docs/auth/identified-clients).
+
+### Using Ably JWT (alternative)
+
+If you have existing JWT-based authentication infrastructure (Auth0, Firebase, Cognito, or custom), you can create Ably JWTs directly without using the Ably SDK on your server:
+
+**Server (no Ably SDK required):**
+
+
+```javascript
+import jwt from 'jsonwebtoken';
+
+const [keyName, keySecret] = process.env.ABLY_API_KEY.split(':');
+
+app.get('/api/ably-jwt', async (req, res) => {
+ // Your existing auth middleware validates the user
+ const userId = req.user.id;
+
+ const ablyJwt = jwt.sign(
+ {
+ 'x-ably-capability': JSON.stringify({
+ '*': ['publish', 'subscribe', 'presence', 'history'],
+ }),
+ 'x-ably-clientId': userId,
+ },
+ keySecret,
+ { algorithm: 'HS256', keyid: keyName, expiresIn: '1h' }
+ );
+
+ res.send(ablyJwt);
+});
+```
+
+
+**Client:**
+
+
+```javascript
+const realtimeClient = new Ably.Realtime({
+ authCallback: async (tokenParams, callback) => {
+ try {
+ const response = await fetch('/api/ably-jwt');
+ const jwt = await response.text();
+ callback(null, jwt);
+ } catch (error) {
+ callback(error, null);
+ }
+ },
+});
+const chatClient = new ChatClient(realtimeClient);
+```
+
+```swift
+let realtimeOptions = ARTClientOptions()
+realtimeOptions.authCallback = { tokenParams, callback in
+ fetchAblyJwt { result in
+ switch result {
+ case .success(let jwt):
+ callback(jwt as ARTTokenDetailsCompatible, nil)
+ case .failure(let error):
+ callback(nil, error)
+ }
+ }
+}
+let realtime = ARTRealtime(options: realtimeOptions)
+let chatClient = ChatClient(realtime: realtime)
+```
+
+```kotlin
+val realtimeClient = AblyRealtime(
+ ClientOptions().apply {
+ authCallback = Auth.TokenCallback { _ ->
+ // Fetch JWT from your server
+ fetchAblyJwt()
+ }
+ }
+)
+val chatClient = ChatClient(realtimeClient)
+```
+
+
+**Why choose JWT for Chat?**
+- No Ably SDK required on your server
+- Integrates with existing Auth0/Firebase/Cognito flows
+- Supports [channel-scoped claims](/docs/auth/capabilities#custom-restrictions) for user roles in chat rooms
+- Eliminates client round-trip to Ably
-In many cases, a users unique application-specific identifier may be used as the `clientId` to provide consistent identification for clients across your application.
+See [Choosing a token mechanism](/docs/auth/token#choosing) for detailed guidance on when to use JWT vs TokenRequest.
Additional options can also be passed to the Chat client to customize the following properties:
@@ -229,7 +411,13 @@ Set the `logHandler` and `logLevel` properties when [instantiating a client](#in
```javascript
-const ably = new Ably.Realtime({ key: '{{API_KEY}}', clientId: ''});
+// Using token authentication (recommended for client-side)
+const ably = new Ably.Realtime({
+ authCallback: async (tokenParams, callback) => {
+ const response = await fetch('/api/ably-token');
+ callback(null, await response.text());
+ },
+});
const chatClient = new ChatClient(ably, {logHandler: logWriteFunc, logLevel: 'debug' });
```
@@ -238,10 +426,16 @@ import * as Ably from 'ably';
import { LogLevel } from '@ably/chat';
import { ChatClientProvider } from '@ably/chat/react';
-const ably = new Ably.Realtime({ key: '{{API_KEY}}', clientId: ''});
+// Using token authentication (recommended for React apps)
+const ably = new Ably.Realtime({
+ authCallback: async (tokenParams, callback) => {
+ const response = await fetch('/api/ably-token');
+ callback(null, await response.text());
+ },
+});
const chatClient = new ChatClient(ably, {logHandler: logWriteFunc, logLevel: 'debug' });
-const App = => {
+const App = () => {
return (
@@ -252,18 +446,30 @@ const App = => {
```swift
let realtimeOptions = ARTClientOptions()
-realtimeOptions.key = "{{API_KEY}}"
-realtimeOptions.clientId = ""
+// Using token authentication (recommended for iOS apps)
+realtimeOptions.authCallback = { tokenParams, callback in
+ fetchAblyToken { result in
+ switch result {
+ case .success(let tokenRequest): callback(tokenRequest, nil)
+ case .failure(let error): callback(nil, error)
+ }
+ }
+}
let realtime = ARTRealtime(options: realtimeOptions)
let clientOptions = ChatClientOptions(logHandler: SomeLogHandler(), logLevel: .debug)
return ChatClient(realtime: realtime, clientOptions: clientOptions)
```
```kotlin
+// Using token authentication (recommended for Android apps)
val realtimeClient = AblyRealtime(
ClientOptions().apply {
- key = "{{API_KEY}}"
- clientId = ""
+ authCallback = { tokenParams, callback ->
+ fetchAblyToken { result ->
+ result.onSuccess { callback.onSuccess(it) }
+ result.onFailure { callback.onError(ErrorInfo(it.message, 40000, 401)) }
+ }
+ }
},
)
val chatClient = ChatClient(realtimeClient) {
diff --git a/src/pages/docs/auth/basic.mdx b/src/pages/docs/platform/auth/basic.mdx
similarity index 91%
rename from src/pages/docs/auth/basic.mdx
rename to src/pages/docs/platform/auth/basic.mdx
index bdcc9a6b02..103112855e 100644
--- a/src/pages/docs/auth/basic.mdx
+++ b/src/pages/docs/platform/auth/basic.mdx
@@ -1,9 +1,11 @@
---
title: Basic auth
meta_description: "Basic authentication allows you to authenticate a secure server using an Ably API key and secret."
+redirect_from:
+ - /docs/auth/basic
---
-Basic authentication is the simplest way to authenticate with Ably. It requires passing an [API key](/docs/auth#api-key) when instancing an SDK.
+Basic authentication is the simplest way to authenticate with Ably. It requires passing an [API key](/docs/platform/auth#api-key) when instancing an SDK.
@@ -285,44 +269,44 @@ final tokenRequest = await realtime.auth.requestToken(tokenParams: tokenParams);
If a set of capabilities are requested, then the Ably Token will be assigned the intersection of the requested capability and the capability of the issuing key.
-Using the following example, an API key exists with the listed capabilities. If an [Ably Token](/docs/auth/token#tokens) is requested and specifies a set of capabilities, then the resulting token will only receive those capabilities that intersect. The capabilities of a token cannot exceed those of the issuing API key.
+Using the following example, an API key exists with the listed capabilities. If an [Ably Token](/docs/platform/auth/token#tokens) is requested and specifies a set of capabilities, then the resulting token will only receive those capabilities that intersect. The capabilities of a token cannot exceed those of the issuing API key.
```javascript
// API key capabilities:
{
- 'chat:*': ['publish', 'subscribe', 'presence'],
- 'status': ['subscribe', 'history'],
+ 'your-namespace:*': ['publish', 'subscribe', 'presence'],
+ 'notifications': ['subscribe', 'history'],
'alerts': ['subscribe']
}
// Token request that specifies capabilities:
const tokenDetails = await auth.requestToken({ capability: {
- 'chat:bob': ['subscribe'], // only 'subscribe' intersects
- 'status': ['*'], // '*'' intersects with 'subscribe'
- 'secret': ['publish', 'subscribe'] // key does not have access to 'secret' channel
+ 'your-namespace:user-123': ['subscribe'], // only 'subscribe' intersects
+ 'notifications': ['*'], // '*'' intersects with 'subscribe'
+ 'private': ['publish', 'subscribe'] // key does not have access to 'private' channel
}});
// Resulting token capabilities:
{
- 'chat:bob': ['subscribe'],
- 'status': ['subscribe', 'history']
+ 'your-namespace:user-123': ['subscribe'],
+ 'notifications': ['subscribe', 'history']
}
```
```python
# API key capabilities:
# {
-# "chat:*": ["publish", "subscribe", "presence"],
-# "status": ["subscribe", "history"],
+# "your-namespace:*": ["publish", "subscribe", "presence"],
+# "notifications": ["subscribe", "history"],
# "alerts": ["subscribe"]
# }
# Token request that specifies capabilities:
capabilities = {
- "chat:bob": ["subscribe"], # only "subscribe" intersects
- "status": ["*"], # "*" intersects with "subscribe"
- "secret": ["publish", "subscribe"] # key does not have access to "secret" channel
+ "your-namespace:user-123": ["subscribe"], # only "subscribe" intersects
+ "notifications": ["*"], # "*" intersects with "subscribe"
+ "private": ["publish", "subscribe"] # key does not have access to "private" channel
}
token_details = await ably_rest.auth.request_token({
@@ -331,48 +315,16 @@ token_details = await ably_rest.auth.request_token({
# Resulting token capabilities:
# {
-# "chat:bob": ["subscribe"],
-# "status": ["subscribe", "history"]
+# "your-namespace:user-123": ["subscribe"],
+# "notifications": ["subscribe", "history"]
# }
```
-```php
-/**
- * API key capabilities:
- * {
- * 'chat:*': ['publish', 'subscribe', 'presence'],
- * 'status': ['subscribe', 'history'],
- * 'alerts': ['subscribe']
- * }
- */
-
-// Token request that specifies capabilities:
-$capabilities = [
- 'chat:bob' => ['subscribe'], // only 'subscribe' intersects
- 'status' => ['*'], // '*' intersects with 'subscribe'
- 'secret' => ['publish', 'subscribe'] // key does not have access to 'secret' channel
-];
-
-$tokenDetails = $rest
- ->auth
- ->requestToken(
- ['capability' => json_encode($capabilities)]
- );
-
-/**
- * Resulting token capabilities:
- * {
- * 'chat:bob': ['subscribe'],
- * 'status': ['subscribe', 'history']
- * }
- */
-```
-
```go
// API key capabilities:
// {
-// "chat:*": ["publish", "subscribe", "presence"],
-// "status": ["subscribe", "history"],
+// "your-namespace:*": ["publish", "subscribe", "presence"],
+// "notifications": ["subscribe", "history"],
// "alerts": ["subscribe"]
// }
@@ -382,9 +334,9 @@ rest, _ := ably.NewREST(
// Define the capabilities
capabilities := map[string][]string{
- "chat:bob": {"subscribe"},
- "status": {"*"},
- "secret": {"publish", "subscribe"},
+ "your-namespace:user-123": {"subscribe"},
+ "notifications": {"*"},
+ "private": {"publish", "subscribe"},
}
capabilitiesJSON, err := json.Marshal(capabilities)
@@ -405,34 +357,8 @@ if err != nil {
// Resulting token capabilities:
// {
-// "chat:bob": ["subscribe"],
-// "status": ["subscribe", "history"]
-// }
-```
-
-```flutter
-// API key capabilities:
-// {
-// 'chat:bob': ['subscribe'],
-// 'status': ['*'],
-// 'secret': ['publish', 'subscribe']
-// }
-
-final tokenParams = ably.TokenParams(
- capability: jsonEncode({
- 'chat:bob': ['subscribe'],
- 'status': ['*'],
- 'secret': ['publish', 'subscribe']
- }),
-);
-
-final tokenDetails = await rest.auth.requestToken(tokenParams: tokenParams);
-
-// Resulting token capabilities:
-// {
-// 'chat:bob': ['subscribe'],
-// 'secret': ['publish','subscribe']
-// 'status': ['subscribe', 'history']
+// "your-namespace:user-123": ["subscribe"],
+// "notifications": ["subscribe", "history"]
// }
```
@@ -441,56 +367,38 @@ final tokenDetails = await rest.auth.requestToken(tokenParams: tokenParams);
If a set of capabilities are requested, and the intersection between those and the API key's capabilities is empty, then the `TokenRequest` will result in an error.
-Using the following example, an API key exists with the listed capabilities. If an [Ably Token](/docs/auth/token#tokens) is requested that specifies a set of capabilities, and there is no intersection between the capabilities of the issuing API key and requested token, then the token request will be rejected. In the following example, the callback will be returned with an error.
+Using the following example, an API key exists with the listed capabilities. If an [Ably Token](/docs/platform/auth/token#tokens) is requested that specifies a set of capabilities, and there is no intersection between the capabilities of the issuing API key and requested token, then the token request will be rejected. In the following example, the callback will be returned with an error.
```javascript
// API key capabilities:
{
- 'chat': ['*']
+ 'your-namespace': ['*']
}
// Token request that specifies capabilities:
const tokenDetails = await auth.requestToken({ capability: {
- 'status': ['*']
+ 'other-namespace': ['*']
}});
```
```python
# API key capabilities:
# {
-# "chat": ["*"]
+# "your-namespace": ["*"]
# }
token_details = await ably_rest.auth.request_token({
'capability': json.dumps({
- {
- "status": ["*"]
- }
+ "other-namespace": ["*"]
})
})
```
-```php
-/**
- * API key capabilities:
- * {
- * 'chat': ['*']
- * }
- */
-
-// Token request that specifies capabilities:
-$tokenDetails = $rest
- ->auth
- ->requestToken(
- ['capability' => json_encode(['status' => ['*']])]
- );
-```
-
```go
// API key capabilities:
// {
-// "chat": ["*"]
+// "your-namespace": ["*"]
// }
rest, _ := ably.NewREST(
@@ -498,7 +406,7 @@ rest, _ := ably.NewREST(
// Define the capabilities
capabilities := map[string][]string{
- "status": {"*"},
+ "other-namespace": {"*"},
}
capabilitiesJSON, err := json.Marshal(capabilities)
@@ -517,31 +425,21 @@ if err != nil {
log.Fatalf("Failed to request token: %v", err)
}
```
-
-```flutter
-// API key capabilities:
-// {
-// 'status': ['*']
-// }
-final tokenParams = ably.TokenParams(
- capability: jsonEncode({
- 'status': ['*']
- }),
-);
-
-final tokenDetails = await realtime.auth.requestToken(tokenParams: tokenParams);
-```
#### Ably JWT capability determination
-Capabilities are determined for [Ably JWTs](/docs/auth/token#jwt) in the following way:
+Capabilities are determined for [Ably JWTs](/docs/platform/auth/token#jwt) in the following way:
* The capabilities granted to an Ably JWT will be the intersection of the capabilities within the Ably JWT and the capabilities of the associated API key.
* If the set of capabilities within the Ably JWT have no intersection with the capabilities of the API key, then an error will instead be returned.
## Custom restrictions on channels
+
+
It is possible for JWTs to contain authenticated claims for users that can be used to allow or disallow certain interactions in your channels.
Messages can be annotated with trusted metadata copied from the client's authentication token by Ably servers. Clients are unable to directly publish messages with user claim metadata, and claims contained within the authentication token are signed to prevent tampering. Claims can be scoped to individual channels or to namespaces of [channels](/docs/channels). The most specific user claim will be added to the message as part of the `extras` object. Note that this does not apply to presence or metadata messages.
@@ -555,8 +453,8 @@ To set the trusted fields you need to include `ably.channel.*` in your JWT authe
'name': 'John Doe',
'x-ably-capability': <...>,
'x-ably-clientId': <...>,
- 'ably.channel.chat1': 'admin', // the user is an admin for the chat1 channel
- 'ably.channel.chat:*': 'moderator', // the user is a moderator in channels within the chat namespace
+ 'ably.channel.your-channel': 'admin', // the user is an admin for a specific channel
+ 'ably.channel.your-namespace:*': 'moderator', // the user is a moderator in all channels within a namespace
'ably.channel.*': 'guest', // the user is a guest in all other channels
}
```
@@ -567,47 +465,23 @@ claims = {
"name": "John Doe",
"x-ably-capability": "<...>",
"x-ably-clientId": "<...>",
- "ably.channel.chat1": "admin", # the user is an admin for the chat1 channel
- "ably.channel.chat:*": "moderator", # the user is a moderator in channels within the chat namespace
+ "ably.channel.your-channel": "admin", # the user is an admin for a specific channel
+ "ably.channel.your-namespace:*": "moderator", # the user is a moderator in all channels within a namespace
"ably.channel.*": "guest" # the user is a guest in all other channels
}
```
-```php
-$claims = [
- 'sub' => '1234567890',
- 'name' => 'John Doe',
- 'x-ably-capability' => '<...>',
- 'x-ably-clientId' => '<...>',
- 'ably.channel.chat1' => 'admin', // the user is an admin for the chat1 channel
- 'ably.channel.chat =>*' => 'moderator', // the user is a moderator in channels within the chat namespace
- 'ably.channel.*' => 'guest' // the user is a guest in all other channels
-];
-```
-
```go
claims := map[string]interface{}{
- "sub": "1234567890",
- "name": "John Doe",
- "x-ably-capability": "<...>",
- "x-ably-clientId": "<...>",
- "ably.channel.chat1": "admin",
- "ably.channel.chat:*": "moderator",
- "ably.channel.*": "guest",
+ "sub": "1234567890",
+ "name": "John Doe",
+ "x-ably-capability": "<...>",
+ "x-ably-clientId": "<...>",
+ "ably.channel.your-channel": "admin",
+ "ably.channel.your-namespace:*": "moderator",
+ "ably.channel.*": "guest",
}
```
-
-```flutter
-final claims = {
- 'sub': '1234567890',
- 'name': 'John Doe',
- 'x-ably-capability': '<...>',
- 'x-ably-clientId': '<...>',
- 'ably.channel.chat1': 'admin',
- 'ably.channel.chat:*': 'moderator',
- 'ably.channel.*': 'guest',
-};
-```
The claims from the token are copied into messages, allowing them to be checked for permission:
@@ -638,17 +512,14 @@ func fromModerator(message map[string]interface{}) bool {
return false
}
```
-
-```flutter
-bool fromModerator(Message message) {
- final userClaim = message.extras['userClaim'];
- return userClaim != null && userClaim == 'moderator';
-}
-```
## Using JWT for per connection publish rate limits
+
+
JWTs may specify publish rate limits for a user on particular channels. These limits can be used to prevent any individual user from sending an excessive number of messages in a short period of time.
An example use case is in a large live chat where you may wish to limit users to posting messages no more than once every 10 seconds.
@@ -666,8 +537,8 @@ The following is an example of setting different rate limits for different chann
'name': 'John Doe',
'x-ably-capability': <...>,
'x-ably-clientId': <...>,
- 'ably.limits.publish.perAttachment.maxRate.chat1': 10, // the user can publish 10 messages per second in channel chat1
- 'ably.limits.publish.perAttachment.maxRate.chat:*': 0.1 // the user can publish a message every 10 seconds in all channels within the chat namespace
+ 'ably.limits.publish.perAttachment.maxRate.your-channel': 10, // the user can publish 10 messages per second in a specific channel
+ 'ably.limits.publish.perAttachment.maxRate.your-namespace:*': 0.1 // the user can publish a message every 10 seconds in all channels within a namespace
}
```
@@ -677,41 +548,19 @@ claims = {
"name": "John Doe",
"x-ably-capability": "<...>",
"x-ably-clientId": "<...>",
- "ably.limits.publish.perAttachment.maxRate.chat1": 10, # the user can publish 10 messages per second in channel chat1
- "ably.limits.publish.perAttachment.maxRate.chat:*": 0.1 # the user can publish a message every 10 seconds in all channels within the chat namespace
+ "ably.limits.publish.perAttachment.maxRate.your-channel": 10, # the user can publish 10 messages per second in a specific channel
+ "ably.limits.publish.perAttachment.maxRate.your-namespace:*": 0.1 # the user can publish a message every 10 seconds in all channels within a namespace
}
```
-```php
-$claims = [
- 'sub' => '1234567890',
- 'name' => 'John Doe',
- 'x-ably-capability' => '<...>',
- 'x-ably-clientId' => '<...>',
- 'ably.limits.publish.perAttachment.maxRate.chat1' => 10, // the user can publish 10 messages per second in channel chat1
- 'ably.limits.publish.perAttachment.maxRate.chat:*' => 0.1 // the user can publish a message every 10 seconds in all channels within the chat namespace
-]
-```
-
```go
claims := map[string]interface{}{
"sub": "1234567890",
"name": "John Doe",
"x-ably-capability": "<...>",
"x-ably-clientId": "<...>",
- "ably.limits.publish.perAttachment.maxRate.chat1": 10.0, // the user can publish 10 messages per second in channel chat1
- "ably.limits.publish.perAttachment.maxRate.chat:*": 0.1, // the user can publish a message every 10 seconds in all channels within the chat namespace
+ "ably.limits.publish.perAttachment.maxRate.your-channel": 10.0, // the user can publish 10 messages per second in a specific channel
+ "ably.limits.publish.perAttachment.maxRate.your-namespace:*": 0.1, // the user can publish a message every 10 seconds in all channels within a namespace
}
```
-
-```flutter
-final claims = {
- 'sub': '1234567890',
- 'name': 'John Doe',
- 'x-ably-capability': '<...>', // Replace with actual capability
- 'x-ably-clientId': '<...>', // Replace with actual client ID
- 'ably.limits.publish.perAttachment.maxRate.chat1': 10, // the user can publish 10 messages per second in channel chat1
- 'ably.limits.publish.perAttachment.maxRate.chat:*': 0.1 // the user can publish a message every 10 seconds in all channels within the chat namespace
-};
-```
diff --git a/src/pages/docs/platform/auth/identified-clients.mdx b/src/pages/docs/platform/auth/identified-clients.mdx
new file mode 100644
index 0000000000..e4ba7b8f89
--- /dev/null
+++ b/src/pages/docs/platform/auth/identified-clients.mdx
@@ -0,0 +1,100 @@
+---
+title: Identified clients
+meta_description: "Clients can be allocated a client ID to help control their operations and interactions with Ably channels."
+redirect_from:
+ - /docs/auth/identified-clients
+---
+
+When a client is authenticated and connected to Ably, it is considered to be an authenticated client. While an authenticated client has a means to authenticate with Ably, they do not necessarily have an identity.
+
+When a client is assigned a trusted identity, that is, a `clientId`, then they are considered to be an identified client. For all operations that client performs with the Ably service, their `clientId` field will be automatically populated and can be trusted by other clients.
+
+For example, assume you are building a chat application and want to allow clients to publish messages and be present on a channel. If each client is assigned a trusted identity by your server, such as a unique email address or UUID, then all other subscribed clients can trust any messages or presence events they receive in the channel as being from that client. No other clients are permitted to assume a `clientId` that they are not assigned in their Ably-compatible token. They are unable to masquerade as another `clientId`.
+
+## ClientId immutability
+
+A connection's `clientId`, once set, is immutable. You cannot change a `clientId` after it has been established for a connection.
+
+### Late initialization
+
+It is possible to have late initialization of `clientId` in certain scenarios:
+
+* AuthCallback discovery: When using `authCallback`, the `clientId` may only become known when the first authCallback completes and returns a token with a `clientId`.
+* Opaque token processing: When the library receives an opaque token string, it only learns the `clientId` when it receives a response from the server that has processed the token.
+
+In both cases, the immutability requirement is satisfied because the `clientId` gets set as part of the client's first interaction with the server. Once this initial authentication is complete, the `clientId` cannot be changed for that connection.
+
+## Assign a clientId
+
+There are three different ways a client can be identified with using a `clientId`:
+
+* A client claims a `clientId` when authenticating with an API key.
+* A client is authenticating with a token issued for a specific `clientId`.
+* A client claims a `clientId` when authenticating with a token that is issued for a wildcard `clientId`.
+
+When a client sets their own ID there is the possibility that they can pretend to be someone else. To prevent this, it is recommended that you embed a `clientId` in the token issued to your clients from your auth server. This ensures that the `clientId` is set by your auth server, and eliminates the chance of a client pretending to be someone else.
+
+
+
+### Basic auth
+
+You can use [basic authentication](/docs/platform/auth/basic) to allow a client to claim any `clientId` when they authenticate with Ably. As the assignation of the `clientId` is not handled by a server, it cannot be trusted to represent the genuine identity of the client.
+
+### Token auth
+
+You can use [token authentication](/docs/platform/auth/token) to set an explicit `clientId` when creating or issuing a token. Clients using that token are restricted to operations for only that `clientId`, and all operations will implicitly contain that `clientId`.
+
+For example, when publishing a message, the `clientId` attribute of the message will be pre-populated with that `clientId`. Entering presence will also implicitly use that `clientId`.
+
+#### Using JWT (recommended)
+
+The recommended approach is to create a JWT with the `x-ably-clientId` claim. This requires no Ably SDK on your server—any JWT library will work:
+
+
+```javascript
+const ablyJwt = jwt.sign(
+ {
+ 'x-ably-capability': JSON.stringify({ '*': ['*'] }),
+ 'x-ably-clientId': userId, // Set the trusted clientId
+ },
+ keySecret,
+ { algorithm: 'HS256', keyid: keyName, expiresIn: '1h' }
+);
+```
+
+
+See [token authentication scenarios](/docs/platform/auth/token#scenario-standard) for complete server and client examples.
+
+#### Using TokenRequest (alternative)
+
+If JWT is not suitable for your use case, you can use the Ably SDK to create a TokenRequest with an explicit `clientId`:
+
+
+```javascript
+// Server-side with Ably SDK
+const ably = new Ably.Rest(process.env.ABLY_API_KEY);
+const tokenRequest = await ably.auth.createTokenRequest({ clientId: 'user-123' });
+// Return tokenRequest to client
+```
+
+```python
+# Server-side with Ably SDK
+ably = AblyRest(os.environ['ABLY_API_KEY'])
+token_request = await ably.auth.create_token_request({'client_id': 'user-123'})
+# Return token_request to client
+```
+
+
+See [token authentication](/docs/platform/auth/token#token-request) for complete TokenRequest examples in all languages.
+
+### Wildcard token auth
+
+You can use [token authentication](/docs/platform/auth/token) to set a wildcard `clientId` using a value of `*` when creating a token. Clients are then able to assume any identity in their operations, such as when publishing a message or entering presence.
+
+## Unidentified clients
+
+If no `clientId` is provided when using [token authentication](/docs/platform/auth/token) then clients are not permitted to assume an identity and will be considered an unidentified client in all operations. Messages published will contain no `clientId` and those clients will not be permitted to enter the [presence](/docs/presence-occupancy/presence) set.
diff --git a/src/pages/docs/platform/auth/index.mdx b/src/pages/docs/platform/auth/index.mdx
new file mode 100644
index 0000000000..6ed83ed957
--- /dev/null
+++ b/src/pages/docs/platform/auth/index.mdx
@@ -0,0 +1,108 @@
+---
+title: Authentication overview
+meta_description: "Ably supports two main authentication schemes: basic authentication and token authentication. Token authentication can be implemented using JWTs, Ably tokens, and Ably token requests."
+redirect_from:
+ - /docs/auth
+ - /docs/rest/authentication
+ - /docs/rest/versions/v1.1/authentication
+ - /docs/rest/versions/v1.0/authentication
+ - /docs/rest/versions/v0.8/authentication
+ - /docs/realtime/authentication
+ - /docs/realtime/versions/v1.1/authentication
+ - /docs/realtime/versions/v1.0/authentication
+ - /docs/realtime/versions/v0.8/authentication
+ - /docs/core-features/authentication
+ - /docs/core-features/versions/v1.1/authentication
+ - /docs/core-features/versions/v1.0/authentication
+ - /docs/core-features/versions/v0.8/authentication
+ - /docs/core-features/authentication.ably-jwt
+ - /docs/general/authentication
+ - /docs/general/_authentication_capabilities
+ - /docs/ids-and-keys
+---
+
+Before a client or server can issue requests to Ably, such as subscribe to channels, or publish messages, it must authenticate with Ably.
+
+## Recommended authentication
+
+| Environment | Recommended Method | Details |
+|-------------|-------------------|---------|
+| **Client-side** (browsers, mobile apps) | [Token authentication](/docs/platform/auth/token) | Your server creates tokens; clients use `authCallback` to fetch them |
+| **Server-side** (Node.js, Python, etc.) | [Basic authentication](/docs/platform/auth/basic) | Use your API key directly in trusted environments |
+
+
+
+### Quick start
+
+
+```javascript
+// Client-side: fetch token from your server
+const realtime = new Ably.Realtime({
+ authCallback: async (tokenParams, callback) => {
+ const response = await fetch('/api/ably-token');
+ callback(null, await response.text());
+ },
+});
+```
+
+
+See [token authentication](/docs/platform/auth/token#scenario-standard) for complete server and client examples.
+
+## Authentication terminology
+
+- **Authentication**: The process of verifying credentials (API key or token) to allow interaction with Ably.
+- **Authorization**: Determining what operations an authenticated client can perform, based on [capabilities](/docs/platform/auth/capabilities).
+- **Identified client**: A client with a verified identity (`clientId`). See [identified clients](/docs/platform/auth/identified-clients).
+
+## Ably API keys
+
+Every Ably app can have one or more API keys associated with it. API keys authenticate directly with Ably or issue tokens. Keys can have different [capabilities](/docs/platform/auth/capabilities), and tokens issued from a key can only have a subset of those capabilities.
+
+### API key format
+
+An Ably API key string has the following format: `I2E_JQ.OqUdfg:EVKVTCBlzLBPYJiCZTsIW_pqylJ9WVRB5K9P19Ap1y0`
+
+The API key has three parts:
+
+1. `I2E_JQ` - the public app ID
+2. `OqUdfg` - the public key ID (`I2E_JQ.OqUdfg` together form the public API key ID)
+3. `EVKVTCBlzLBPYJiCZTsIW_pqylJ9WVRB5K9P19Ap1y0` - the API key secret (never share this)
+
+### Create an API key
+
+API keys are created in the [Ably dashboard](https://ably.com/dashboard) or programmatically via the [Control API](/docs/platform/account/control-api).
+
+To create an API key in the dashboard:
+
+1. Click the **API Keys** tab in your [dashboard](https://ably.com/dashboard).
+2. Click **Create a new API key**.
+3. Enter a name to identify the key.
+4. Select the [capabilities](/docs/platform/auth/capabilities) to apply.
+5. Optionally enable [token revocation](/docs/platform/auth/revocation).
+6. Optionally restrict scope to specific channels or queues.
+
+
+
+## Selecting an authentication mechanism
+
+Ably supports two authentication mechanisms:
+
+1. **[Token authentication](/docs/platform/auth/token)**: Short-lived tokens that expire and can be revoked. Recommended for clients.
+2. **[Basic authentication](/docs/platform/auth/basic)**: Uses your API key directly. Use only on trusted servers.
+
+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.
+
+| Scenario | Basic | Token | Recommendation |
+|----------|:-----:|:-----:|----------------|
+| Script may be exposed (browser, mobile) | No | Yes | Use token auth—don't embed API keys in client code |
+| Connection may be insecure | No | Yes | Use token auth to limit exposure window |
+| No server to control access | Yes | No | Basic auth is your only option |
+| Fine-grained access control needed | No | Yes | Token auth allows per-user [capabilities](/docs/platform/auth/capabilities) |
+| Time-limited or revocable access | No | Yes | Tokens expire and can be [revoked](/docs/platform/auth/revocation) |
+| Users must be identified | Partial | Yes | Token auth lets your server set trusted [clientIds](/docs/platform/auth/identified-clients) |
diff --git a/src/pages/docs/auth/revocation.mdx b/src/pages/docs/platform/auth/revocation.mdx
similarity index 97%
rename from src/pages/docs/auth/revocation.mdx
rename to src/pages/docs/platform/auth/revocation.mdx
index dad67730ee..bc98609a21 100644
--- a/src/pages/docs/auth/revocation.mdx
+++ b/src/pages/docs/platform/auth/revocation.mdx
@@ -1,6 +1,8 @@
---
title: Token revocation
meta_description: "Token revocation is a mechanism that enables an app to invalidate authentication tokens."
+redirect_from:
+ - /docs/auth/revocation
---
Token revocation is a mechanism that enables an app to invalidate authentication tokens. This invalidation can be used to force specified clients to re-obtain a token, and subsequently enables the app to modify the rights granted to clients, or to decline to re-issue a token. Token revocation can be enforced immediately or postponed by 30 seconds, allowing the client the opportunity to request a new token (see `allowReauthMargin` under [Revoke a token](#revoke)). By default `allowReauthMargin` is set to false, meaning token revocation is near immediate. Setting this value to true would postpone the revocation by 30 seconds.
@@ -233,7 +235,7 @@ In this example, all users that have been assigned the revocation key `users.gro
### channel
-This target specifier will match tokens that exactly match one of the resource names present in the token [capabilities](/docs/auth/capabilities). Note that this is not the same thing as revoking all tokens that have access to the channel.
+This target specifier will match tokens that exactly match one of the resource names present in the token [capabilities](/docs/platform/auth/capabilities). Note that this is not the same thing as revoking all tokens that have access to the channel.
For example, a token with a capability of `{"foo:*": ["*"]}` will be revoked by a target of `channel:foo:*`, but a revocation to `channel:*:*` will have no effect (even though that is a superset of the capabilities of `foo:*`), and nor will `channel:foo:bar` (even for connections using the token to attach to that particular channel). It must be the exact string used in the token capabilities (which may be inherited from key capabilities).
diff --git a/src/pages/docs/auth/token.mdx b/src/pages/docs/platform/auth/token.mdx
similarity index 75%
rename from src/pages/docs/auth/token.mdx
rename to src/pages/docs/platform/auth/token.mdx
index 912d469dfe..e7bf1c7051 100644
--- a/src/pages/docs/auth/token.mdx
+++ b/src/pages/docs/platform/auth/token.mdx
@@ -1,11 +1,23 @@
---
title: "Token auth"
meta_description: "Token authentication allows clients to authenticate with Ably, without exposing the Ably API key and secret."
+redirect_from:
+ - /docs/auth/token
---
-Token authentication uses a trusted device with an [API key](/docs/auth#api-key) to issue time-limited tokens to untrusted clients. Tokens have a limited set of access rights, known as [capabilities](/docs/auth/capabilities), and can have a specific [identity](/docs/auth/identified-clients) using a `clientId`.
+Token authentication uses a trusted device with an [API key](/docs/platform/auth#api-key) to issue time-limited tokens to untrusted clients. Tokens have a limited set of access rights, known as [capabilities](/docs/platform/auth/capabilities), and can have a specific [identity](/docs/platform/auth/identified-clients) using a `clientId`.
-Token authentication is the recommended authentication method to use client-side as it provides more fine-grained access control and limits the risk of credentials being exposed.
+Token authentication is the recommended authentication method to use client-side for the following reasons:
+
+* Tokens ensure that an Ably API key isn't exposed in client applications.
+* Tokens are short-lived so there is only a short period of time during which a compromised token can be used.
+* Tokens provide more fine-grained access control, which also limits the area of exposure a compromised token can access.
+
+
## Access restrictions
@@ -35,6 +47,49 @@ Providing a literal `token` or `tokenDetails` is typically used for testing: sin
Authentication using tokens can be achieved requesting and issuing [Ably Tokens](#tokens) or passing a [JSON Web Tokens (JWT)](#jwt) to the Ably service.
+## Choosing a token mechanism
+
+Ably supports three token authentication mechanisms. **Ably JWT is recommended for most use cases** as it offers the best combination of flexibility, performance, and features.
+
+### Ably JWT (recommended)
+
+Best for most applications. Your server creates a JWT signed with your Ably API key—no SDK required. Supports channel-scoped claims and per-connection rate limits.
+
+See [implementation details](#standard).
+
+### TokenRequest
+
+Your server creates a signed `TokenRequest` using the Ably SDK. The client exchanges this with Ably for an actual token. Choose when your capability list is very large, or you need to keep capabilities confidential.
+
+See [implementation details](#token-request).
+
+### Embedded Token JWT
+
+Wrap an Ably token inside your existing JWT structure for deep integration with existing auth systems.
+
+See [implementation details](#embedded).
+
+## Token mechanism comparison
+
+| Feature | Ably JWT (Recommended) | Embedded JWT | TokenRequest |
+|---------|:----------------------:|:------------:|:------------:|
+| **Server SDK required** | No | Yes | Yes |
+| **Client round-trip to Ably** | No | No | Yes |
+| **Channel-scoped claims** | Yes | No | No |
+| **Per-connection rate limits** | Yes | No | No |
+| Works with any MQTT client | Yes | Yes | Yes |
+| Automatic SDK token refresh | Yes | Yes | Yes |
+| Implementation complexity | Medium | Higher | Low |
+| Works in serverless/edge | Yes (stateless) | Yes (with SDK) | Yes (with SDK) |
+
+
+
## Token refresh
One of the important benefits of using an Ably SDK is that the automatic refresh of tokens will be handled for you.
@@ -45,14 +100,13 @@ To use automatic refresh of tokens, provide either an `authUrl` or an `authCallb
Understanding how token authentication works helps clarify why automatic renewal is essential:
-1. Your server uses the Ably API key to request a 'Token Request' object from Ably.
-2. Your client uses this 'Token Request' object to request the actual token from the Ably server every time it needs to authenticate.
-3. These tokens are short-lived and expire after a certain period of time.
-4. The client SDK automatically requests a new token just before the previous one expires, ensuring the connection never drops due to authentication failure.
+1. Your client calls `authUrl` or `authCallback` to request a token from your server.
+2. Your server validates the client and returns a token (JWT or TokenRequest).
+3. The client uses this token to authenticate with Ably.
+4. Tokens are short-lived and expire after a set period.
+5. The client SDK automatically requests a new token before expiry, ensuring uninterrupted connectivity.
-Using an `authUrl` or `authCallback` ensures your client automatically requests a new token when needed, making the authentication process seamless and preventing service interruptions.
-
-An `authURL` is recommended for use with web-based clients as they can easily utilize cookies and other web-only features. For non-web clients, `authCallback` is the recommended strategy.
+An `authURL` is recommended for web-based clients as they can utilize cookies. For non-web clients, `authCallback` is the recommended strategy.
### Token TTL limits
@@ -60,7 +114,7 @@ Ably enforces maximum TTL (time-to-live) limits on different types of tokens:
- Access tokens: Maximum TTL of 24 hours.
- Device tokens (for push notifications): Maximum TTL of 5 years.
-- Revocable tokens: Maximum TTL of 1 hour (when [token revocation](/docs/auth/revocation#revocable-tokens) is enabled).
+- Revocable tokens: Maximum TTL of 1 hour (when [token revocation](/docs/platform/auth/revocation#revocable-tokens) is enabled).