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/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/capabilities.mdx b/src/pages/docs/auth/capabilities.mdx index 34a9d2f663..90b7ba396c 100644 --- a/src/pages/docs/auth/capabilities.mdx +++ b/src/pages/docs/auth/capabilities.mdx @@ -103,68 +103,87 @@ Ably Tokens and JWTs are issued from an existing API key and their capabilities If an API key must be shared with a third party, then it is recommended that [the principle of least privilege](https://en.wikipedia.org/wiki/Principle_of_least_privilege) is considered, assigning only the capabilities needed by that third party. Thus, any Ably requests authenticated using that API key or Ably-compatible tokens associated with that API key, will be restricted to the capabilities assigned to the API key. -Capabilities can be set when creating a token or token request, as shown in the following example: +#### Using JWT (recommended) + +Set capabilities in a JWT using the `x-ably-capability` claim. No Ably SDK is required—any JWT library will work: ```javascript - var tokenParams = { clientId: 'foo', capability: JSON.stringify(capability) }; - const tokenRequest = await ably.auth.createTokenRequest(tokenParams); -``` +// Server-side: Create JWT with capabilities using any JWT library +import jwt from 'jsonwebtoken'; -```python -token_params = { - 'client_id': 'foo', - 'capability': json.dumps(capability) -} +const [keyName, keySecret] = process.env.ABLY_API_KEY.split(':'); -token_request = await ably_rest.auth.create_token_request(token_params) +const ablyJwt = jwt.sign( + { + 'x-ably-capability': JSON.stringify({ + 'your-namespace:*': ['publish', 'subscribe', 'presence'], + 'notifications': ['subscribe'], + }), + 'x-ably-clientId': userId, + }, + keySecret, + { algorithm: 'HS256', keyid: keyName, expiresIn: '1h' } +); ``` -```php -$tokenParams = [ - 'clientId' => 'client@example.com', - 'capability' => json_encode($capability) -]; -$tokenRequest = $rest->auth->createTokenRequest($tokenParams); +```python +# Server-side: Create JWT with capabilities using any JWT library +import jwt +import json +import time +import os + +key_name, key_secret = os.environ['ABLY_API_KEY'].split(':') + +now = int(time.time()) +ably_jwt = jwt.encode( + { + 'iat': now, + 'exp': now + 3600, + 'x-ably-capability': json.dumps({ + 'your-namespace:*': ['publish', 'subscribe', 'presence'], + 'notifications': ['subscribe'], + }), + 'x-ably-clientId': user_id, + }, + key_secret, + algorithm='HS256', + headers={'kid': key_name} +) ``` + -```go -rest, _ := ably.NewREST( - ably.WithKey("xVLyHw.I2jW-g:dOTlhEt-nIubVAPMAUJnGv-_F8BZ7xNYnXdajpGaISg")) +#### Using TokenRequest (alternative) -// Define the capability -capability := map[string][]string{ - "*": {"*"}, -} +If JWT is not suitable, use the Ably SDK to create a TokenRequest with capabilities: -capabilityJSON, err := json.Marshal(capability) -if err != nil { - log.Fatalf("Failed to marshal capability: %v", err) -} - -// Define the token parameters -tokenParams := &ably.TokenParams{ - ClientID: "foo", - Capability: string(capabilityJSON), -} - -// Create a token request -tokenRequest, err := rest.Auth.CreateTokenRequest(tokenParams) -if err != nil { - log.Fatalf("Failed to create token request: %v", err) -} + +```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', + capability: JSON.stringify({ + 'your-namespace:*': ['publish', 'subscribe'], + }), +}); ``` -```flutter -final tokenParams = ably.TokenParams( - clientId: 'foo', - capability: jsonEncode(capability), -); - -final tokenRequest = await rest.auth.createTokenRequest(tokenParams: tokenParams); +```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', + 'capability': json.dumps({ + 'your-namespace:*': ['publish', 'subscribe'], + }), +}) ``` +See [token authentication](/docs/auth/token#token-request) for complete TokenRequest examples in all languages. + ### Token capability determination The capabilities for tokens are determined based on those of the issuing API key and those requested by the token. @@ -179,8 +198,8 @@ Using the following example, an API key exists with the listed capabilities. If ```javascript // API key capabilities: { - 'chat': ['publish', 'subscribe', 'presence'], - 'status': ['subscribe'] + 'your-namespace': ['publish', 'subscribe', 'presence'], + 'notifications': ['subscribe'] } // Token request that doesn't specify any capabilities: @@ -188,19 +207,19 @@ Using the following example, an API key exists with the listed capabilities. If // Resulting token capabilities: { - 'chat': ['publish', 'subscribe', 'presence'], - 'status': ['subscribe'] + 'your-namespace': ['publish', 'subscribe', 'presence'], + 'notifications': ['subscribe'] } ``` ```python # API key capabilities: # { -# "chat": ["publish", "subscribe", "presence"], -# "status": ["subscribe"] +# "your-namespace": ["publish", "subscribe", "presence"], +# "notifications": ["subscribe"] # } -// Token request that doesn't specify any capabilities: +# Token request that doesn't specify any capabilities: token = await ably.auth.create_token_request( { "clientId": "client@example.com", @@ -209,37 +228,16 @@ token = await ably.auth.create_token_request( # Resulting token capabilities: # { -# "chat": ["publish", "subscribe", "presence"], -# "status": ["subscribe"] +# "your-namespace": ["publish", "subscribe", "presence"], +# "notifications": ["subscribe"] # } ``` -```php -// API key capabilities: -//{ -// 'chat': ['publish', 'subscribe', 'presence'], -// 'status': ['subscribe'] -//} - -// Token request that doesn't specify any capabilities: -$tokenParams = [ - 'clientId' => 'client@example.com', - 'ttl' => 3600 * 1000, // ms -]; -$tokenRequest = $rest->auth->requestToken($tokenParams); - -// Resulting token capabilities: -//{ -// 'chat': ['publish', 'subscribe', 'presence'], -// 'status': ['subscribe'] -//} -``` - ```go // API key capabilities: // { -// "chat": ["publish", "subscribe", "presence"], -// "status": ["subscribe"] +// "your-namespace": ["publish", "subscribe", "presence"], +// "notifications": ["subscribe"] // } rest, _ := ably.NewREST( @@ -259,24 +257,8 @@ if err != nil { // Resulting token capabilities: // { -// "chat": ["publish", "subscribe", "presence"], -// "status": ["subscribe"] -// } -``` - -```flutter -// API key capabilities: -// { -// 'chat': ['publish', 'subscribe', 'presence'], -// 'status': ['subscribe'] -// } - -final tokenRequest = await realtime.auth.requestToken(tokenParams: tokenParams); - -// Resulting token capabilities: -// { -// 'chat': ['publish', 'subscribe', 'presence'], -// 'status': ['subscribe'] +// "your-namespace": ["publish", "subscribe", "presence"], +// "notifications": ["subscribe"] // } ``` @@ -291,38 +273,38 @@ Using the following example, an API key exists with the listed capabilities. If ```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 +313,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 +332,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 +355,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"] // } ``` @@ -447,50 +371,32 @@ Using the following example, an API key exists with the listed capabilities. If ```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 +404,7 @@ rest, _ := ably.NewREST( // Define the capabilities capabilities := map[string][]string{ - "status": {"*"}, + "other-namespace": {"*"}, } capabilitiesJSON, err := json.Marshal(capabilities) @@ -517,20 +423,6 @@ 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 @@ -542,6 +434,10 @@ Capabilities are determined for [Ably JWTs](/docs/auth/token#jwt) in the followi ## 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 +451,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 +463,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 +510,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 +535,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 +546,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/auth/identified-clients.mdx b/src/pages/docs/auth/identified-clients.mdx index e4754fa566..df569cd835 100644 --- a/src/pages/docs/auth/identified-clients.mdx +++ b/src/pages/docs/auth/identified-clients.mdx @@ -48,167 +48,47 @@ You can use [token authentication](/docs/auth/token) to set an explicit `clientI 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`: +#### Using JWT (recommended) - -```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 ... */ -``` +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: -```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'), + +```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' } ); ``` + -```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 ... */ -``` +See [token authentication scenarios](/docs/auth/token#scenario-standard) for complete server and client examples. -```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 ... -``` +#### Using TokenRequest (alternative) -```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 ... - }]; -``` +If JWT is not suitable for your use case, you can use the Ably SDK to create a TokenRequest with an explicit `clientId`: -```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) + +```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 ``` -```rest_flutter -final rest = ably.Rest(options: ably.ClientOptions(key: '{{API_KEY}}')); -final tokenRequest = await rest.auth.createTokenRequest( - tokenParams: ably.TokenParams(clientId: 'Bob'), -); +```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/auth/token#token-request) for complete TokenRequest examples in all languages. + ### 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. diff --git a/src/pages/docs/auth/index.mdx b/src/pages/docs/auth/index.mdx index 51949e95e5..5d45e3c797 100644 --- a/src/pages/docs/auth/index.mdx +++ b/src/pages/docs/auth/index.mdx @@ -20,91 +20,88 @@ redirect_from: - /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. +Before a client or server can issue requests to Ably, such as subscribe to channels, or publish messages, it must authenticate with Ably. -## Authentication terminology +## Recommended authentication -The following terminology helps explain authentication, authorization, and identification in the context of the Ably service: +| Environment | Recommended Method | Details | +|-------------|-------------------|---------| +| **Client-side** (browsers, mobile apps) | [Token authentication](/docs/auth/token) | Your server creates tokens; clients use `authCallback` to fetch them | +| **Server-side** (Node.js, Python, etc.) | [Basic authentication](/docs/auth/basic) | Use your API key directly in trusted environments | -"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. +### Quick start -"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. + +```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()); + }, +}); +``` + -"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. +See [token authentication](/docs/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/auth/capabilities). +- **Identified client**: A client with a verified identity (`clientId`). See [identified clients](/docs/auth/identified-clients). ## 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. +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/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`. +An Ably API key string has the following format: `I2E_JQ.OqUdfg:EVKVTCBlzLBPYJiCZTsIW_pqylJ9WVRB5K9P19Ap1y0` -The API key is made up of three parts: +The API key has 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. +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). 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: +API keys are created in the [Ably dashboard](https://ably.com/dashboard) or programmatically via the [Control API](/docs/platform/account/control-api). -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. +To create an API key in the dashboard: -