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.