Auth and Security
Both the REST client library and the Realtime client library use common authentication mechanisms. The two schemes supported by Ably are Basic Authentication, which uses your API key, and Token Authentication, which relies on a token you obtain from Ably, or a token request that you sign and issue to your clients. Token Authentication, in most cases, is the recommended strategy as it provides robust access control and stringent security measures.
Both types of authentication rely on a series of API keys set up for each application. Each key is configured via the dashboard and exposed as a single string such as xVLyHw.Nz6cuA:skQTwKsMpLn7WWveOKVTKpbV-wn4LYD3H0l5Ljdw-KI
which contains information that identifies the key as well as containing the “secret” key value.
If you are using an Ably client library then the key can be considered to be an opaque string supplied when the library is instantiated. If you are using the Ably REST API endpoint directly, then the API key string can be used in the basic authorisation header
A key is associated with a set of capabilities – i.e. an explicit indication of which operations (such as publish, subscribe or presence) are allowable using that key. Additionally, the capabilities can be restricted to a set of a channels or channel namespaces. View all supported capability operations.
Getting started
The simplest way to authenticate with Ably is to use an API key string when instancing the client library. This scheme is simple and authenticates with Ably using basic authentication. However, this method suffers from a number of problems that might make it unsuitable for certain use cases:
- the secret is passed directly by the client to Ably, so it is not permitted for connections that are not over TLS (HTTPS or non-encrypted realtime connections) to prevent the key secret being intercepted
- the secret may be required to be embedded in a script on a public site
- all of the configured capabilities of the key are implicitly possible in any request, and clients that legitimately obtain this key may then abuse the rights for that key
- clients are permitted to use any client ID in all operations with Ably. As such, a client ID in messages and presence cannot be trusted as any client using Basic Authentication can masquerade with any client ID
These issues are addressed with token-based authentication. Tokens are authentication credentials that are short-lived, and therefore they may more readily be distributed to clients where there is a risk of compromise. Tokens may also be issued with a particular scope – such as a limited set of access rights or capabilities or being limited to use by a specific clientId
ClientId
identity – and therefore token-based authentication provides the flexibility to implement access and identity control policies in the application. Tokens may be obtained using the client library authentication API or directly from the Ably REST API tokenRequest endpoint.
See capabilities and token security explained below for examples that use token authentication to control which channels a client can attach to and which operations they can perform.
Selecting an authentication mechanism
The following guidance aims to help you choose which system to use in any given situation. When deciding, it is recommended to bear in mind the 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.
Basic authentication is appropriate where:
- the script, program or system holding the key is not exposed; for example, typically on one of your own servers. A key should not be embedded in a script in a public-facing web page
- a secure, unmediated connection exists between the client and the Ably service. Keys should only really be sent over a TLS connection (that’s either an HTTPS connection, or an encrypted realtime connection). A key should not be used over a proxied connection unless the proxy is trusted
- access needs to be granted selectively to groups of clients to specific channels or channel namespaces, but only a small number of such access control groups need to be established
- clients are trusted to assume any client ID in the operations they are permitted to perform
- you don’t wish to run your own server to control access
Token authentication is appropriate when:
- there is a risk of exposure of the client’s credentials, either directly or over an insecure, or insecurely proxied, connection
- a client is only intended to have use of the service for a limited period of time
- a client needs to have the ability to identify itself (authenticate its specific identity with a
clientId
), but cannot be trusted sufficiently not to masquerade as a different client identity - fine-grained access needs to be given on a per-client basis to specific channels and/or capabilities
- there may be a requirement to be able to revoke the rights of a client individually
Note that 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 remove browsers and devices will use individually issued tokens.
Basic Authentication explained
Basic authentication is the default authentication scheme when a client library is instantiated with an API key. It as simple as:
var ably = new Ably.Realtime({ key: 'xVLyHw.Nz6cuA:skQTwKsMpLn7WWveOKVTKpbV-wn4LYD3H0l5Ljdw-KI' });
Process used by client libraries connecting with basic auth:
Token Authentication explained
Token authentication is the default authentication scheme when a client library is instantiated with any of the following options:
- a
token
Token
ortokenDetails
TokenDetails
is provided; - an
authUrl
AuthUrl
orauthCallback
AuthCallback
is provided that returns a token or token request; - a
clientId
ClientId
is provided; -
useTokenAuth
UseTokenAuth
is true
Please note that when setting up a mechanism to automatically renew tokens, an authURL
might be more relevant and recommended to be used with the web based clients as they can easily utilize cookies and other web-only features. However, in case of non-web clients, authCallback
is the recommended strategy.
Token authentication is typically done in one of two ways:
Signed token request is created by your servers and passed to clients
Using our client libraries, a signed token request is generated and handed to client libraries. Our client libraries then use that signed token request to request a token from Ably and then authenticate with that token. This is the recommended approach for authentication as: a signed token request can be generated securely by your servers without communicating with Ably; your secret API key is never shared with Ably or your clients; signed token requests cannot be tampered with, must be used soon after creation and can only be used once. This process is depicted in the following diagram:
An example of creating a token request can be seen below:
var ably = new Ably.Rest({ key: 'xVLyHw.Nz6cuA:skQTwKsMpLn7WWveOKVTKpbV-wn4LYD3H0l5Ljdw-KI' });
ably.createTokenRequest({ clientId: '[email protected]' }, null, function(err, tokenRequest) {
/* tokenRequest => {
"capability": "{\"*\":[\"*\"]}",
"clientId": "[email protected]",
"keyName": "xVLyHw.Nz6cuA",
"nonce": "5576521221082658",
"timestamp": _VAR_MS_SINCE_EPOCH_VAR_,
"mac": "GZRgXssZDCegRV....EXAMPLE"
} */
});
Token is issued by your servers and passed to clients
Using our client libraries, a token is requested from Ably on your servers and then handed to client libraries. Our client libraries then use that token to authenticate with Ably. This is an alternative approach for authentication that allows you to issue tokens directly as opposed to providing signed token requests from your servers. The advantage for clients is it saves one round trip request as they do not need to request a token themselves. The disadvantage is that your servers must communicate with Ably each time a token is required. This process is depicted in the following diagram:
An example of issuing a token can be seen below:
var ably = new Ably.Rest({ key: 'xVLyHw.Nz6cuA:skQTwKsMpLn7WWveOKVTKpbV-wn4LYD3H0l5Ljdw-KI' });
ably.requestToken({ clientId: '[email protected]' }, function(err, token) {
/* token => {
"token": "xVLyHw.Dtxd9tuz....EXAMPLE",
"capability": "{\"*\":[\"*\"]}"
"clientId": "[email protected]",
"expires": 1449745287315,
"keyName": "xVLyHw.Nz6cuA",
"issued": 1449741687315,
} */
});
Capabilities and Token Security explained
API keys, like tokens, have a set of capabilities assigned to them that specify which operations (such as subscribe or publish) can be performed on which channels. However, unlike tokens, API keys are long-lived, secret and typically not shared with un-trusted clients.
API keys and their capabilities are configured using the dashboard, they cannot be added or removed programmatically. Tokens on the other hand are designed to be shared with un-trusted clients, are short-lived, and significantly, they are configured and issued programmatically using the Ably client libraries or directly from the Ably REST API. See selecting an authentication scheme to understand why token authentication, in most cases, is the preferred authentication scheme.
Tokens are issued from an existing API key, and their capabilities can, at most, match the capabilities of the issuing API key. If an API key must be shared with a third party, then it is recommended that the 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 tokens issued from that API key, will be restricted to the capabilities assigned to the key.
Capabilities for tokens are determined as follows:
- If no capability is specified in the token request, then the token will be given the full set of capabilities assigned to the issuing key, see example;
- If a set of capabilities are requested, then the token will be assigned the intersection of the requested capability and the capability of the issuing key, see example;
- If a set of capabilities are requested, and the intersection between those and the API key’s capabilities is empty (i.e. they are entirely incompatible), then the token request will result in an error, see example.
See capability operations below for the complete set of supported operations on a channel.
Resource names and wildcards
Capabilities are a map from resources to a list of operations. Each resource can match a single channel e.g. channel
, or multiple channels using wildcards (*
). Wildcards can only replace whole segments (segments are delimited by :
) of the resource name. A wildcard at the end of the name can replace arbitrarily many segments. For example:
- A resource of
*
will match any channel - A resource of
namespace:*
will match any channel in thenamespace
namespace, includingnamespace:channel
, andnamespace:channel:other
- A resource of
foo:*:baz
will matchfoo:bar:baz
, but notfoo:bar:bam:baz
- A resource of
foo:*
will matchfoo:bar
,foo:bar:bam
,foo:bar:bam:baz
etc., as the wildcard as at the end - A resource of
foo*
(without a colon!) will only match the single channel literally calledfoo*
, which probably isn’t what you want
A resource can also be a queue, in which case it will start with [queue]
, e.g. [queue]appid-queuename
. (This is unambiguous as channel names may not begin with a [
). Similar wildcard rules apply, e.g. [queue]*
will match all queues.
You can also have a resource name of [*]*
, which will match both all queues and all channels.
Wildcards are also supported for operations, by requesting an operations list of ['*']
.
Capabilities example in code
If you want to see some live code examples of how capabilities work, take a look at our capabilities example.
Token request without capabilities example
Given an API key exists with the following capabilities:
{
"chat": ["publish", "subscribe", "presence"],
"status": ["subscribe"]
}
If token is requested without requiring any capabilities:
auth.requestToken(tokenCallback)
Then the token request is treated as requesting all capabilities, i.e. {"[*]*":["*"]}
), and all capabilities of the API key are assigned to the token. The capabilities for the issued token would be as follows:
{
"chat": ["publish", "subscribe", "presence"],
"status": ["subscribe"]
}
Token request with intersection of capabilities example
Given an API key exists with the following capabilities:
{
"chat:*": ["publish", "subscribe", "presence"],
"status": ["subscribe", "history"],
"alerts": ["subscribe"]
}
And a token is requested with the following explicit capabilities:
auth.requestToken({ capability: {
"chat:bob": ["subscribe"], // only "subscribe" intersects
"status": ["*"], // "*"" intersects with "subscribe"
"secret": ["publish", "subscribe"] // key does not have access to "secret" channel
}}, tokenCallback)
Then Ably will intersect the API key’s capabilities and the requested capabilities i.e. Ably will satisfy the token request’s capabilities as far as possible based on the capability of the issuing API key. The capabilities for the issued token would be as follows:
{
"chat:bob": ["subscribe"],
"status": ["subscribe", "history"]
}
Token request with incompatible capabilities
Given an API key exists with the following capabilities:
{
"chat": ["*"]
}
And a token is requested with the following explicit capabilities:
auth.requestToken({ capability: {
"status": ["*"]
}}, tokenCallback)
Then Ably will be unable to issue a token because the intersection of the requested capabilities and the API key’s capabilities is empty – they are entirely incompatible. In the example above, requestToken
will call the callback with an error.
Token request with wider channel scope than the key
Given an API key exists with the following capabilities:
{
"chat:team:*": ["publish"]
}
And a token is requested with the following explicit capabilities:
auth.requestToken({ capability: {
"chat:*": ["*"],
"status": ["*"]
}}, tokenCallback)
Then Ably will intersect the API key’s capabilities and the requested capabilities i.e. Ably will satisfy the token request’s capabilities as far as possible based on the capability of the issuing API key. The capabilities for the issued token would be as follows:
{
"chat:team:*": ["publish"]
}
See a working capabilities example.
Tokens
In the documentation, references to tokens typically refer to both TokenDetails
object that contain the token string or the token string itself. TokenDetails
objects are obtained when requesting tokens from the Ably service and contain not only the token string in the token
attribute, but also contain attributes describing the properties of the token.
TokenDetails type
TokenDetails
is a type providing details of the token string and its associated metadata.
PropertiesMembersAttributes
- tokenToken
- The token itself. A typical token string may appear like
xVLyHw.A-pwh7wYFnGvIy-3MSpomxTDHuovZvVNWT3JRYDEkC_pGI-0tA
Type:String
- expiresExpires
-
The time (in milliseconds since the epoch)The time at which this token expires
Type:Integer
Long Integer
DateTimeOffset
Time
NSDate
- issuedIssued
-
The time (in milliseconds since the epoch)The time at which this token was issued
Type:Integer
Long Integer
DateTimeOffset
Time
NSDate
- capabilityCapability
- The capability associated with this token. The capability is a a JSON stringified canonicalised representation of the resource paths and associated operations. Read more about authentication and capabilities
Type:String
Capability
- clientIdclient_idClientId
- The client ID, if any, bound to this token. If a client ID is included, then the token authenticates its bearer as that client ID, and the token may only be used to perform operations on behalf of that client ID. The client is then considered to be an identified client
Type:String
Methods
- expired?
- True when the token has expired
Type:Boolean
Methods
- is_expired()
- True when the token has expired
Type:Boolean
Methods
- IsValidToken()
- True if the token has not expired
Type:Boolean
Token request spec
The Ably REST and Realtime client libraries aim to make things as simple as possible so it is not necessary to understand all of the details of token requests to interact with the service and issue tokens for clients. If you wish to issue tokens or token requests, we recommend you start with the client library authentication documentation.
However, if you are using the REST token endpoint directly, or if you are creating token requests without the use of our client libraries, then the following specification will give you an in-depth understanding of how token requests work.
API key format
API keys are issued and managed from within your account dashboard. The API key string available in your dashboard is structured as a triple <app ID>:<key ID>:<key value>
, where:
- app ID
- (public) identifier for the application
- key ID
- (public) identifier for the key in question: this uniquely identifies the key and is a system-assigned, URL-safe, identifier
- key value
- (private) key “secret” string, system-generated, uniquely associated with this key
Token request format
A token request is made against the requestToken
endpoint, with a JSON token request in the request body. The token request comprises:
- the
keyName
comprising of the app ID and key ID such asxVLyHw.Nz6cuA
- a capability (i.e. a set of channel names/namespaces and, for each, a set of operations) which should be a subset of the set of capability associated with the key specified in
keyName
- optionally, a
clientId
thus identifying clients using this token and preventing them from identifying themselves with any otherclientId
- optionally, an expiry time or TTL, will default to 1 hour if not specified
- a timestamp to ensure token request is still valid
- a unique nonce string, randomly-generated by the client, to ensure the token request cannot be reused
A signed token request also contains:
- a signature, generated as an HMAC of each of the above components, using the key secret value.
Signed token requests can be used to request a token from Ably without an authenticated connection. The signature generated with the key secret confirms the authenticity of the token and can thus be “trusted” by Ably. As signed token requests can be issued without a request to Ably, a server with a valid API key can issue token requests directly to clients, and clients can in turn generate a token by sending the token request to Ably.
The receiving Ably server verifies the signature if present, the timestamp (which must be within 2 minutes of the current time), verifies that the nonce/timestamp combination has not been used previously, verifies that the requested validity period is permitted, and verifies that the requested capabilities are permitted for that token based on the key capabilities.
The server may choose to subset the capabilities based on the capabilities of the key.
The server replies with an access token, which is essentially a signed version of the resolved set of capabilities, plus other metadata associated with the token (such as expiry time).
This access token can then be used for subsequent REST requests or Realtime connections. If a clientId
was included in the request, then the token is associated with that clientId
, and may be used to identify that client in operations that require identification (e.g. joining a channel that requires identification, or publishing a message with a verified clientId
).
Parameter canonicalisation
The parameters of the token request are normalized/canonicalized as follows:
- keyName
- no action required
- ttl
- the decimal integer representation, without leading zeros, of the requested life of the token in seconds, if none is specified a default of 1 hour is used
- capability
- this is a canonicalised representation of the channel paths and associated operations in the capability. It is a JSON stringified value of a JavaScript object of the form:
{ "channel1": ["operation1a", "operation1b", "operation1c", ...], "channel2": ["operation2a", "operation2b", "operation2c", ...], ... }
with the following constraints:
- all whitespace is removed;
- channels are listed in forward lexicographic order;
- operations are listed in forward lexicographic order;
- there is no trailing comma on any list of array or object elements;
- all strings are quoted and escaped as per the JSON standard;
- for channel paths, the wildcard character
*
has special meaning. When the channel path is exactly"*"
, then all channels are matched. If however, a channel path ends with*
, then the path before the*
is used to match all channels in that namespace. For example, the channel path"user:*"
matches all channels in theuser
namespace such as"user:john"
and"user:matt"
. Find out more about channel namespaces; - for operations, the wildcard character
*
has special meaning. When the operation is exactly"*"
, then all operations are supported. Otherwise, a permitted operation string must be provided
- clientId
- the canonical form is the unquoted and unescaped string. In the case that no clientId is included in the request, the empty string is used.
- timestamp
- the decimal integer representation, without leading zeros, of the time of the of the request in seconds since the epoch.
- nonce
- an unquoted, unescaped random string of at least 16 characters.
Capability operations
The following capability operations are available for API keys and issued tokens.
- subscribe
- can subscribe to messages and presence state change messages on channels
- publish
- can publish messages to channels
- presence
- can register presence on a channel (enter, update and leave)
- history
- can retrieve message and presence state history on channels
- stats
- can retrieve current and historical usage statistics for an app
- push-subscribe
- can subscribe devices for push notifications
- push-admin
- can manage device registrations and push subscriptions for all devices in an app
See a working capabilities example, read understanding capabilities and token security above to get a more thorough overview of how capabilities can be used to secure your application along with working examples.
HMAC calculation
First the canonicalised request text, constructed as follows:
- start with the empty string
- for each of the following fields in order:
keyName
,ttl
,capabilities
,clientId
,timestamp
,nonce
, even when empty- append the canonicalised string value for that field
- append a newline (0xa) character.
Note that a newline character is added for each field, including any empty client ID string, and the last (nonce) field.
The resulting string must then the UTF8-encoded and then HMAC value is computed with hmac-sha-256 using the key secret value.
The HMAC value is then base-64 encoded.
Request body format
In the case of a signed token request, the request body is the JSON stringified representation of the object with the form:
{
keyName: "<app ID>:<key ID>",
ttl: <expiry in milliseconds>,
capability: "<capability string>",
clientId: "<client ID optional>",
timestamp: <timestamp as ms since epoch>,
nonce: "<random unique nonce>",
mac: "<base 64-encoded HMAC value>"
}
An unsigned token request is identical except that the mac property is omitted. Note that Basic authentication must be used in order to request a token with an unsigned request.
Response body format
If successful, the authorisation request returns the JSON stringified representation of an object containing the token:
{
token: "<token value>",
issued: <timestamp as ms since epoch>,
expires: <timestamp as ms since epoch>,
capability: "<canonical capability text>",
clientId: "<client ID optional>"
}
Example token requests
Unsigned token request example
curl -X POST "https://rest.ably.io/keys/xVLyHw.Nz6cuA/requestToken" \
--user "xVLyHw.Nz6cuA:skQTwKsMpLn7WWveOKVTKpbV-wn4LYD3H0l5Ljdw-KI" \
--header "Content-Type: application/json" \
--data '{
"keyName": "xVLyHw.Nz6cuA",
"ttl": "3600000",
"capability":
"{\"private\":[\"subscribe\",\"publish\",\"presence\"],\"*\":[\"subscribe\"]}",
"clientId": "unique_identifier",
"timestamp": _VAR_MS_SINCE_EPOCH_VAR_,
"nonce": "95e543b88299f6bae83df9b12fbd1ecd"
}'
Responds with JSON token:
{
"token": "xVLyHw.HHZNjgqmC-ACW....truncated",
"keyName": "xVLyHw.Nz6cuA",
"issued": 1449745478956,
"expires": 1449749078956,
"capability":
"{\"*\":[\"subscribe\"],\"private\":[\"presence\",\"publish\",\"subscribe\"]}",
"clientId": "unique_identifier"
}
Signed token request example
curl -X POST "https://rest.ably.io/keys/xVLyHw.Nz6cuA/requestToken" \
-H "Content-Type: application/json" \
--data '{
"keyName": "xVLyHw.-pwh7w",
"timestamp": 1656984785250,
"nonce": "8766715b961fe1e3bc89db4846b23c41",
"clientId": "unique_identifier",
"ttl": 43200000,
"mac": "I3dGRijwpr1OqxsGxp7JtES/COb4/a33gGuLRu0Ahug="
}'
Responds with JSON token:
{
"token": "xVLyHw.DTSukCRj1lis1sJltr...rhLRBcZgmXLf1FP8wKGrPYkkIs",
"keyName": "xVLyHw.Nz6cuA",
"issued": 1449745797497,
"expires": 1449749397497,
"capability": "{\"*\":[\"*\"]}"
}