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

app/controllers/blackevin_tokens_controller.rb
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
end
config/routes.rb
resource :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
end

Hanami, 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]}}
end
mount TOKENS, at: '/blackevin/token'         # Hanami, Rails
map('/blackevin/token') { run TOKENS }       # config.ru

It answers GET and POST, sends cache-control: no-store, and builds a Blackevin::Rest per request unless you pass rest:.

The arguments

default
client_idnonewho the token acts as. An Integer is fine; it is sent as a string
ttlone hourmilliseconds, 48 hours at most. 1.hour in seconds reads as 3.6 seconds
capabilityeverything the key holds, minus amqp-subscribea 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.

On this page