JWT identification
AnyCable provides support for JWT-based authentication and identification.
You can pass a properly structured token along the connection request to authorize the connection and set up identifiers (in terms of Action Cable). This approach brings the following benefits:
- Performance. No RPC call is required during the connection initiation, since we already have identification information. Thus, less load on the RPC server, much faster connection time (at least, 2x faster).
- Usability. Universal way of dealing with credentials (no need to deal with cookies for web and whatever else for mobile apps).
- Security. CSRF-safe by design. Configurable life time for tokens makes it easier to keep access under control.
Usage
See the demo of using JWT identification in a Rails app with AnyCable JS client library.
NOTE: Currently, we only support the HMAC signing algorithms.
First, you must enable JWT identification support in anycable-go
by configuring the following params:
- --jwt_id_key (
ANYCABLE_JWT_ID_KEY
): the encryption key used to verify tokens. - (Optional) --jwt_id_param (
ANYCABLE_JWT_ID_PARAM
): the name of a query string param or an HTTP header, which carries a token. The header name is prefixed withX-
. Default:jid
(and theX-JID
header correspondingly). - (Optional) --jwt_id_enforce (
ANYCABLE_JWT_ID_ENFORCE
): whether to require all connection requests to contain a token. Connections without a token would be rejected right away. If not set, the servers fallbacks to the RPC call (as w/o JWT enabled). Default: false.
A client must provide an identification token either via a query param or via an HTTP header (if possible). For example:
import { createCable } from '@anycable/web'
let cable = createCable('ws://cable.example.com/cable?jid=[JWT_TOKEN]')
The token MUST include the ext
claim with the JSON-encoded connection identifiers.
Generating tokens
Rails integration
Use anycable-rails-jwt gem to integration JWT identification into your Rails app.
Custom implementation
Here is an example Ruby code to generate tokens:
require "jwt"
require "json"
ENCRYPTION_KEY = "some-sercret-key"
# !!! Expiration is the responsibility of the token issuer
exp = Time.now.to_i + 30
# Provides the serialized values for identifiers (`identified_by` in Action Cable)
identifiers = {user_id: 42}
# JWT payload
payload = {ext: identifiers.to_json, exp: exp}
puts JWT.encode payload, ENCRYPTION_KEY, "HS256"
Handling expired tokens
🎥 Check out this AnyCasts episode to learn more about the expiration problem and how to solve it using anycable-client.
Whenever a server encounters a token that has expired, it rejects the connection and send the disconnect
message with reason: "token_expired"
. It's a client responsibility to handle this situation and refresh the token.
See, for example, how anycable-client handles this.