USPS JWT Authentication

Gem Version

Installation

Add the gem to your Gemfile:

gem 'usps-jwt_auth'

Then run the install task:

bundle exec rails usps:jwt:install

Configuration

Config options audience, is_admin, and find_member are required.

Usps::JwtAuth.configure do |config|
  # This will default to `Rails.env` if available.
  config.environment = 'development'

  # These will append to `Rails.root` if available.
  config.keys_path        = 'config/keys'
  config.public_keys_path = 'config/public_keys'

  # These options will default to the listed `ENV` variable if available.
  #
  # The ultimate defaults are listed to the right.
  #
  config.audience    = 'example'   # ENV['JWT_AUDIENCE']    # nil
  config.algorithm   = 'RS512'     # ENV['JWT_ALGORITHM']   # 'RS512'
  config.key_size    = 4096        # ENV['JWT_KEY_SIZE']    # 4096
  config.issuer_base = 'usps:1'    # ENV['JWT_ISSUER_BASE'] # 'usps:1'
  config.issuers     = ['admin:1'] # ENV['JWT_ISSUERS']     # []

  # Base URL of the shared public-key store (see "Public key resolution").
  config.public_keys_url = 'https://keys.aws.usps.org' # ENV['JWT_PUBLIC_KEYS_URL'] # nil

  # Optional. Called as `publisher.call(fingerprint, pem)` when an issuer writes a
  # new public key, so it can be pushed to the shared store. Defaults to nil (no-op).
  config.publisher = ->(fingerprint, pem) { S3.put("#{fingerprint}.pub", pem) }

  config.is_admin = ->(user) { Pundit.policy(user, :admin).admin? }
  config.find_member = ->(certificate) { Members::Member.find(certificate) }
end

Public key resolution

A token names its signing key by an SSH SHA256 fingerprint (the key claim), which is content-addressed — a fingerprint maps to exactly one key, forever.

When decoding, the gem first looks for <fingerprint>.pub under public_keys_path (the local cache). On a miss, if public_keys_url is set, it fetches <public_keys_url>/<fingerprint>.pub once, verifies the fetched key reproduces the fingerprint (so the store is an untrusted cache that cannot forge keys), caches it under public_keys_path, and uses it. With no public_keys_url set, only locally cached keys are used.

Issuers publish keys to that same store: set publisher and the gem pushes each newly written public key on encode. Because keys are content-addressed, rotation needs no consumer changes — a new key is published and consumers fetch it on first sight.

Usage

class ApplicationController < ActionController::Base
  include Usps::JwtAuth::Concern

  before_action :authenticate_user_from_jwt!
  # skip_before_action :authenticate_user_from_jwt!, only: %i[]

  # ...
end