Tokens
The token endpoint your browsers call, in Rails, Sinatra and anything that speaks Rack.
Your API key is your account, so it never goes to a browser. The browser asks
your server for a token request: signed with the key, scoped to what this
user may touch, and dead after its ttl.
Signing is local — no call to Blackevin — so your sign-in never depends on us
being reachable. The browser side is the JavaScript SDK with
authUrl: '/blackevin/token'; see
Authentication for the whole flow.
Rails
class BlackevinTokensController < ApplicationController
before_action :authenticate_user!
def show
render json: Blackevin::Rest::Auth.new.create_token_request(
client_id: current_user.id,
ttl: 1.hour.in_milliseconds,
capability: {
"team:#{current_user.team_id}" => %w[subscribe publish],
"presence:team:#{current_user.team_id}" => %w[presence]
}
)
end
endresource :blackevin_token, only: :show, path: 'blackevin/token'The token request renders to the wire shape by itself — camelCase keys, absent
fields omitted — so render json: needs no serializer.
Sinatra
require 'sinatra'
require 'blackevin'
get '/blackevin/token' do
halt 401 unless current_user
content_type :json
Blackevin::Rest::Auth.new.create_token_request(client_id: current_user.id).to_json
endHanami, Roda, a bare config.ru
Blackevin::TokenEndpoint is a Rack application, and the gem does not depend on
Rack to provide it. The block receives the Rack env and answers who is asking;
nil is a 401.
TOKENS = Blackevin::TokenEndpoint.new do |env|
user = env['warden']&.user
next unless user
{client_id: user.id, capability: {"team:#{user.team_id}" => %w[subscribe publish]}}
endmount TOKENS, at: '/blackevin/token' # Hanami, Rails
map('/blackevin/token') { run TOKENS } # config.ruIt answers GET and POST, sends cache-control: no-store, and builds a
Blackevin::Rest per request unless you pass rest:.
The arguments
| default | ||
|---|---|---|
client_id | none | who the token acts as. An Integer is fine; it is sent as a string |
ttl | one hour | milliseconds, 48 hours at most. 1.hour in seconds reads as 3.6 seconds |
capability | everything the key holds, minus amqp-subscribe | a Hash of channel pattern to operations, or a JSON String signed byte for byte |
Capabilities lists the operations and how patterns match. A token can never hold more than the key that signed it.
A token for the server itself
A server that wants to act as one client, inside a narrow capability, exchanges the request itself:
auth = Blackevin::Rest::Auth.new
details = auth.request_token(
auth.create_token_request(client_id: 'worker-1', capability: {'jobs:*' => %w[publish]})
)
worker = Blackevin::Rest.new(token: details.token)
details.expires_at # a Time
details.expired?A token request is single use: the node refuses a second exchange of the same one.