token_authority 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -1
  3. data/README.md +53 -15
  4. data/app/controllers/concerns/token_authority/initial_access_token_authentication.rb +2 -2
  5. data/app/controllers/concerns/token_authority/token_authentication.rb +1 -1
  6. data/app/controllers/token_authority/protected_resource_metadata_controller.rb +39 -0
  7. data/app/helpers/token_authority/authorization_grants_helper.rb +2 -3
  8. data/app/models/concerns/token_authority/claim_validatable.rb +4 -4
  9. data/app/models/concerns/token_authority/resourceable.rb +2 -2
  10. data/app/models/concerns/token_authority/session_creatable.rb +1 -1
  11. data/app/models/token_authority/access_token.rb +26 -18
  12. data/app/models/token_authority/access_token_request.rb +1 -1
  13. data/app/models/token_authority/authorization_request.rb +2 -2
  14. data/app/models/token_authority/authorization_server_metadata.rb +4 -4
  15. data/app/models/token_authority/client.rb +4 -4
  16. data/app/models/token_authority/client_metadata_document.rb +2 -2
  17. data/app/models/token_authority/client_registration_request.rb +5 -5
  18. data/app/models/token_authority/jwks_fetcher.rb +1 -1
  19. data/app/models/token_authority/protected_resource_metadata.rb +110 -31
  20. data/app/models/token_authority/refresh_token.rb +2 -2
  21. data/app/models/token_authority/refresh_token_request.rb +1 -1
  22. data/app/views/token_authority/authorization_grants/new.html.erb +2 -2
  23. data/lib/generators/token_authority/install/templates/token_authority.rb +100 -114
  24. data/lib/token_authority/configuration.rb +345 -175
  25. data/lib/token_authority/errors.rb +29 -0
  26. data/lib/token_authority/routing/constraints.rb +2 -2
  27. data/lib/token_authority/routing/routes.rb +74 -16
  28. data/lib/token_authority/version.rb +1 -1
  29. data/lib/token_authority.rb +2 -2
  30. metadata +2 -2
  31. data/app/controllers/token_authority/resource_metadata_controller.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0e6a0810b0a8015aea5ada0e1bbb87596875c7641042281dac811fb3fc8bf0c
4
- data.tar.gz: 76eda2d8230aca93850b6e025c0be79cfac68da48159bbcb7f34e9f102229649
3
+ metadata.gz: 8989a407b702176b74b3e121e4463dfc28e858097716c4cf6752f2ab54c78871
4
+ data.tar.gz: 1ecdecd8785d3f214e64359b4ab7e43ecb8630eb2af9237bb5a4d8a604d5227d
5
5
  SHA512:
6
- metadata.gz: '069ecd5dc4b50ca9929192ea6d22084db1e5019fa4df90ebcacbc900f4385d88452f3abc108deaa3ce29a88a7c212095dbfc4f056968409983189c0f97292130'
7
- data.tar.gz: 45ae89f8b90abffacc828544b9c910c40801cf93b65ce5a362faa28b01a242a565233d771f1a56b64e82765a89452ec4a45aacab42ce7883d98b120dbc11c3aa
6
+ metadata.gz: b3b4bbc0e464d6781255efe9b523d753abbe78843c5bec77c5057861eef157b112e099c4afbd8a073ef0f1d1f69fff1946f27dddb6d0ce95a9a91bb3e0bc6c34
7
+ data.tar.gz: 0615a3d781b577ebb433bc5e01cae57827a8fe5bad6a6866092ea44438526e81090c28a39b708120150610c34a544fb4a405f8d9331f16e18f7943a27e55df3a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-01-24
4
+
5
+ ### Added
6
+
7
+ - Added support for multiple protected resources; applications can now define multiple protected resources under different subdomain constraints.
8
+ - Added `issuer_url` method that returns either `token_issuer_url` or derives from `authorization_servers`
9
+ - Added validation requiring either `token_issuer_url` or `:authorization_servers` on at least one resource
10
+
11
+ ### Breaking
12
+
13
+ - Replaced `rfc_9728_*` config options with `config.resources` hash (keyed by symbol)
14
+ - Replaced `token_authority_routes` with `token_authority_auth_server_routes` and `token_authority_protected_resource_route`
15
+ - Removed `rfc_8707_resources` config option; resource allowlist is now derived from `config.resources`
16
+ - Renamed `rfc_8707_require_resource` to `require_resource`
17
+ - Renamed `rfc_8707_enabled?` method to `resources_enabled?`
18
+ - Changed default for `require_scope` from `false` to `true`
19
+ - Changed default for `require_resource` from `false` to `true`
20
+ - Changed default for `token_audience_url` from application URL to `nil`
21
+ - Changed default for `dcr_enabled` from `false` to `true`
22
+ - Changed default for `token_issuer_url` from required to `nil`
23
+ - When `token_audience_url` is nil, the `:resource` URL is used as the audience claim
24
+ - When `token_issuer_url` is nil, it's derived from the first resource's `:authorization_servers`
25
+ - Renamed configuration options to remove RFC number prefixes:
26
+ - `rfc_9068_audience_url` → `token_audience_url`
27
+ - `rfc_9068_issuer_url` → `token_issuer_url`
28
+ - `rfc_9068_default_access_token_duration` → `default_access_token_duration`
29
+ - `rfc_9068_default_refresh_token_duration` → `default_refresh_token_duration`
30
+ - `rfc_8414_service_documentation` → `authorization_server_documentation`
31
+ - `rfc_7591_enabled` → `dcr_enabled`
32
+ - `rfc_7591_require_initial_access_token` → `dcr_require_initial_access_token`
33
+ - `rfc_7591_initial_access_token_validator` → `dcr_initial_access_token_validator`
34
+ - `rfc_7591_allowed_grant_types` → `dcr_allowed_grant_types`
35
+ - `rfc_7591_allowed_response_types` → `dcr_allowed_response_types`
36
+ - `rfc_7591_allowed_scopes` → `dcr_allowed_scopes`
37
+ - `rfc_7591_allowed_token_endpoint_auth_methods` → `dcr_allowed_token_endpoint_auth_methods`
38
+ - `rfc_7591_client_secret_expiration` → `dcr_client_secret_expiration`
39
+ - `rfc_7591_software_statement_jwks` → `dcr_software_statement_jwks`
40
+ - `rfc_7591_software_statement_required` → `dcr_software_statement_required`
41
+ - `rfc_7591_jwks_cache_ttl` → `dcr_jwks_cache_ttl`
42
+
43
+ ## [0.2.1] - 2025-01-24
44
+
45
+ ### Fixes
46
+
47
+ - Implemented support for all mandatory access token JWT claims
48
+ - Disable turbo on consent screen to allow redirects
49
+
50
+ ### Documentation
51
+
52
+ - Update README to include link to MCP Quickstart guide.
53
+
3
54
  ## [0.2.0] - 2025-01-23
4
55
 
5
56
  - Implemented support for OAuth 2.1 authorization flows and JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens (RFC 9068).
@@ -18,6 +69,8 @@
18
69
 
19
70
  - Initial release
20
71
 
21
- [Unreleased]: https://github.com/dickdavis/token_authority/compare/v0.2.0...HEAD
72
+ [Unreleased]: https://github.com/dickdavis/token_authority/compare/v0.3.0...HEAD
73
+ [0.2.1]: https://github.com/dickdavis/token_authority/compare/v0.2.1...v0.3.0
74
+ [0.2.1]: https://github.com/dickdavis/token_authority/compare/v0.2.0...v0.2.1
22
75
  [0.2.0]: https://github.com/dickdavis/token_authority/compare/v0.1.0...v0.2.0
23
76
  [0.1.0]: https://github.com/dickdavis/token_authority/releases/tag/v0.1.0
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # TokenAuthority
2
2
 
3
- Rails engine allowing apps to act as their own OAuth 2.1 provider. The goal of this project is to make authorization dead simple for MCP server developers.
4
-
5
- This project aims to implement the OAuth standards specified in the [MCP Authorization Specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#standards-compliance).
3
+ 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.
6
4
 
7
5
  | Status | Standard |
8
6
  |--------|----------|
@@ -16,7 +14,7 @@ This project aims to implement the OAuth standards specified in the [MCP Authori
16
14
 
17
15
  ## Usage
18
16
 
19
- TokenAuthority is simple to install and configure.
17
+ TokenAuthority is simple to install and configure. For MCP server developers, see the [MCP Quickstart](https://github.com/dickdavis/token_authority/wiki/MCP-Quickstart) guide for a complete working example.
20
18
 
21
19
  ### Installation
22
20
 
@@ -38,22 +36,35 @@ See the [Installation Guide](https://github.com/dickdavis/token_authority/wiki/I
38
36
 
39
37
  ### Configuration
40
38
 
41
- Configure TokenAuthority in the generated initializer. The following represents a minimal configuration:
39
+ 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:
42
40
 
43
41
  ```ruby
44
42
  # config/initializers/token_authority.rb
45
43
  TokenAuthority.configure do |config|
46
- # The secret key used for encryption/decryption
44
+ # The secret key used for signing JWT tokens
47
45
  config.secret_key = Rails.application.credentials.secret_key_base
48
- # The URI for the protected resource (to be included in tokens and metadata)
49
- config.rfc_9068_audience_url = "https://example.com/api/"
50
- # The URI for the authorization server (to be included in tokens and metadata)
51
- config.rfc_9068_issuer_url = "https://example.com/"
52
- # Define available scopes and their descriptions (shown on consent screen)
46
+
47
+ # Define available scopes (required by default)
53
48
  config.scopes = {
54
49
  "read" => "Read your data",
55
- "write" => "Create and modify your data",
56
- "delete" => "Delete your data"
50
+ "write" => "Create and modify your data"
51
+ }
52
+
53
+ # Define protected resources (required by default)
54
+ # :resource is used as the audience (aud) claim in tokens
55
+ # :authorization_servers provides the issuer (iss) claim
56
+ config.resources = {
57
+ api: {
58
+ resource: "https://example.com/api",
59
+ resource_name: "My API",
60
+ scopes_supported: %w[read write],
61
+ authorization_servers: ["https://example.com"],
62
+ bearer_methods_supported: ["header"],
63
+ jwks_uri: "https://example.com/.well-known/jwks.json",
64
+ resource_documentation: "https://example.com/docs/api",
65
+ resource_policy_uri: "https://example.com/privacy",
66
+ resource_tos_uri: "https://example.com/terms"
67
+ }
57
68
  }
58
69
  end
59
70
  ```
@@ -66,7 +77,8 @@ Add the engine routes to your `config/routes.rb`:
66
77
 
67
78
  ```ruby
68
79
  Rails.application.routes.draw do
69
- token_authority_routes
80
+ token_authority_auth_server_routes
81
+ token_authority_protected_resource_route
70
82
  end
71
83
  ```
72
84
 
@@ -79,10 +91,36 @@ To mount the engine at a different path, use the `at` option:
79
91
 
80
92
  ```ruby
81
93
  Rails.application.routes.draw do
82
- token_authority_routes(at: "/auth")
94
+ token_authority_auth_server_routes(at: "/auth")
95
+ token_authority_protected_resource_route
83
96
  end
84
97
  ```
85
98
 
99
+ 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:
100
+
101
+ ```ruby
102
+ Rails.application.routes.draw do
103
+ token_authority_auth_server_routes
104
+
105
+ constraints subdomain: "api" do
106
+ token_authority_protected_resource_route
107
+ end
108
+
109
+ constraints subdomain: "mcp" do
110
+ token_authority_protected_resource_route
111
+ end
112
+ end
113
+ ```
114
+
115
+ > **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:
116
+ >
117
+ > ```ruby
118
+ > # config/environments/development.rb
119
+ > config.hosts << /.*\.lvh\.me/
120
+ > ```
121
+ >
122
+ > See the [Installation Guide](https://github.com/dickdavis/token_authority/wiki/Installation-Guide#subdomain-development-setup) for details.
123
+
86
124
  ### User Consent
87
125
 
88
126
  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](https://github.com/dickdavis/token_authority/wiki/Customizing-Views) for details.
@@ -13,14 +13,14 @@ module TokenAuthority
13
13
  private
14
14
 
15
15
  def initial_access_token_required?
16
- TokenAuthority.config.rfc_7591_require_initial_access_token
16
+ TokenAuthority.config.dcr_require_initial_access_token
17
17
  end
18
18
 
19
19
  def authenticate_initial_access_token
20
20
  token = extract_bearer_token
21
21
  raise TokenAuthority::InvalidInitialAccessTokenError if token.blank?
22
22
 
23
- validator = TokenAuthority.config.rfc_7591_initial_access_token_validator
23
+ validator = TokenAuthority.config.dcr_initial_access_token_validator
24
24
  raise TokenAuthority::InvalidInitialAccessTokenError unless validator&.call(token)
25
25
  end
26
26
 
@@ -84,7 +84,7 @@ module TokenAuthority
84
84
  # @return [User] the user from the configured user_class
85
85
  # @api private
86
86
  def token_user
87
- @token_user ||= TokenAuthority.config.user_class.constantize.find(@decoded_token.user_id)
87
+ @token_user ||= TokenAuthority.config.user_class.constantize.find(@decoded_token.sub)
88
88
  end
89
89
 
90
90
  # Returns the scopes granted in the authenticated token.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TokenAuthority
4
+ ##
5
+ # Serves RFC 9728 Protected Resource Metadata at /.well-known/oauth-protected-resource
6
+ #
7
+ # This controller enables OAuth clients to discover metadata about protected resources
8
+ # without requiring manual configuration. Clients can query this endpoint to learn:
9
+ # - Which authorization servers issue tokens for this resource
10
+ # - What scopes are supported
11
+ # - How to present bearer tokens (header vs body)
12
+ # - Where to find public keys for token verification
13
+ #
14
+ # The subdomain extraction strategy supports multi-tenant deployments where different
15
+ # subdomains represent different protected resources (e.g., api.example.com vs
16
+ # mcp.example.com). Returns 404 if no configuration exists for the subdomain.
17
+ #
18
+ # @see https://www.rfc-editor.org/rfc/rfc9728.html RFC 9728
19
+ # @since 0.3.0
20
+ class ProtectedResourceMetadataController < ActionController::API
21
+ # Returns metadata for the protected resource identified by request subdomain.
22
+ #
23
+ # The subdomain is extracted from the request and used to look up configuration.
24
+ # For requests without a subdomain (bare domain), uses the default protected_resource
25
+ # configuration if present. This allows both single-resource and multi-resource
26
+ # deployments with the same code.
27
+ #
28
+ # @return [JSON] RFC 9728 compliant metadata response
29
+ # @return [HTTP 404] if no configuration exists for this subdomain
30
+ def show
31
+ metadata = ProtectedResourceMetadata.new(resource: request.subdomain)
32
+ render json: metadata.to_h
33
+ rescue ResourceNotConfiguredError
34
+ # Return 404 rather than 500 because this is a client error: they're querying
35
+ # a subdomain that hasn't been configured as a protected resource
36
+ head :not_found
37
+ end
38
+ end
39
+ end
@@ -3,14 +3,13 @@
3
3
  module TokenAuthority
4
4
  module AuthorizationGrantsHelper
5
5
  # Returns a human-friendly display name for a resource URI.
6
- # Looks up the URI in the configured rfc_8707_resources mapping.
6
+ # Looks up the URI in the resource registry derived from protected resources.
7
7
  # Falls back to the URI itself if no mapping is configured.
8
8
  #
9
9
  # @param resource_uri [String] The resource URI
10
10
  # @return [String] The display name or the URI if no mapping exists
11
11
  def resource_display_name(resource_uri)
12
- resources = TokenAuthority.config.rfc_8707_resources || {}
13
- resources[resource_uri] || resource_uri
12
+ TokenAuthority.config.resource_registry[resource_uri] || resource_uri
14
13
  end
15
14
 
16
15
  # Returns a human-friendly display name for a scope.
@@ -21,8 +21,8 @@ module TokenAuthority
21
21
  extend ActiveSupport::Concern
22
22
 
23
23
  # JWT claims that should trigger session revocation when invalid.
24
- # These represent security violations like wrong audience or issuer.
25
- REVOCABLE_CLAIMS = %i[aud iss user_id].freeze
24
+ # These represent security violations like wrong audience, issuer, or subject.
25
+ REVOCABLE_CLAIMS = %i[aud iss sub].freeze
26
26
 
27
27
  # JWT claims that should trigger session expiration when invalid.
28
28
  # Currently only includes the exp (expiration time) claim.
@@ -37,7 +37,7 @@ module TokenAuthority
37
37
 
38
38
  validates :jti, presence: true
39
39
 
40
- validates :aud, presence: true, format: {with: /\A#{TokenAuthority.config.rfc_9068_audience_url}*/}
40
+ validates :aud, presence: true, format: {with: /\A#{TokenAuthority.config.audience_url}*/}
41
41
 
42
42
  validates :exp, presence: true
43
43
  validate do
@@ -46,7 +46,7 @@ module TokenAuthority
46
46
  errors.add(:exp, :expired) if Time.zone.now > Time.zone.at(exp)
47
47
  end
48
48
 
49
- validates :iss, presence: true, format: {with: /\A#{TokenAuthority.config.rfc_9068_issuer_url}\z/}
49
+ validates :iss, presence: true, format: {with: /\A#{TokenAuthority.config.issuer_url}\z/}
50
50
 
51
51
  after_validation :expire_token_authority_session, if: :errors_for_expirable_claims?
52
52
  after_validation :revoke_token_authority_session, if: :errors_for_revocable_claims?
@@ -88,9 +88,9 @@ module TokenAuthority
88
88
  # @return [Boolean] true if all resources are allowed
89
89
  # @api private
90
90
  def allowed_resources?
91
- return true unless TokenAuthority.config.rfc_8707_enabled?
91
+ return true unless TokenAuthority.config.resources_enabled?
92
92
 
93
- resources.all? { |uri| TokenAuthority.config.rfc_8707_resources.key?(uri) }
93
+ resources.all? { |uri| TokenAuthority.config.resource_registry.key?(uri) }
94
94
  end
95
95
 
96
96
  # Checks if the current resources are a subset of the granted resources.
@@ -78,7 +78,7 @@ module TokenAuthority
78
78
 
79
79
  instrument("session.create") do
80
80
  access_token_expiration = client.access_token_duration.seconds.from_now.to_i
81
- access_token = TokenAuthority::AccessToken.default(user_id:, exp: access_token_expiration, resources:, scopes:)
81
+ access_token = TokenAuthority::AccessToken.default(user_id:, client_id: client.public_id, exp: access_token_expiration, resources:, scopes:)
82
82
 
83
83
  refresh_token_expiration = client.refresh_token_duration.seconds.from_now.to_i
84
84
  refresh_token = TokenAuthority::RefreshToken.default(exp: refresh_token_expiration, resources:, scopes:)
@@ -8,8 +8,7 @@ module TokenAuthority
8
8
  # (JWT ID) is stored in the Session model for revocation lookups.
9
9
  #
10
10
  # The tokens follow RFC 9068 JWT Profile for OAuth Access Tokens, including
11
- # standard claims (iss, aud, exp, iat, jti) plus custom claims like user_id
12
- # and scope.
11
+ # standard claims (iss, sub, aud, exp, iat, jti, client_id) plus the scope claim.
13
12
  #
14
13
  # This is an ActiveModel object (not ActiveRecord) that provides validation
15
14
  # and serialization of JWT claims without database persistence.
@@ -18,6 +17,7 @@ module TokenAuthority
18
17
  # token = TokenAuthority::AccessToken.default(
19
18
  # exp: 5.minutes.from_now,
20
19
  # user_id: 42,
20
+ # client_id: "550e8400-e29b-41d4-a716-446655440000",
21
21
  # resources: ["https://api.example.com"],
22
22
  # scopes: ["read", "write"]
23
23
  # )
@@ -31,17 +31,22 @@ module TokenAuthority
31
31
  class AccessToken
32
32
  include TokenAuthority::ClaimValidatable
33
33
 
34
- # @!attribute [rw] user_id
35
- # The ID of the user this token grants access for.
36
- # @return [Integer]
37
- attr_accessor :user_id
34
+ # @!attribute [rw] sub
35
+ # The subject identifier (resource owner) per RFC 9068.
36
+ # @return [String]
37
+ attr_accessor :sub
38
+
39
+ # @!attribute [rw] client_id
40
+ # The OAuth client identifier per RFC 9068/RFC 8693.
41
+ # @return [String]
42
+ attr_accessor :client_id
38
43
 
39
44
  # @!attribute [rw] scope
40
45
  # Space-separated list of OAuth scopes granted to this token.
41
46
  # @return [String, nil]
42
47
  attr_accessor :scope
43
48
 
44
- validates :user_id, presence: true, comparison: {equal_to: :user_id_from_token_authority_session}
49
+ validates :sub, presence: true, comparison: {equal_to: :sub_from_token_authority_session}
45
50
 
46
51
  # Creates a new access token with default claims per RFC 9068.
47
52
  #
@@ -49,7 +54,8 @@ module TokenAuthority
49
54
  # otherwise falls back to the configured default audience URL.
50
55
  #
51
56
  # @param exp [Time, Integer] token expiration time
52
- # @param user_id [Integer] the user ID
57
+ # @param user_id [Integer] the user ID (converted to string for sub claim)
58
+ # @param client_id [String] the OAuth client identifier
53
59
  # @param resources [Array<String>] resource indicators (RFC 8707)
54
60
  # @param scopes [Array<String>] OAuth scopes to include
55
61
  #
@@ -59,15 +65,16 @@ module TokenAuthority
59
65
  # token = AccessToken.default(
60
66
  # exp: 5.minutes.from_now,
61
67
  # user_id: 123,
68
+ # client_id: "550e8400-e29b-41d4-a716-446655440000",
62
69
  # resources: ["https://api.example.com"],
63
70
  # scopes: ["read", "write"]
64
71
  # )
65
- def self.default(exp:, user_id:, resources: [], scopes: [])
72
+ def self.default(exp:, user_id:, client_id:, resources: [], scopes: [])
66
73
  # Use resources for aud claim if provided, otherwise fall back to config
67
74
  aud = if resources.any?
68
75
  (resources.size == 1) ? resources.first : resources
69
76
  else
70
- TokenAuthority.config.rfc_9068_audience_url
77
+ TokenAuthority.config.audience_url
71
78
  end
72
79
 
73
80
  scope_claim = scopes.any? ? scopes.join(" ") : nil
@@ -76,9 +83,10 @@ module TokenAuthority
76
83
  aud:,
77
84
  exp:,
78
85
  iat: Time.zone.now.to_i,
79
- iss: TokenAuthority.config.rfc_9068_issuer_url,
86
+ iss: TokenAuthority.config.issuer_url,
80
87
  jti: SecureRandom.uuid,
81
- user_id:,
88
+ sub: user_id.to_s,
89
+ client_id:,
82
90
  scope: scope_claim
83
91
  )
84
92
  end
@@ -93,7 +101,7 @@ module TokenAuthority
93
101
  #
94
102
  # @example
95
103
  # token = AccessToken.from_token(jwt_string)
96
- # user_id = token.user_id
104
+ # subject = token.sub
97
105
  def self.from_token(token)
98
106
  new(TokenAuthority::JsonWebToken.decode(token))
99
107
  end
@@ -104,7 +112,7 @@ module TokenAuthority
104
112
  #
105
113
  # @return [Hash] the JWT claims
106
114
  def to_h
107
- {aud:, exp:, iat:, iss:, jti:, user_id:, scope:}.compact
115
+ {aud:, exp:, iat:, iss:, jti:, sub:, client_id:, scope:}.compact
108
116
  end
109
117
 
110
118
  # Encodes the token as a signed JWT string.
@@ -116,12 +124,12 @@ module TokenAuthority
116
124
 
117
125
  private
118
126
 
119
- # Returns the user_id from the associated session for validation.
127
+ # Returns the subject (user_id as string) from the associated session for validation.
120
128
  #
121
- # @return [Integer, nil]
129
+ # @return [String, nil]
122
130
  # @api private
123
- def user_id_from_token_authority_session
124
- token_authority_session&.user_id
131
+ def sub_from_token_authority_session
132
+ token_authority_session&.user_id&.to_s
125
133
  end
126
134
  end
127
135
  end
@@ -141,7 +141,7 @@ module TokenAuthority
141
141
  return if resources.empty?
142
142
 
143
143
  # If resources are provided but feature is disabled, reject them
144
- unless TokenAuthority.config.rfc_8707_enabled?
144
+ unless TokenAuthority.config.resources_enabled?
145
145
  errors.add(:resources, :not_allowed)
146
146
  return
147
147
  end
@@ -225,7 +225,7 @@ module TokenAuthority
225
225
 
226
226
  def resources_must_be_valid
227
227
  # Check if resource is required
228
- if TokenAuthority.config.rfc_8707_require_resource && resources.empty?
228
+ if TokenAuthority.config.require_resource && resources.empty?
229
229
  errors.add(:resources, :required)
230
230
  return
231
231
  end
@@ -233,7 +233,7 @@ module TokenAuthority
233
233
  return if resources.empty?
234
234
 
235
235
  # If resources are provided but feature is disabled, reject them
236
- unless TokenAuthority.config.rfc_8707_enabled?
236
+ unless TokenAuthority.config.resources_enabled?
237
237
  errors.add(:resources, :not_allowed)
238
238
  return
239
239
  end
@@ -61,7 +61,7 @@ module TokenAuthority
61
61
  metadata[:service_documentation] = service_documentation if service_documentation.present?
62
62
 
63
63
  # RFC 7591 Dynamic Client Registration
64
- if TokenAuthority.config.rfc_7591_enabled
64
+ if TokenAuthority.config.dcr_enabled
65
65
  metadata[:registration_endpoint] = "#{issuer}#{@mount_path}/register"
66
66
  end
67
67
 
@@ -74,7 +74,7 @@ module TokenAuthority
74
74
  # @return [String]
75
75
  # @api private
76
76
  def issuer
77
- TokenAuthority.config.rfc_9068_issuer_url.to_s.chomp("/")
77
+ TokenAuthority.config.issuer_url.to_s.chomp("/")
78
78
  end
79
79
 
80
80
  # Returns the list of supported scopes from configuration.
@@ -88,14 +88,14 @@ module TokenAuthority
88
88
  # @return [String, nil]
89
89
  # @api private
90
90
  def service_documentation
91
- TokenAuthority.config.rfc_8414_service_documentation
91
+ TokenAuthority.config.authorization_server_documentation
92
92
  end
93
93
 
94
94
  # Returns the supported token endpoint authentication methods.
95
95
  # @return [Array<String>]
96
96
  # @api private
97
97
  def token_endpoint_auth_methods_supported
98
- TokenAuthority.config.rfc_7591_allowed_token_endpoint_auth_methods
98
+ TokenAuthority.config.dcr_allowed_token_endpoint_auth_methods
99
99
  end
100
100
  end
101
101
  end
@@ -241,8 +241,8 @@ module TokenAuthority
241
241
  end
242
242
 
243
243
  def set_default_durations
244
- self.access_token_duration ||= TokenAuthority.config.rfc_9068_default_access_token_duration
245
- self.refresh_token_duration ||= TokenAuthority.config.rfc_9068_default_refresh_token_duration
244
+ self.access_token_duration ||= TokenAuthority.config.default_access_token_duration
245
+ self.refresh_token_duration ||= TokenAuthority.config.default_refresh_token_duration
246
246
  end
247
247
 
248
248
  def set_client_id_issued_at
@@ -251,9 +251,9 @@ module TokenAuthority
251
251
 
252
252
  def set_client_secret_expiration
253
253
  return if client_type == "public"
254
- return unless TokenAuthority.config.rfc_7591_client_secret_expiration
254
+ return unless TokenAuthority.config.dcr_client_secret_expiration
255
255
 
256
- self.client_secret_expires_at = Time.current + TokenAuthority.config.rfc_7591_client_secret_expiration
256
+ self.client_secret_expires_at = Time.current + TokenAuthority.config.dcr_client_secret_expiration
257
257
  end
258
258
 
259
259
  def generate_client_secret_for(secret_id)
@@ -47,11 +47,11 @@ module TokenAuthority
47
47
 
48
48
  # URL-based clients use default token durations from config
49
49
  def access_token_duration
50
- TokenAuthority.config.rfc_9068_default_access_token_duration
50
+ TokenAuthority.config.default_access_token_duration
51
51
  end
52
52
 
53
53
  def refresh_token_duration
54
- TokenAuthority.config.rfc_9068_default_refresh_token_duration
54
+ TokenAuthority.config.default_refresh_token_duration
55
55
  end
56
56
 
57
57
  # URL-based clients cannot have secrets
@@ -72,7 +72,7 @@ module TokenAuthority
72
72
  def token_endpoint_auth_method_is_allowed
73
73
  return if token_endpoint_auth_method.blank?
74
74
 
75
- allowed = TokenAuthority.config.rfc_7591_allowed_token_endpoint_auth_methods
75
+ allowed = TokenAuthority.config.dcr_allowed_token_endpoint_auth_methods
76
76
  unless allowed.include?(token_endpoint_auth_method)
77
77
  errors.add(:token_endpoint_auth_method, "is not allowed: #{token_endpoint_auth_method}")
78
78
  end
@@ -82,7 +82,7 @@ module TokenAuthority
82
82
  return if grant_types.blank?
83
83
  return unless grant_types.is_a?(Array)
84
84
 
85
- allowed = TokenAuthority.config.rfc_7591_allowed_grant_types
85
+ allowed = TokenAuthority.config.dcr_allowed_grant_types
86
86
  disallowed = grant_types - allowed
87
87
  errors.add(:grant_types, "contains disallowed types: #{disallowed.join(", ")}") if disallowed.any?
88
88
  end
@@ -98,7 +98,7 @@ module TokenAuthority
98
98
  def scopes_are_allowed
99
99
  return if scope.blank?
100
100
 
101
- allowed = TokenAuthority.config.rfc_7591_allowed_scopes
101
+ allowed = TokenAuthority.config.dcr_allowed_scopes
102
102
  return if allowed.blank? # If no restrictions configured, allow all
103
103
 
104
104
  requested_scopes = scope.to_s.split(/\s+/).reject(&:blank?)
@@ -146,10 +146,10 @@ module TokenAuthority
146
146
  end
147
147
 
148
148
  def parse_software_statement
149
- jwks = TokenAuthority.config.rfc_7591_software_statement_jwks
149
+ jwks = TokenAuthority.config.dcr_software_statement_jwks
150
150
  if jwks.present?
151
151
  SoftwareStatement.decode_and_verify(software_statement, jwks: jwks)
152
- elsif TokenAuthority.config.rfc_7591_software_statement_required
152
+ elsif TokenAuthority.config.dcr_software_statement_required
153
153
  raise TokenAuthority::UnapprovedSoftwareStatementError
154
154
  else
155
155
  SoftwareStatement.decode(software_statement)
@@ -55,7 +55,7 @@ module TokenAuthority
55
55
  end
56
56
 
57
57
  def store_in_cache(uri, jwks_data)
58
- ttl = TokenAuthority.config.rfc_7591_jwks_cache_ttl
58
+ ttl = TokenAuthority.config.dcr_jwks_cache_ttl
59
59
  uri_hash = JwksCache.hash_uri(uri)
60
60
 
61
61
  JwksCache.find_or_initialize_by(uri_hash: uri_hash).tap do |cache|