Ruby (Rails / Sinatra)
Setup
# Gemfile
gem 'jwt'
gem 'faraday'
Rails example
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
AUTH_URL = ENV.fetch('AUTH_URL', 'http://auth:4000')
before_action :authenticate!
private
def authenticate!
token = cookies[:identsphere_at] || request.headers['Authorization']&.sub(/^Bearer /, '')
return render json: { error: 'unauthenticated' }, status: 401 unless token
jwks_response = Rails.cache.fetch('identsphere_jwks', expires_in: 5.minutes) do
Faraday.get("#{AUTH_URL}/.well-known/jwks.json").body
end
jwks = JSON.parse(jwks_response)
decoded, _header = JWT.decode(token, nil, true, algorithms: ['RS256'], jwks: { keys: jwks['keys'] })
@current_user = decoded
rescue JWT::DecodeError
render json: { error: 'invalid token' }, status: 401
end
end
Proxy login through Rails
# app/controllers/auth_controller.rb
class AuthController < ApplicationController
skip_before_action :authenticate!, only: [:login, :register]
def login
res = Faraday.post("#{AUTH_URL}/v1/auth/login") do |req|
req.headers['Content-Type'] = 'application/json'
req.body = request.raw_post
end
# forward Set-Cookie headers
res.headers.each do |k, v|
response.set_header('Set-Cookie', v) if k.downcase == 'set-cookie'
end
render plain: res.body, status: res.status
end
end
Sinatra
Same idea, leaner:
require 'sinatra'
require 'jwt'
require 'faraday'
AUTH_URL = ENV.fetch('AUTH_URL')
before do
next if request.path == '/auth/login'
token = request.cookies['identsphere_at']
halt 401 unless token
jwks_doc = JSON.parse(Faraday.get("#{AUTH_URL}/.well-known/jwks.json").body)
payload, _ = JWT.decode(token, nil, true, algorithms: ['RS256'], jwks: { keys: jwks_doc['keys'] })
@user = payload
rescue JWT::DecodeError
halt 401
end
get '/api/me' do
json user_id: @user['sub'], org_id: @user['org']
end
Caveats
Same as Pattern 2 elsewhere: revocation lag ~ JWKS cache TTL + access-token TTL.