# JSON Web Tokens (JWTs)
JSON Web Token (JWT) is the recommended token format for most applications. Your clients request a JWT from your server, which creates and signs it using your Ably API key. No Ably SDK is required on the server — any JWT library works.
## Server setup
### Creating JWTs
Your server creates JWTs signed with your Ably API key secret. No Ably SDK is required. Any JWT library works.
To generate JWTs in Node.js, install the [`jsonwebtoken`](https://github.com/auth0/node-jsonwebtoken) package.
To generate JWTs in Python, install the [`PyJWT`](https://pyjwt.readthedocs.io/) package.
To generate JWTs in Java, install the [`java-jwt`](https://github.com/auth0/java-jwt) library.
To generate JWTs in Go, use the [`golang-jwt`](https://github.com/golang-jwt/jwt) package.
To generate JWTs in Ruby, install the [`ruby-jwt`](https://github.com/jwt/ruby-jwt) gem.
To generate JWTs in PHP, install the [`firebase/php-jwt`](https://github.com/firebase/php-jwt) package.
To generate JWTs in C#, use the [`System.IdentityModel.Tokens.Jwt`](https://www.nuget.org/packages/System.IdentityModel.Tokens.Jwt) NuGet package.
To generate JWTs in Flutter, install the [`crypto`](https://pub.dev/packages/crypto) package.
To generate JWTs in Swift, use a library such as [`SwiftJWT`](https://github.com/Kitura/Swift-JWT).
#### 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";
```
Ably does not support asymmetric signatures based on a key pair belonging to a third party.
### JWT claims
| Claim | Required | Description |
|-------|----------|-------------|
| `x-ably-capability` | Yes | JSON string defining [capabilities](https://ably.com/docs/auth/capabilities.md) |
| `x-ably-clientId` | No | Sets a trusted [client ID](https://ably.com/docs/auth/identified-clients.md) |
| `exp` | Yes | Expiration time (Unix timestamp) |
| `iat` | Yes | Issued at time (Unix timestamp) |
## Client setup
`authUrl` is useful for web-based clients that can pass cookies automatically. For non-web clients, `authCallback` provides more control.
### authCallback
Use `authCallback` to fetch JWTs from your server. The SDK automatically calls this function when connecting and when the token is near expiry.
#### Realtime Javascript
```
const realtime = new Ably.Realtime({
authCallback: async (tokenParams, callback) => {
try {
const response = await fetch('/api/ably-token', {
credentials: 'include',
});
if (!response.ok) throw new Error('Auth failed');
callback(null, await response.text());
} catch (error) {
callback(error, null);
}
},
});
```
#### Realtime Python
```
import aiohttp
async def get_ably_jwt(*args, **kwargs):
async with aiohttp.ClientSession() as session:
async with session.get('/api/ably-token') as response:
if response.status != 200:
raise Exception(f"Auth failed: {response.status}")
return await response.text()
realtime = AblyRealtime(auth_callback=get_ably_jwt)
```
#### Realtime Java
```
ClientOptions options = new ClientOptions();
options.authCallback = new Auth.TokenCallback() {
@Override
public Object getTokenRequest(Auth.TokenParams params) throws AblyException {
// Make HTTP request to your auth server and return JWT string
return fetchJwtFromServer();
}
};
AblyRealtime realtime = new AblyRealtime(options);
```
#### Realtime Kotlin
```
val options = ClientOptions()
options.authCallback = Auth.TokenCallback { params ->
// Make HTTP request to your auth server and return JWT string
fetchJwtFromServer()
}
val realtime = AblyRealtime(options)
```
#### Realtime Swift
```
let options = ARTClientOptions()
options.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: options)
```
#### Realtime Objc
```
ARTClientOptions *options = [[ARTClientOptions alloc] init];
options.authCallback = ^(ARTTokenParams *tokenParams, void (^callback)(id, NSError *)) {
[self fetchAblyJwt:^(NSString *jwt, NSError *error) {
if (error) {
callback(nil, error);
} else {
callback(jwt, nil);
}
}];
};
ARTRealtime *realtime = [[ARTRealtime alloc] initWithOptions:options];
```
#### Realtime Csharp
```
ClientOptions options = new ClientOptions();
options.AuthCallback = async tokenParams =>
{
// Make HTTP request to your auth server and return JWT string
return await FetchJwtFromServerAsync();
};
AblyRealtime realtime = new AblyRealtime(options);
```
#### Realtime Go
```
client, err := ably.NewRealtime(
ably.WithAuthCallback(func(ctx context.Context, params ably.TokenParams) (ably.Tokener, error) {
// Fetch JWT from your auth server
jwt, err := fetchJwtFromServer()
if err != nil {
return nil, err
}
return ably.TokenString(jwt), nil
}))
```
#### Realtime Ruby
```
realtime = Ably::Realtime.new(auth_callback: -> (token_params) {
# Fetch JWT from your auth server
fetch_jwt_from_server()
})
```
#### Realtime Flutter
```
final clientOptions = ably.ClientOptions(
authCallback: (tokenParams) async {
// Fetch JWT from your auth server
return await fetchJwtFromServer();
},
);
final realtime = ably.Realtime(options: clientOptions);
```
The [`tokenParams`](https://ably.com/docs/api/realtime-sdk/authentication.md#token-params) argument is available for convenience but should not be trusted. Your auth endpoint should authenticate clients separately using cookies, headers, or request body.
### authUrl
You can specify an `authUrl` as an alternative to `authCallback`. The SDK makes an HTTP request to this URL to obtain a JWT.
#### Realtime Javascript
```
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 Kotlin
```
val options = ClientOptions()
options.authUrl = "/auth"
val realtime = AblyRealtime(options)
```
#### Realtime Flutter
```
final clientOptions = ably.ClientOptions(
authUrl: '/auth'
);
final realtime = ably.Realtime(options: clientOptions);
```
#### Rest Javascript
```
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 Kotlin
```
val options = ClientOptions()
options.authUrl = "/auth"
val rest = 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);
```
### AuthOptions
Use properties set with [`AuthOptions`](https://ably.com/docs/api/realtime-sdk/authentication.md#auth-options) to customize authentication behavior:
- `authMethod` - when `authUrl` is called, the default `GET` method will be used, unless `POST` is specified.
- `authHeaders` - pass additional headers as required.
- `authParams` - pass additional query parameters.
#### Realtime Javascript
```
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 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);
```
## JWT features
JWTs support features not available with other token mechanisms.
### Channel-scoped claims
Embed trusted metadata in JWTs that other clients can read. Use the `ably.channel.*` claim pattern:
#### Javascript
```
const ablyJwt = jwt.sign(
{
'x-ably-capability': JSON.stringify({
'chat:*': ['publish', 'subscribe', 'presence'],
}),
'x-ably-clientId': userId,
// Channel-scoped claim - other clients can read this
'ably.channel.chat:lobby': JSON.stringify({
role: 'moderator',
displayName: 'Alice',
}),
},
keySecret,
{ algorithm: 'HS256', keyid: keyName, expiresIn: '1h' }
);
```
Other clients can read these claims from presence or message metadata, providing trusted user information without additional server calls.
### Per-connection rate limits
Restrict how fast specific clients can publish messages using the `ably.limits.publish.*` claim:
#### Javascript
```
const ablyJwt = jwt.sign(
{
'x-ably-capability': JSON.stringify({ '*': ['publish', 'subscribe'] }),
'x-ably-clientId': userId,
// Per-connection rate limit - max 10 messages per second
'ably.limits.publish.perAttachment.maxRate.*': 10,
},
keySecret,
{ algorithm: 'HS256', keyid: keyName, expiresIn: '1h' }
);
```
## Token lifecycle
Token refresh, TTL limits, and dynamic access control apply to all token types. See [token authentication](https://ably.com/docs/auth/token.md#refresh) for details.
For a comparison of JWT, TokenRequest, and Ably Token mechanisms, see [choosing a token mechanism](https://ably.com/docs/auth/token.md#choosing).
## Related Topics
- [Overview](https://ably.com/docs/auth/token.md): Token authentication allows clients to authenticate with Ably, without exposing the Ably API key and secret.
- [Ably Tokens](https://ably.com/docs/auth/token/ably-tokens.md): Ably Tokens are an alternative to JWTs when you need to keep capabilities confidential or when your capability list is very large.
## Documentation Index
To discover additional Ably documentation:
1. Fetch [llms.txt](https://ably.com/llms.txt) for the canonical list of available pages.
2. Identify relevant URLs from that index.
3. Fetch target pages as needed.
Avoid using assumed or outdated documentation paths.