Skip to content

Rails engine allowing apps to act as their own OAuth 2.1 provider.

License

Notifications You must be signed in to change notification settings

dickdavis/token_authority

Repository files navigation

TokenAuthority

Rails engine allowing apps to act as their own OAuth 2.1 provider. The goal of this project is to make standards-based authorization as simple as possible.

Status Standard
OAuth 2.1 IETF DRAFT
JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens (RFC 9068)
OAuth 2.0 Authorization Server Metadata (RFC 8414)
OAuth 2.0 Protected Resource Metadata (RFC 9728)
OAuth 2.0 Resource Indicators (RFC 8707)
OAuth 2.0 Dynamic Client Registration Protocol (RFC 7591)
OAuth Client ID Metadata Documents

Usage

TokenAuthority is simple to install and configure. For MCP server developers, see the MCP Quickstart guide for a complete working example.

Installation

Add this line to your application's Gemfile:

gem "token_authority"

Install the gem, generate the required set-up files, and run the migration:

$ bundle
$ bin/rails generate token_authority:install
$ bin/rails db:migrate

See the Installation Guide for generator options and custom configurations.

Configuration

Configure TokenAuthority in the generated initializer. TokenAuthority is configured with dynamic client registration, client metadata documents, and resource indicators enabled by default. The following represents a minimal configuration:

# config/initializers/token_authority.rb
TokenAuthority.configure do |config|
  # The secret key used for signing JWT tokens
  config.secret_key = Rails.application.credentials.secret_key_base

  # Define available scopes (required by default)
  config.scopes = {
    "read" => "Read your data",
    "write" => "Create and modify your data"
  }

  # Define protected resources (required by default)
  # :resource is used as the audience (aud) claim in tokens
  # :authorization_servers provides the issuer (iss) claim
  config.resources = {
    api: {
      resource: "https://example.com/api",
      resource_name: "My API",
      scopes_supported: %w[read write],
      authorization_servers: ["https://example.com"],
      bearer_methods_supported: ["header"],
      jwks_uri: "https://example.com/.well-known/jwks.json",
      resource_documentation: "https://example.com/docs/api",
      resource_policy_uri: "https://example.com/privacy",
      resource_tos_uri: "https://example.com/terms"
    }
  }
end

See the Configuration Reference for all available options.

Mount the Engine

Add the engine routes to your config/routes.rb:

Rails.application.routes.draw do
  token_authority_auth_server_routes
  token_authority_protected_resource_route
end

This exposes:

  • RFC 8414 Authorization Server Metadata at /.well-known/oauth-authorization-server
  • RFC 9728 Protected Resource Metadata at /.well-known/oauth-protected-resource
  • OAuth endpoints at /oauth/authorize, /oauth/token, etc.

To mount the engine at a different path, use the at option:

Rails.application.routes.draw do
  token_authority_auth_server_routes(at: "/auth")
  token_authority_protected_resource_route
end

For applications with multiple protected resources, each resource must be on its own subdomain. This is because RFC 9728 defines a fixed well-known path (/.well-known/oauth-protected-resource) that can only exist once per host:

Rails.application.routes.draw do
  token_authority_auth_server_routes

  constraints subdomain: "api" do
    token_authority_protected_resource_route
  end

  constraints subdomain: "mcp" do
    token_authority_protected_resource_route
  end
end

Development Note: Rails subdomain constraints require a real domain with proper DNS resolution. Use lvh.me (which resolves to 127.0.0.1) for local development: mcp.lvh.me:3000, api.lvh.me:3000, etc. You'll also need to allow these hosts in your development config:

# config/environments/development.rb
config.hosts << /.*\.lvh\.me/

See the Installation Guide for details.

User Consent

Before issuing authorization codes, TokenAuthority displays a consent screen where users can approve or deny access to OAuth clients. The consent views are fully customizable and the layout is configurable—see Customizing Views for details.

The consent screen requires user authentication. Your authenticatable_controller must provide two methods:

  • authenticate_user! - Ensures the user is logged in (redirects to login if not)
  • current_user - Returns the authenticated user

If you use Devise, these methods are already available on ApplicationController. For other authentication systems, see User Authentication.

Protecting API Endpoints

Use the TokenAuthentication concern to validate access tokens:

class Api::V1::UsersController < ActionController::API
  include TokenAuthority::TokenAuthentication

  before_action :require_read_scope

  def current
    render json: { id: token_user.id, email: token_user.email }
  end

  private

  def require_read_scope
    return if token_scope.include?("read")

    render json: { error: "insufficient_scope" }, status: :forbidden
  end
end

The concern automatically validates the access token on every request and provides:

  • token_user - Returns the authenticated user
  • token_scope - Returns an array of scope tokens (e.g., ["read", "write"]), or [] if no scopes

See Protecting API Endpoints for error handling details.

Event Logging

TokenAuthority emits structured events using Rails 8.1's event reporting system for monitoring, debugging, and auditing. Events cover the full OAuth lifecycle:

  • Authorization requests and consent
  • Token exchanges, refreshes, and revocations
  • Client and token authentication
  • Security events (e.g., token theft detection)

Event logging is enabled by default. Events are automatically logged to Rails.logger:

[TokenAuthority] token_authority.authorization.request.received client_id="..." client_type="public" ...
[TokenAuthority] token_authority.token.exchange.completed client_id="..." user_id=42 session_id=1 ...
[TokenAuthority] token_authority.security.token.theft_detected client_id="..." user_id=42 ...

See Event Logging for the full event reference and custom subscriber examples.

Instrumentation

TokenAuthority emits ActiveSupport::Notifications instrumentation events for performance monitoring. These events provide timing data that APM tools (New Relic, Datadog, Skylight) automatically capture.

Instrumentation is enabled by default. Events are automatically logged to Rails.logger:

[TokenAuthority::Instrumentation] token_authority.session.create (15.2ms)
[TokenAuthority::Instrumentation] token_authority.jwt.encode (0.4ms) token_size=312
[TokenAuthority::Instrumentation] token_authority.client.resolve (0.5ms) client_type="registered"

See Instrumentation for the full event reference and custom subscriber examples.

Learn More

Development

Clone the repository and install dependencies:

git clone https://github.com/dickdavis/token-authority.git
cd token-authority
bundle install

Set up git hooks:

bundle exec lefthook install

Run the test suite:

bundle exec rspec

Run the linter:

bundle exec standardrb

Generate documentation:

bundle exec yard

For manual testing with the dummy app, see Manual Testing.

Releasing

  1. Update the version number in lib/token_authority/version.rb
  2. Commit the version change: git commit -am "Bump version to X.Y.Z"
  3. Run the release task: rake release

This will create a git tag, push the tag to GitHub, and publish the gem to RubyGems.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/dickdavis/token-authority.

License

The gem is available as open source under the terms of the MIT License.

About

Rails engine allowing apps to act as their own OAuth 2.1 provider.

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Packages

No packages published

Languages