Module: StandardId::Routing
- Defined in:
- lib/standard_id/routing.rb
Overview
Route-mapper extensions, available inside Rails.application.routes.draw.
Constant Summary collapse
- WELL_KNOWN_DOCUMENTS =
Documents this helper can draw, mapped to their path and controller.
{ oauth_authorization_server: { path: "oauth-authorization-server", to: "standard_id/api/well_known/oauth_authorization_server#show" }, openid_configuration: { path: "openid-configuration", to: "standard_id/api/well_known/openid_configuration#show" }, jwks: { path: "jwks.json", to: "standard_id/api/well_known/jwks#show" } }.freeze
Class Method Summary collapse
-
.selected_documents(only: nil, except: nil) ⇒ Hash
The WELL_KNOWN_DOCUMENTS entries
only:/except:select.
Instance Method Summary collapse
-
#standard_id_well_known_routes(at:, only: nil, except: nil, extra_paths: [], path_inserted: true) ⇒ Object
Draw StandardId's discovery documents at the ORIGIN ROOT.
Class Method Details
.selected_documents(only: nil, except: nil) ⇒ Hash
Returns the WELL_KNOWN_DOCUMENTS entries only:/except: select.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/standard_id/routing.rb', line 103 def self.selected_documents(only: nil, except: nil) if only.present? && except.present? raise ArgumentError, "standard_id_well_known_routes accepts `only:` or `except:`, not both" end keys = WELL_KNOWN_DOCUMENTS.keys requested = Array(only).map(&:to_sym) + Array(except).map(&:to_sym) unknown = requested - keys if unknown.any? raise ArgumentError, "Unknown StandardId well-known document(s): #{unknown.join(', ')}. Valid: #{keys.join(', ')}" end selected = if only.present? Array(only).map(&:to_sym) elsif except.present? keys - Array(except).map(&:to_sym) else keys end WELL_KNOWN_DOCUMENTS.slice(*selected) end |
Instance Method Details
#standard_id_well_known_routes(at:, only: nil, except: nil, extra_paths: [], path_inserted: true) ⇒ Object
Draw StandardId's discovery documents at the ORIGIN ROOT.
# config/routes.rb
Rails.application.routes.draw do
standard_id_well_known_routes at: "/api/v1"
namespace :api do
mount StandardId::ApiEngine, at: "/", as: :standard_id_api
end
end
Why the gem cannot just route these itself
The engine's own .well-known routes live INSIDE its mount, so an app
mounting ApiEngine at /api/v1 serves them at
/api/v1/.well-known/openid-configuration. But RFC 8615 clients probe the
origin root, which is outside any engine mount — a mapper helper in the
host's routes file is the only place these can be drawn.
The path-inserted form, and why it is not optional
For each document this draws BOTH:
/.well-known/oauth-authorization-server
/.well-known/oauth-authorization-server/api/v1 <- RFC 8414 §3.1
The second is the spec's form for an issuer that carries a path: the
well-known segment is INSERTED BEFORE the issuer's path rather than appended
to it. It looks redundant and it is not. It is the URL Claude Code actually
requests in production against a path-carrying issuer; without it the client
404s, falls back to issuer-relative defaults (hitting the engine's real
/authorize instead of a host audience shim), and every subsequent
authenticated request 401s. That failure is remote from its cause and hard
to diagnose, which is why the route is drawn for you rather than documented
as an extra step.
Both forms serve the same document. The mount path is stamped into the route defaults so DiscoveryResolver can build endpoint URLs off it — a root route has no SCRIPT_NAME to read it from.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/standard_id/routing.rb', line 74 def standard_id_well_known_routes(at:, only: nil, except: nil, extra_paths: [], path_inserted: true) mount_path = StandardId::Oauth::DiscoveryResolver.normalize_path(at) documents = StandardId::Routing.selected_documents(only: only, except: except) suffixes = path_inserted ? ([mount_path] + Array(extra_paths)).map { |p| StandardId::Oauth::DiscoveryResolver.normalize_path(p) } : [] suffixes = suffixes.reject(&:empty?).uniq defaults = { StandardId::Oauth::DiscoveryResolver::MOUNT_PATH_PARAM => mount_path } scope ".well-known" do documents.each do |name, config| get config[:path], to: config[:to], as: :"standard_id_root_#{name}", defaults: defaults # jwks.json is a concrete file path, not a metadata document whose # location RFC 8414 §3.1 relocates — a path-inserted variant of it # would advertise a URL no spec describes. next if name == :jwks suffixes.each_with_index do |suffix, index| get "#{config[:path]}#{suffix}", to: config[:to], as: :"standard_id_root_#{name}_at_#{index}", defaults: defaults end end end end |