token_authority 0.2.0 → 0.2.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c0e6a0810b0a8015aea5ada0e1bbb87596875c7641042281dac811fb3fc8bf0c
4
- data.tar.gz: 76eda2d8230aca93850b6e025c0be79cfac68da48159bbcb7f34e9f102229649
3
+ metadata.gz: 1b6b1981cf717ab9e179f87aaea998642ad74315333c25ea3ea1e8e267d3a293
4
+ data.tar.gz: 25b6bb154158120808d02ab4f8c939c1d7f731f57db9e380167fe414ecaba7e0
5
5
  SHA512:
6
- metadata.gz: '069ecd5dc4b50ca9929192ea6d22084db1e5019fa4df90ebcacbc900f4385d88452f3abc108deaa3ce29a88a7c212095dbfc4f056968409983189c0f97292130'
7
- data.tar.gz: 45ae89f8b90abffacc828544b9c910c40801cf93b65ce5a362faa28b01a242a565233d771f1a56b64e82765a89452ec4a45aacab42ce7883d98b120dbc11c3aa
6
+ metadata.gz: 9ce50536b44c5ef5063160808e7c93bc757f01b01f81f62c1ed0d3ba86da56d675c0d97e6fb3d944da4ac822925e5a7efc81dfef2c1adbc2094f9a43a4801af7
7
+ data.tar.gz: 476f43e644e3bf3b10aa57c3cff10e0bf2d1a73712b9cc0f2b9c89d306ec187e8663e3efd58582f5032f8f81f0ef8b5f46343daeb415b8e17a32ad047e2a38ee
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.1] - 2025-01-24
4
+
5
+ ### Fixes
6
+
7
+ - Implemented support for all mandatory access token JWT claims
8
+ - Disable turbo on consent screen to allow redirects
9
+
10
+ ### Documentation
11
+
12
+ - Update README to include link to MCP Quickstart guide.
13
+
3
14
  ## [0.2.0] - 2025-01-23
4
15
 
5
16
  - Implemented support for OAuth 2.1 authorization flows and JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens (RFC 9068).
@@ -18,6 +29,7 @@
18
29
 
19
30
  - Initial release
20
31
 
21
- [Unreleased]: https://github.com/dickdavis/token_authority/compare/v0.2.0...HEAD
32
+ [Unreleased]: https://github.com/dickdavis/token_authority/compare/v0.2.1...HEAD
33
+ [0.2.1]: https://github.com/dickdavis/token_authority/compare/v0.2.0...v0.2.1
22
34
  [0.2.0]: https://github.com/dickdavis/token_authority/compare/v0.1.0...v0.2.0
23
35
  [0.1.0]: https://github.com/dickdavis/token_authority/releases/tag/v0.1.0
data/README.md CHANGED
@@ -16,7 +16,7 @@ This project aims to implement the OAuth standards specified in the [MCP Authori
16
16
 
17
17
  ## Usage
18
18
 
19
- TokenAuthority is simple to install and configure.
19
+ 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
20
 
21
21
  ### Installation
22
22
 
@@ -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.
@@ -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.
@@ -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,10 +65,11 @@ 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
@@ -78,7 +85,8 @@ module TokenAuthority
78
85
  iat: Time.zone.now.to_i,
79
86
  iss: TokenAuthority.config.rfc_9068_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
@@ -19,7 +19,7 @@
19
19
  </ul>
20
20
  <% end %>
21
21
  <div>
22
- <%= button_to t('token_authority.authorization_grants.new.reject_cta'), authorization_grants_path, params: { approve: false } %>
23
- <%= button_to t('token_authority.authorization_grants.new.approve_cta'), authorization_grants_path, params: { approve: true } %>
22
+ <%= button_to t('token_authority.authorization_grants.new.reject_cta'), authorization_grants_path, params: { approve: false }, data: { turbo: false } %>
23
+ <%= button_to t('token_authority.authorization_grants.new.approve_cta'), authorization_grants_path, params: { approve: true }, data: { turbo: false } %>
24
24
  </div>
25
25
  </div>
@@ -2,5 +2,5 @@ module TokenAuthority
2
2
  # The current version of the TokenAuthority gem.
3
3
  #
4
4
  # @return [String] the version string in semantic versioning format
5
- VERSION = "0.2.0"
5
+ VERSION = "0.2.1"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: token_authority
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dick Davis