# Token auth Token authentication uses a trusted device with an [API key](https://ably.com/docs/auth#api-key) to issue time-limited tokens to untrusted clients. Tokens have a limited set of access rights, known as [capabilities](https://ably.com/docs/auth/capabilities), and can have a specific [identity](https://ably.com/docs/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. ## Access restrictions ### Token-based client validation Token authentication enables you to validate client characteristics (such as origin, IP address, cookies, or any other client features) in your authentication server before issuing tokens. This provides flexible access control as you can implement any validation logic in your auth server as part of the token issuance process. ### API key restrictions For cases where token authentication is impractical, Ably can add origin or IP address restrictions directly to API keys. However, this approach has significant limitations: - Less flexible than token authentication. - Manual intervention required to modify restrictions. - Enterprise support packages only: contact [Ably support](https://ably.com/support) if interested. - Not a security boundary: origin headers can be easily spoofed, especially outside browser contexts. - Permissive fallback: requests with no origin header are allowed when origin restrictions are set. For maximum security and flexibility, token authentication with server-side validation is the recommended approach. Any of the following cause an SDK to use token authentication: * An [`authUrl`](https://ably.com/docs/api/realtime-sdk/types#client-options) or [`authCallback`](https://ably.com/docs/api/realtime-sdk/types#client-options) is provided that returns an Ably-compatible token or an Ably [`TokenRequest`](https://ably.com/docs/api/realtime-sdk/types#token-request). * [`useTokenAuth`](https://ably.com/docs/api/realtime-sdk/types#client-options) is true. * A [`token`](https://ably.com/docs/api/realtime-sdk/types#client-options) or [`tokenDetails`](https://ably.com/docs/api/realtime-sdk/types#client-options) property is provided. Providing a literal `token` or `tokenDetails` is typically used for testing: since tokens are short-lived, in production you typically want to use an authentication method that allows the client library to renew the token automatically before the current token expires. Authentication using tokens can be achieved requesting and issuing [Ably Tokens](#tokens) or passing a [JSON Web Tokens (JWT)](#jwt) to the Ably service. ## Token refresh One of the important benefits of using an Ably SDK is that the automatic refresh of tokens will be handled for you. To use automatic refresh of tokens, provide either an `authUrl` or an `authCallback`. When the token is near to expiry the `authUrl` or `authCallback` is invoked and a new token is automatically requested. ### Token authentication workflow 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. 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. ### Token TTL limits 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](https://ably.com/docs/auth/revocation#revocable-tokens) is enabled). ### authUrl You can specify an `authUrl` when you create the Ably client. For example: ```realtime_javascript const realtime = new Ably.Realtime({ authUrl: '/auth' }); ``` ```realtime_nodejs const realtime = new Ably.Realtime({ authUrl: '/auth' }); ``` ```realtime_ruby realtime = Ably::Realtime.new(auth_url: '/auth') ``` ```realtime_python realtime = AblyRealtime(auth_url='/auth') ``` ```realtime_java ClientOptions options = new ClientOptions(); options.authUrl = "/auth"; AblyRealtime realtime = new AblyRealtime(options); ``` ```realtime_objc ARTClientOptions *options = [[ARTClientOptions alloc] init]; options.authUrl = [NSURL URLWithString:@"/auth"]; ARTRealtime *realtime = [[ARTRealtime alloc] initWithOptions:options]; ``` ```realtime_swift let options = ARTClientOptions() options.authUrl = NSURL(string: "/auth") let realtime = ARTRealtime(options: options) ``` ```realtime_csharp ClientOptions options = new ClientOptions(); options.AuthUrl = new Uri("/auth"); AblyRealtime realtime = new AblyRealtime(options); ``` ```realtime_go client, err := ably.NewRealtime(ably.WithAuthURL("/auth")) ``` ```realtime_flutter final clientOptions = ably.ClientOptions( authUrl: '/auth' ); final realtime = ably.Realtime(options: clientOptions); ``` ```rest_javascript const rest = new Ably.Rest({ authUrl: '/auth' }); ``` ```rest_nodejs const rest = new Ably.Rest({ authUrl: '/auth' }); ``` ```rest_ruby rest = Ably::Rest.new(auth_url: '/auth') ``` ```rest_python rest = AblyRest(auth_url='/auth') ``` ```rest_php $rest = new Ably\AblyRest(['authUrl' => '/auth']); ``` ```rest_java ClientOptions options = new ClientOptions(); options.authUrl = "/auth"; AblyRest rest = new AblyRest(options); ``` ```rest_csharp AblyRest rest = new AblyRest(new ClientOptions { AuthUrl = new Uri("/auth") }); ``` ```rest_objc ARTClientOptions *options = [[ARTClientOptions alloc] init]; options.authUrl = [NSURL URLWithString:@"/auth"]; ARTRest *rest = [[ARTRest alloc] initWithOptions:options]; ``` ```rest_swift let options = ARTClientOptions() options.authUrl = NSURL(string: "/auth") let rest = ARTRest(options: options) ``` ```rest_go client, err := ably.NewREST(ably.WithAuthURL("/auth")) ``` ```rest_flutter final clientOptions = ably.ClientOptions( authUrl: '/auth' ); final rest = ably.Rest(options: clientOptions); ``` The client will obtain a token, JWT, or tokenRequest from the URL and use it to authenticate requests to Ably. Before token expiry, a request for a new token will be made automatically by the client to the `authUrl`. ### authCallback You can specify an authentication callback function when you create the Ably client. Inside `authCallback`, you can make a network request to your servers to generate the `tokenRequest`. For example: ```realtime_javascript const ablyClient = new Realtime({ authCallback: async (tokenParams, callback) => { let tokenRequest; try { tokenRequest = await obtainTokenRequest(); // Make a network request to your server } catch (err) { callback(err, null); return; } callback(null, tokenRequest); } }); ``` ```realtime_nodejs const ablyClient = new Realtime({ authCallback: async (tokenParams, callback) => { let tokenRequest; try { tokenRequest = await obtainTokenRequest(); // Make a network request to your server } catch (err) { callback(err, null); return; } callback(null, tokenRequest); } }); ``` ```realtime_python realtime = AblyRealtime(auth_callback=get_token_request) async def get_token_request(*args, **kwargs): create_token_request = await get_token_request() // Implement this function to get a token request from your server if create_token_request.status_code == 200: token_request = create_token_request.json() return token_request else: raise Exception(f"Failed to retrieve token request: {create_token_request.status_code} - {create_token_request.text}") ``` ```realtime_go realtime, _ := ably.NewRealtime( ably.WithAuthCallback(func(context.Context, ably.TokenParams) (ably.Tokener, error) { createTokenRequest := getTokenRequest() // Implement this function to get a token request from your server if (createTokenRequest.StatusCode == 200) { return createTokenRequest } return "Failed to retrieve token request: '%v' - '%v'", createTokenRequest.StatusCode, createTokenRequest.Text }) ) ``` ```realtime_flutter ably.ClientOptions clientOptions = ably.ClientOptions( authCallback: (ably.TokenParams tokenParams) async { // `createTokenRequest` should be implemented to communicate with user server ably.TokenRequest tokenRequest = await createTokenRequest(tokenParams); // `authCallback` has to return an instance of TokenRequest return tokenRequest; } ); ably.Realtime realtime = ably.Realtime(options: clientOptions); ``` ```realtime_java ClientOptions options = new ClientOptions(); options.authCallback = new Auth.TokenCallback() { @Override public Object getTokenRequest(Auth.TokenParams params) throws AblyException { // Implement this function to get a token request from your server return getTokenRequestFromServer(); } }; AblyRealtime realtime = new AblyRealtime(options); ``` ```rest_javascript const rest = new Ably.Rest({ authCallback: (tokenParams, callback) => { // implement your callback here }, }); ``` ```rest_nodejs const rest = new Ably.Rest({ authCallback: (tokenParams, callback) => { // implement your callback here }, }); ``` ```rest_python rest = AblyRest(auth_callback=get_token_request) async def get_token_request(*args, **kwargs): create_token_request = await get_token_request() // Implement this function to get a token request from your server if create_token_request.status_code == 200: token_request = create_token_request.json() return token_request else: raise Exception(f"Failed to retrieve token request: {create_token_request.status_code} - {create_token_request.text}") ``` ```rest_java ClientOptions options = new ClientOptions(); options.authCallback = new Auth.TokenCallback() { @Override public Object getTokenRequest(Auth.TokenParams params) throws AblyException { // Implement this function to get a token request from your server return getTokenRequestFromServer(); } }; AblyRest rest = new AblyRest(options); ``` ```rest_php $rest = new Ably\AblyRest( [ 'authCallback' => function(Ably\Models\TokenParams $params) { $curl = curl_init('/auth'); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($curl); curl_close($curl); $tokenRequestData = json_decode($response, true); $tokenRequest = new Ably\Models\TokenRequest($tokenRequestData); return $tokenRequest; } ] ); ``` ```rest_go rest, _ := ably.NewREST( ably.WithAuthCallback(func(context.Context, ably.TokenParams) (ably.Tokener, error) { createTokenRequest := getTokenRequest() // Implement this function to get a token request from your server if (createTokenRequest.StatusCode == 200) { return createTokenRequest } return "Failed to retrieve token request: '%v' - '%v'", createTokenRequest.StatusCode, createTokenRequest.Text }) ) ``` ```rest_flutter ably.ClientOptions clientOptions = ably.ClientOptions( authCallback: (ably.TokenParams tokenParams) async { // `createTokenRequest` should be implemented to communicate with user server ably.TokenRequest tokenRequest = await createTokenRequest(tokenParams); // `authCallback` has to return an instance of TokenRequest return tokenRequest; } ); ably.Rest rest = ably.Rest(options: clientOptions); ``` The [`tokenParams`](https://ably.com/docs/api/realtime-sdk/authentication#token-params) argument in `authCallback` is available for convenience, allowing you to see the capabilities, `clientId`, and other details requested by the client. However, `tokenParams` should not be trusted or used to generate the `tokenRequest` on the server side. Instead it is recommended that your `createTokenRequest` API authenticates clients separately, for example based on cookies, headers, or HTTP body. ## AuthOptions Use properties set with [`AuthOptions`](https://ably.com/docs/api/realtime-sdk/authentication#auth-options) to override the default authentication values set when instantiating a client. You can also embed `AuthOptions` into your [`ClientOptions`](https://ably.com/docs/api/realtime-sdk#client-options) while instantiating. There are several `AuthOptions` you can specify along with `authUrl` and `authCallback`: - `authMethod` :- when `authUrl` is called, the default `GET` method will be used, unless `POST` is specified. - `authHeaders` :- allows you to pass additional headers as required, depending on your use case. - `authParams` :- allows you to pass additional query parameters, depending on your use case. The following is an example of passing `AuthOptions`: ```realtime_javascript const realtime = new Ably.Realtime({ authUrl: "/auth", authMethod: "POST", authParams: {p1: param1, b: param2}, authHeaders: {h1: header1, h2: header2} }); ``` ```realtime_nodejs const realtime = new Ably.Realtime({ authUrl: "/auth", authMethod: "POST", authParams: {p1: param1, b: param2}, authHeaders: {h1: header1, h2: header2} }); ``` ```realtime_python realtime = AblyRealtime(auth_url='/auth', auth_method="GET", auth_headers={'h1': 'v1'}, auth_params={'param1': 'param2'}) ``` ```realtime_go headers := http.Header{} headers.Set("h1", "header1") headers.Set("h2", "header2") client, err := ably.NewRealtime( ably.WithAuthURL("/auth"), ably.WithAuthMethod("GET"), ably.WithAuthHeaders(headers), ably.WithAuthParams(url.Values{ "p1": {"param1"}, "p2": {"param2"}, })) if err != nil { panic(err) } ``` ```realtime_flutter final clientOptions = ably.ClientOptions( authUrl: '/auth', authMethod: 'GET', authParams: { 'p1': 'param1', 'b': 'param2', }, authHeaders: { 'h1': 'header1', 'h2': 'header2', }, ); final realtime = ably.Realtime(options: clientOptions); ``` ```realtime_java ClientOptions options = new ClientOptions(); options.authUrl = "/auth"; options.authMethod = "POST"; options.authParams = new Param[]{ new Param("p1", "param1"), new Param("p2", "param2") }; options.authHeaders = new Param[]{ new Param("h1", "header1"), new Param("h2", "header2") }; AblyRealtime realtime = new AblyRealtime(options); ``` ```rest_javascript const rest = new Ably.Rest({ authUrl: "/auth", authMethod: "POST", authParams: {p1: param1, b: param2}, authHeaders: {h1: header1, h2: header2} }); ``` ```rest_nodejs const rest = new Ably.Rest({ authUrl: "/auth", authMethod: "POST", authParams: {p1: param1, b: param2}, authHeaders: {h1: header1, h2: header2} }); ``` ```rest_python rest = AblyRest(auth_url='/auth', auth_method="GET", auth_headers={'h1': 'v1'}, auth_params={'param1': 'param2'}) ``` ```rest_go headers := http.Header{} headers.Set("h1", "header1") headers.Set("h2", "header2") client, err := ably.NewREST( ably.WithAuthURL("/auth"), ably.WithAuthMethod("GET"), ably.WithAuthHeaders(headers), ably.WithAuthParams(url.Values{ "p1": {"param1"}, "p2": {"param2"}, })) if err != nil { panic(err) } ``` ```rest_flutter final clientOptions = ably.ClientOptions( authUrl: '/auth', authMethod: 'GET', authParams: { 'p1': 'param1', 'b': 'param2', }, authHeaders: { 'h1': 'header1', 'h2': 'header2', }, ); final rest = ably.Rest(options: clientOptions); ``` ```rest_java ClientOptions options = new ClientOptions(); options.authUrl = "/auth"; options.authMethod = "POST"; options.authParams = new Param[]{ new Param("p1", "param1"), new Param("p2", "param2") }; options.authHeaders = new Param[]{ new Param("h1", "header1"), new Param("h2", "header2") }; AblyRest rest = new AblyRest(options); ``` ## Ably Tokens Ably Tokens can be used to authenticate with Ably in the following ways: * Ably [TokenRequest](#token-request) is created by your servers and passed to clients. * An [Ably Token](#ably-token) is issued by your servers and passed to clients. Note that the machine on which you are running your auth server should have an accurate clock, as tokens and `TokenRequest` contain a timestamp. You can use an [NTP daemon](https://en.wikipedia.org/wiki/Ntpd), or if you are not able to control your server's clock, you can wish to use the `queryTime` [auth option](https://ably.com/docs/api/rest-sdk/types#auth-options). ### Ably TokenRequest Using an Ably SDK, a `TokenRequest` is [generated from your server](https://ably.com/docs/api/realtime-sdk/authentication#create-token-request) and returned to the client-side SDK instance. The client-side SDK instance then uses the [`TokenRequest`](https://ably.com/docs/api/realtime-sdk/types#token-request) to request an [Ably Token](https://ably.com/docs/api/realtime-sdk/authentication#request-token) from Ably, and subsequently authenticates using that [Ably Token](https://ably.com/docs/api/realtime-sdk/authentication#token-details). This is the recommended approach for client-side authentication, for the following reasons: * An Ably `TokenRequest` can be generated securely by your servers without communicating with Ably. * Your secret API key is never shared with Ably or your clients. * An Ably `TokenRequest` cannot be tampered with due to being signed, must be used soon after creation, and can only be used once. The process used by Ably SDKs to authenticate with Ably using a `TokenRequest` is illustrated in the following diagram: ![Ably TokenRequest auth process diagram](https://raw.githubusercontent.com/ably/docs/main/src/images/content/diagrams/Ably-token-auth-1.png) The following is an example of creating an Ably `TokenRequest`: ```javascript const ably = new Ably.Rest({ key: 'your-api-key' }); const tokenRequest = await ably.auth.createTokenRequest({ clientId: 'client@example.com' }); ``` ```python ably = AblyRest('your-api-key') token = await ably.auth.create_token_request( { "clientId": "client@example.com", "capability": { "channel1": ["publish", "subscribe"], }, 'ttl': 3600 * 1000, # ms }) ``` ```java ClientOptions options = new ClientOptions("your-api-key"); AblyRest rest = new AblyRest(options); Auth.TokenParams tokenParams = new Auth.TokenParams(); tokenParams.clientId = "client@example.com"; Auth.TokenRequest tokenDetails = rest.auth.createTokenRequest(tokenParams, null); ``` ```php $rest = new Ably\AblyRest( ['key' => 'your-api-key'] ); $tokenRequest = $rest->auth->createTokenRequest( ['clientId' => 'client@example.com'] ); ``` ```go rest, err := ably.NewREST( ably.WithKey("your-api-key")) if err != nil { log.Fatalf("Error creating Ably client: %v", err) } tokenParams := &ably.TokenParams{ClientID: "client@example.com"} tokenRequest, _ := rest.Auth.CreateTokenRequest(tokenParams) ``` ```flutter final clientOptions = ably.ClientOptions( key: 'your-api-key', ); final rest = ably.Rest(options: clientOptions); const tokenParams = ably.TokenParams( clientId: 'client@example.com' ); final tokenRequest = rest.auth.createTokenRequest(tokenParams: tokenParams); ``` Clients can pass this server-side generated `TokenRequest` to Ably to authenticate with Ably automatically. ### Ably Token Using an Ably SDK, an Ably Token is [requested by your servers](https://ably.com/docs/api/realtime-sdk/authentication#request-token) from Ably and then passed to the client-side SDK instance. The client-side SDK instance then uses that [Ably Token](https://ably.com/docs/api/realtime-sdk/authentication#tokens) to authenticate with Ably. This is an alternative approach for authentication that enables you to issue"Ably Tokens directly as opposed to providing Ably `TokenRequests` from your servers. The advantage for clients is that it saves one round trip request as they do not need to request an Ably Token themselves. The disadvantage is that your servers must communicate with Ably each time an Ably Token is required. The process used by Ably SDKs to authenticate with Ably using an Ably Token is illustrated in the following diagram: ![Ably token auth process diagram](https://raw.githubusercontent.com/ably/docs/main/src/images/content/diagrams/Ably-Auth-Overview1.png) The following is an example of issuing an Ably Token from a server: ```javascript const ably = new Ably.Rest({ key: 'your-api-key' }); const tokenDetails = await ably.auth.requestToken({ clientId: 'client@example.com' }); ``` ```python rest = AblyRest(key='your-api-key') token_request_params = { 'clientId': 'client@example.com', } token_details = await rest.auth.request_token(token_params=token_request_params) ``` ```java ClientOptions options = new ClientOptions("your-api-key"); AblyRest rest = new AblyRest(options); Auth.TokenParams tokenParams = new Auth.TokenParams(); tokenParams.clientId = "client@example.com"; Auth.TokenDetails tokenDetails = rest.auth.requestToken(tokenParams, null); ``` ```php $rest = new Ably\AblyRest( ['key' => 'your-api-key'] ); $tokenDetails = $rest->auth->requestToken( ['clientId' => 'client@example.com'] ); ``` ```go rest, err := ably.NewREST( ably.WithKey("API_KEY")) if err != nil { log.Fatalf("Error creating Ably client: %v", err) } tokenParams := &ably.TokenParams{ClientID: "client@example.com"} tokenRequest, _ := rest.Auth.RequestToken(context.Background(), tokenParams) ``` ```flutter final clientOptions = ably.ClientOptions( key: 'your-api-key', ); final rest = ably.Rest(options: clientOptions); const tokenParams = ably.TokenParams( clientId: 'client@example.com', ); final tokenDetails = await rest.auth.requestToken( tokenParams: tokenParams ); ``` ## JSON Web Tokens (JWT) JSON Web Tokens (JWT) can be used to authenticate with Ably in the following ways: * [Ably JWT](#standard) is created by your servers and passed to clients. * [Ably Token](#embedded) is embedded in a JWT from your server and passed to clients. Note that the machine on which you are running your auth server should have an accurate clock, as tokens contain a timestamp. You can use an [NTP daemon](https://en.wikipedia.org/wiki/Ntpd), or if you are not able to control your server's clock, you can wish to use the `queryTime` [auth option](https://ably.com/docs/api/rest-sdk/types#auth-options). ### JWT using your API key It is possible to use a [JWT](https://jwt.io) as a form of token for authentication with Ably, so long as it is structured appropriately, in what will be referred to as an [Ably JWT](https://ably.com/docs/api/realtime-sdk/authentication#ably-jwt). It is possible for an Ably JWT to contain claims indicating its `clientId`, capabilities and expiry - in an analogous way to an [Ably Token](#tokens) - and it is signed with the applicable [Ably API key's secret part](https://ably.com/docs/auth#api-key). This is similar to signing an Ably [`TokenRequest`](#token-request), but the client does not need to request an Ably Token, instead being able to use the Ably JWT as a token in itself. Any compliant third-party JWT library may be used to create the JWT without requiring the token to be issued by Ably. This can be useful for situations where an Ably client library is not available, such as an embedded device connecting to Ably via [MQTT](https://ably.com/docs/protocols/mqtt). The process used by Ably SDKs to authenticate with Ably using JWTs is illustrated in the following diagram: ![Ably JWT auth method](https://raw.githubusercontent.com/ably/docs/main/src/images/content/diagrams/Ably-Auth-Overview2.png) The following is an example of creating an Ably JWT: To generate a JWT token in Flutter, you will need to install the [`crypto`](https://pub.dev/packages/crypto) library To generate a JWT token in Java, you will need to install the [`java-jwt`](https://github.com/auth0/java-jwt) library. ```javascript var header = { "typ":"JWT", "alg":"HS256", "kid": "{{API_KEY_NAME}}" } var currentTime = Math.round(Date.now()/1000); var claims = { "iat": currentTime, /* current time in seconds */ "exp": currentTime + 3600, /* time of expiration in seconds */ "x-ably-capability": "{\"*\":[\"*\"]}" } var base64Header = btoa(header); var base64Claims = btoa(claims); /* Apply the hash specified in the header */ var signature = hash((base64Header + "." + base64Claims), "{{API_KEY_SECRET}}"); var ablyJwt = base64Header + "." + base64Claims + "." + signature; ``` ```python import jwt import time def createAblyJwt(ably_api_key: str): # Split the API key into key ID and secret parts = ably_api_key.split(":") kid = parts[0] # Prepare JWT headers headers = { "typ": "JWT", "alg": "HS256", "kid": kid } # Prepare JWT claims claims = { "iat": int(time.time()), # Issued at time "exp": int(time.time()) + 120, # Expiration time (2 minutes from now) "x-ably-capability": "{\"*\":[\"*\"]}" # Full capability } # Encode the JWT jwtToken = jwt.encode(headers=headers, payload=claims, key=parts[1]) return jwtToken ``` ```java Map headerClaims = new HashMap<>(); headerClaims.put("typ", "JWT"); headerClaims.put("alg", "HS256"); headerClaims.put("kid", "{{API_KEY_NAME}}"); // Define the current time long currentTimeInSeconds = System.currentTimeMillis() / 1000; // Define the claims Map claims = new HashMap<>(); claims.put("iat", currentTimeInSeconds); claims.put("exp", currentTimeInSeconds + 3600); claims.put("x-ably-capability", "{\"*\":[\"*\"]}"); // Create the JWT Algorithm algorithm = Algorithm.HMAC256("{{API_KEY_SECRET}}"); String token = JWT.create() .withHeader(headerClaims) .withPayload(claims) .sign(algorithm); ``` ```php $header = [ 'typ' => 'JWT', 'alg' => 'HS256', 'kid' => '{{API_KEY_NAME}}' ]; $currentTime = time(); $claims = [ 'iat' => $currentTime, /* current time in seconds */ 'exp' => $currentTime + 3600, /* time of expiration in seconds (an hour) */ 'x-ably-capability' => '{\"*\":[\"*\"]}' ]; $base64Header = base64_encode(json_encode($header)); $base64Claims = base64_encode(json_encode($claims)); $signature = hash_hmac( 'sha256', $base64Header . '.' . $base64Claims, '{{API_KEY_SECRET}}', true ); $jwt = $base64Header . '.' . $base64Claims . '.' . $signature; ``` ```go // Create JWT header header := map[string]string{ "typ": "JWT", "alg": "HS256", "kid": "{{API_KEY_NAME}}", } // Get current time in seconds currentTime := time.Now().Unix() // Create JWT claims claims := map[string]interface{}{ "iat": currentTime, // current time in seconds "exp": currentTime + 3600, // time of expiration in seconds "x-ably-capability": "{\"*\":[\"*\"]}", } // Encode header to base64 headerJSON, err := json.Marshal(header) if err != nil { log.Fatalf("Failed to marshal header: %v", err) } base64Header := base64.RawURLEncoding.EncodeToString(headerJSON) // Encode claims to base64 claimsJSON, err := json.Marshal(claims) if err != nil { log.Fatalf("Failed to marshal claims: %v", err) } base64Claims := base64.RawURLEncoding.EncodeToString(claimsJSON) // Create the signature dataToSign := base64Header + "." + base64Claims h := hmac.New(sha256.New, []byte("{{API_KEY_SECRET}}")) h.Write([]byte(dataToSign)) signature := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) // Combine the parts to form the final JWT ablyJwt := base64Header + "." + base64Claims + "." + signature log.Println("Ably JWT:", ablyJwt) ``` ```flutter final header = { "typ": "JWT", "alg": "HS256", "kid": "{{API_KEY_NAME}}" }; final currentTime = (DateTime.now().millisecondsSinceEpoch / 1000).round(); final claims = { "iat": currentTime, /* current time in seconds */ "exp": currentTime + 3600, /* time of expiration in seconds */ "x-ably-capability": "{\"*\":[\"*\"]}" }; final base64Header = base64UrlEncode(utf8.encode(json.encode(header))); final base64Claims = base64UrlEncode(utf8.encode(json.encode(claims))); final hmacSha256 = Hmac(sha256, utf8.encode("$base64Header.$base64Claims")); final digest = hmacSha256.convert(utf8.encode("{{API_KEY_SECRET}}")); final signature = base64UrlEncode(digest.bytes); final ablyJwt = "$base64Header.$base64Claims.$signature"; ``` At present Ably does not support asymmetric signatures based on a key pair belonging to a third party. ### Embed an Ably Token in a JWT If a system has an existing [JWT](https://jwt.io/) scheme, it's possible to embed an Ably Token as a claim within it. The JWT and embedded token need to meet the following requirements: * The embedded token is an [Ably Token](#tokens). * The embedded token is included under the `x-ably-token` key in the [JOSE Header](https://tools.ietf.org/html/rfc7519), or if using JWS, the embedded token is included using the `x-ably-token` claim in the payload. * The expiry time of the embedded token must not be earlier than the outer JWT's expiry time (`exp` claim). Ably will reject any JWT if it is unencrypted and its `exp` claim is later than the expiry of the enclosed token. This helps to ensure that tokens are renewed prior to expiry. The process used by Ably SDKs to authenticate with Ably using an Ably Token embedded in a JWT is illustrated in the following diagram: ![Token embedded in a JWT auth method](https://raw.githubusercontent.com/ably/docs/main/src/images/content/diagrams/Ably-Auth-Overview3.png) The following is an example of issuing an Ably Token inside the of header of a JWT. Note that the authenticity of the JWT will not be checked, due to Ably not having access to your `SECRET` key. ```javascript const ably = new Ably.Rest({ key: 'your-api-key' }); const tokenDetails = await ably.auth.requestToken({ clientId: 'client@example.com' }); const header = { "typ":"JWT", "alg":"HS256", "x-ably-token": tokenDetails.token } const claims = { "exp": currentTime + 3600 } const base64Header = btoa(header); const base64Claims = btoa(claims); /* Apply the hash specified in the header */ const signature = hash((base64Header + "." + base64Claims), SECRET); const jwt = base64Header + "." + base64Claims + "." + signature; /* Send jwt to client */ ``` ```python rest = AblyRest(key='your-api-key') token_request_data = { 'clientId': 'client@example.com', } token_details = await rest.auth.request_token(token_params=token_request_data) header = { "typ": "JWT", "alg": "HS256", "x-ably-token": token_details.token } claims = { "exp": int(time.time()) + 3600 } base64_header = base64.urlsafe_b64encode(bytes(json.dumps(header), 'utf-8')).decode('utf-8') base64_claims = base64.urlsafe_b64encode(bytes(json.dumps(claims), 'utf-8')).decode('utf-8') signature = hashlib.sha256((base64_header + "." + base64_claims + "{{API_KEY_SECRET}}").encode('utf-8')).digest() signature_base64 = base64.urlsafe_b64encode(signature).decode('utf-8') jwt_token = base64_header + "." + base64_claims + "." + signature_base64 ``` ```java ClientOptions options = new ClientOptions("your-api-key"); AblyRest rest = new AblyRest(options); Auth.TokenParams tokenParams = new Auth.TokenParams(); tokenParams.clientId = "client@example.com"; Auth.TokenDetails tokenDetails = rest.auth.requestToken(tokenParams, null); Map headerClaims = new HashMap<>(); headerClaims.put("typ", "JWT"); headerClaims.put("alg", "HS256"); headerClaims.put("x-ably-token", tokenDetails.token); // Time of expiration in seconds (an hour) long currentTimeInSeconds = System.currentTimeMillis() / 1000; // Define the claims Map claims = new HashMap<>(); claims.put("exp", currentTimeInSeconds + 3600); // Create the JWT Algorithm algorithm = Algorithm.HMAC256("{{API_KEY_SECRET}}"); String token = JWT.create() .withHeader(headerClaims) .withPayload(claims) .sign(algorithm); ``` ```php $rest = new Ably\AblyRest( ['key' => 'your-api-key'] ); $tokenDetails = $rest->auth->requestToken( ['clientId' => 'client@example.com'] ); $header = [ 'typ' => 'JWT', 'alg' => 'HS256', 'x-ably-token' => $tokenDetails->token ]; $currentTime = time(); $claims = [ 'exp' => $currentTime + 3600 /* time of expiration in seconds (an hour) */ ]; $base64Header = base64_encode(json_encode($header)); $base64Claims = base64_encode(json_encode($claims)); $secret = 'YOUR_SECRET'; $signature = hash_hmac('sha256', $base64Header . '.' . $base64Claims, $secret, true); $base64Signature = base64_encode($signature); $jwt = $base64Header . '.' . $base64Claims . '.' . $base64Signature; ``` ```go rest, err := ably.NewREST(ably.WithKey("your-api-key")) if err != nil { log.Fatalf("Failed to create Ably REST client: %v", err) } // Request a token tokenParams := &ably.TokenParams{ ClientID: "client@example.com", } tokenDetails, err := rest.Auth.RequestToken(context.Background(), tokenParams) if err != nil { log.Fatalf("Failed to request token: %v", err) } // Get the current time in seconds currentTime := time.Now().Unix() // Create JWT header header := map[string]interface{}{ "typ": "JWT", "alg": "HS256", "x-ably-token": tokenDetails.Token, } // Create JWT claims claims := map[string]interface{}{ "exp": currentTime + 3600, // time of expiration in seconds } // Encode header to base64 headerJSON, err := json.Marshal(header) if err != nil { log.Fatalf("Failed to marshal header: %v", err) } base64Header := base64.RawURLEncoding.EncodeToString(headerJSON) // Encode claims to base64 claimsJSON, err := json.Marshal(claims) if err != nil { log.Fatalf("Failed to marshal claims: %v", err) } base64Claims := base64.RawURLEncoding.EncodeToString(claimsJSON) // Create the signature dataToSign := base64Header + "." + base64Claims h := hmac.New(sha256.New, []byte("SECRET")) h.Write([]byte(dataToSign)) signature := base64.RawURLEncoding.EncodeToString(h.Sum(nil)) // Combine the parts to form the final JWT jwt := base64Header + "." + base64Claims + "." + signature log.Println("JWT:", jwt) // Send JWT to client (for demonstration, print it here) ``` ```flutter final clientOptions = ably.ClientOptions( key: 'your-api-key', ); final rest = ably.Rest(options: clientOptions); const tokenParams = ably.TokenParams( clientId: 'client@example.com', ); final tokenDetails = await rest.auth.requestToken( tokenParams: tokenParams ); final header = { "typ": "JWT", "alg": "HS256", "x-ably-token": tokenDetails.token }; final currentTime = (DateTime.now().millisecondsSinceEpoch / 1000).round(); final claims = { "iat": currentTime, /* current time in seconds */ "exp": currentTime + 3600, /* time of expiration in seconds */ "x-ably-capability": "{\"*\":[\"*\"]}" }; final base64Header = base64UrlEncode(utf8.encode(json.encode(header))); final base64Claims = base64UrlEncode(utf8.encode(json.encode(claims))); final hmacSha256 = Hmac(sha256, utf8.encode("$base64Header.$base64Claims")); final digest = hmacSha256.convert(utf8.encode("{{API_KEY_SECRET}}")); final signature = base64UrlEncode(digest.bytes); final ablyJwt = "$base64Header.$base64Claims.$signature"; ``` ## Dynamic channel access control Token authentication allows you to dynamically change a client's channel access permissions without disconnecting. Use the [`authorize()`](https://ably.com/docs/api/realtime-sdk/authentication#authorize) method to re-authenticate with updated [capabilities](https://ably.com/docs/auth/capabilities). When you call `client.auth.authorize()`: 1. The client obtains a new token with different capabilities from your authentication server. 2. The new token is sent to Ably using the existing realtime connection. 3. The updated permissions are automatically applied without disconnecting. ### Use cases You can trigger re-authentication in response to: - Detect error code [40160](https://ably.com/docs/platform/errors/codes#40160) when a client attempts to attach to a channel. - Instruct clients to re-authenticate via Ably channels or other communication methods. The following example shows how to re-authenticate with updated capabilities: ```javascript // Re-authenticate to get new capabilities try { const newTokenDetails = await client.auth.authorize({ clientId: 'user123', // Your auth server will return a token with updated capabilities }); console.log('Successfully updated permissions'); } catch (error) { console.error('Failed to re-authenticate:', error); } ``` For security purposes, handle non-compliant clients by: - Set shorter token TTL expiry times to force frequent re-authentication (minimum 10 minutes recommended). - Use the [token revocation API](https://ably.com/docs/auth/revocation) to immediately invalidate tokens. ## When to use token auth Ably recommends that token authentication is used 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.