rodauth-oauth 1.0.0.pre.beta2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f96bced835f21567ea5603751e8cf06b53d4eae70d9cab8bc685e2fca8c2027
4
- data.tar.gz: 59badde6a055fa2638bcb41b01534f0b5b8de9eac541ac596f25e6d5cd6fb043
3
+ metadata.gz: 12c86242a8a2001fba629cb6bd8e25886b8805fce5d0965ebc70377824e25e91
4
+ data.tar.gz: 2fdc78f81b737c9c0d0086f258a86807f8855d3f0bc9be89bffb6d6a90946ed3
5
5
  SHA512:
6
- metadata.gz: 492a5c3c12bcc678c5eb6171e9d9851db745412fdb3675674c61b512dbfd1ff1aec09da02a3d4df3acb9133148990538200c49ce05de7a34dea85107aebf151b
7
- data.tar.gz: 65f1223065b2a0bce609b4137b8fadd206fffa9904caac97c41dc4d429528dea474a8b450128409ed164f6407c690e95a3e7c4a83b8f9302fb41d0336e132dea
6
+ metadata.gz: be15b77c46a135d213cd6e6e6bc7000b961e036febdb8f75bb922f09554d68ab757d5094fe96816c83c5966a3094daa32ecbbee798b2a8bb72417df9506ac3b3
7
+ data.tar.gz: a5fec610e9193d449ef49fbfde36688398c9e4e576da5ea579165c306ecc51bc910d0d758c923a9bece59ef4e7651347f9ebb7951504f3354b101fe5f845173a
data/CHANGELOG.md CHANGED
@@ -1 +1 @@
1
- See the Release Notes under https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/doc/release_notes
1
+ See the Release Notes under https://gitlab.com/os85/rodauth-oauth/-/tree/master/doc/release_notes
@@ -90,6 +90,10 @@ The client secret is hashed (with bcrypt) before being stored, by default. While
90
90
  oauth_applications_client_secret_hash_column nil
91
91
  ```
92
92
 
93
+ ## oauth applications: oauth_applications_homepage_url_column no longer required
94
+
95
+ The homepage url is no longer considered a require prooperty of an OAuth client application.
96
+
93
97
  ## oauth grants: access token and refresh token hashed by default
94
98
 
95
99
  access token and refresh token columns are now hashed by default, and point to the same column as the main counterpart:
@@ -205,6 +209,14 @@ JWKs URI endpoint has been moved to its plugin. If you require this functionalit
205
209
  enable :oauth_jwt, :oauth_jwt_jwks
206
210
  ```
207
211
 
212
+ ## OIDC RP-initiated logout segregated in its plugin
213
+
214
+ It was previously being loaded in the `:oidc` plugin by default. If you require this funtionality, enable the plugin:
215
+
216
+ ```ruby
217
+ enable :oidc_rp_initiated_logout
218
+ ```
219
+
208
220
  ## routing functions renamed
209
221
 
210
222
  Previously, loading well-known routes, the oauth server metadata, or oauth application/tokens (now grants) management dashboard implied calling a function on roda to load those routes. These have been renamed:
data/README.md CHANGED
@@ -1,11 +1,25 @@
1
1
  # Rodauth::Oauth
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rodauth-oauth.svg)](http://rubygems.org/gems/rodauth-oauth)
4
- [![pipeline status](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/rodauth-oauth/pipelines?page=1&scope=all&ref=master)
5
- [![coverage report](https://gitlab.com/honeyryderchuck/rodauth-oauth/badges/master/coverage.svg?job=coverage)](https://honeyryderchuck.gitlab.io/rodauth-oauth/coverage/#_AllFiles)
4
+ [![pipeline status](https://gitlab.com/os85/rodauth-oauth/badges/master/pipeline.svg)](https://gitlab.com/os85/rodauth-oauth/pipelines?page=1&scope=all&ref=master)
5
+ [![coverage report](https://gitlab.com/os85/rodauth-oauth/badges/master/coverage.svg?job=coverage)](https://os85.gitlab.io/rodauth-oauth/coverage/#_AllFiles)
6
6
 
7
7
  This is an extension to the `rodauth` gem which implements the [OAuth 2.0 framework](https://tools.ietf.org/html/rfc6749) for an authorization server.
8
8
 
9
+ ## Certification
10
+ [<img width="184" height="96" align="right" src="/openid-certified.jpg" alt="OpenID Certification">](https://openid.net/certification/)
11
+
12
+ `rodauth-oauth` is [certified](https://openid.net/certification/) for the following profiles of the OpenID Connect™ protocol:
13
+
14
+ * Basic OP
15
+ * Implicit OP
16
+ * Hybrid OP
17
+ * Config OP
18
+ * Dynamic OP
19
+ * Form Post OP
20
+
21
+ (it also passes the conformance tests for the RP-Initiated Logout OP).
22
+
9
23
  ## Features
10
24
 
11
25
  This gem implements the following RFCs and features of OAuth:
@@ -32,6 +46,7 @@ This gem implements the following RFCs and features of OAuth:
32
46
 
33
47
  * `oauth_dynamic_client_registration` - [Dynamic Client Registration Protocol](https://datatracker.ietf.org/doc/html/rfc7591);
34
48
  * OAuth application and token management dashboards;
49
+ * The recommendations for [Native Apps](https://www.rfc-editor.org/rfc/rfc8252);
35
50
 
36
51
  It also implements the [OpenID Connect layer](https://openid.net/connect/) (via the `openid` feature) on top of the OAuth features it provides, including:
37
52
 
@@ -65,10 +80,10 @@ Or install it yourself as:
65
80
  ## Resources
66
81
  | | |
67
82
  | ------------- | ----------------------------------------------------------- |
68
- | Website | https://honeyryderchuck.gitlab.io/rodauth-oauth/ |
69
- | Documentation | https://honeyryderchuck.gitlab.io/rodauth-oauth/rdoc/ |
70
- | Wiki | https://gitlab.com/honeyryderchuck/rodauth-oauth/wikis/home |
71
- | CI | https://gitlab.com/honeyryderchuck/rodauth-oauth/pipelines |
83
+ | Website | https://os85.gitlab.io/rodauth-oauth/ |
84
+ | Documentation | https://os85.gitlab.io/rodauth-oauth/rdoc/ |
85
+ | Wiki | https://gitlab.com/os85/rodauth-oauth/wikis/home |
86
+ | CI | https://gitlab.com/os85/rodauth-oauth/pipelines |
72
87
 
73
88
  ## Articles
74
89
 
@@ -132,12 +147,12 @@ end
132
147
 
133
148
  ### Example (TL;DR)
134
149
 
135
- Just [check our example applications](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/examples/).
150
+ Just [check our example applications](https://gitlab.com/os85/rodauth-oauth/-/tree/master/examples/).
136
151
 
137
152
 
138
153
  ### Database migrations
139
154
 
140
- You have to generate database tables for accounts, oauth applications, grants and tokens. In order for you to hit the ground running, [here's a set of migrations (using `sequel`) to generate the needed tables](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/test/migrate) (omit the first 2 if you already have account tables, and [follow recommendations from rodauth accordingly](https://github.com/jeremyevans/rodauth)).
155
+ You have to generate database tables for accounts, oauth applications, grants and tokens. In order for you to hit the ground running, [here's a set of migrations (using `sequel`) to generate the needed tables](https://gitlab.com/os85/rodauth-oauth/-/tree/master/test/migrate) (omit the first 2 if you already have account tables, and [follow recommendations from rodauth accordingly](https://github.com/jeremyevans/rodauth)).
141
156
 
142
157
  You can change column names or even use existing tables, however, be aware that you'll have to define new column accessors at the `rodauth` plugin declaration level. Let's say, for instance, you'd like to change the `oauth_grants` table name to `access_grants`, and it's `code` column to `authorization_code`; then, you'd have to do the following:
143
158
 
@@ -270,7 +285,7 @@ end
270
285
 
271
286
  `rodauth-oauth` supports translating all user-facing text found in all pages and forms, by integrating with [rodauth-i18n](https://github.com/janko/rodauth-i18n). Just set it up in your application and `rodauth` configuration.
272
287
 
273
- Default translations shipping with `rodauth-oauth` can be found [in this directory](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/locales). If they're not available for the languages you'd like to support, consider getting them translated from the english text, and contributing them to this repository via a Merge Request.
288
+ Default translations shipping with `rodauth-oauth` can be found [in this directory](https://gitlab.com/os85/rodauth-oauth/-/tree/master/locales). If they're not available for the languages you'd like to support, consider getting them translated from the english text, and contributing them to this repository via a Merge Request.
274
289
 
275
290
  (This feature is available since `v0.7`.)
276
291
 
@@ -289,4 +304,4 @@ After checking out the repo, run `bundle install` to install dependencies. Then,
289
304
 
290
305
  ## Contributing
291
306
 
292
- Bug reports and pull requests are welcome on Gitlab at https://gitlab.com/honeyryderchuck/rodauth-oauth.
307
+ Bug reports and pull requests are welcome on Gitlab at https://gitlab.com/os85/rodauth-oauth.
@@ -12,9 +12,9 @@ plugin :rodauth do
12
12
  end
13
13
  ```
14
14
 
15
- For more info about integrating it, [check the wiki](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/home#openid-connect-since-v01).
15
+ For more info about integrating it, [check the wiki](https://gitlab.com/os85/rodauth-oauth/-/wikis/home#openid-connect-since-v01).
16
16
 
17
- It supports omniauth openID integrations out-of-the-box, [check the OpenID example, which integrates with omniauth_openid_connect](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/tree/master/examples).
17
+ It supports omniauth openID integrations out-of-the-box, [check the OpenID example, which integrates with omniauth_openid_connect](https://gitlab.com/os85/rodauth-oauth/-/tree/master/examples).
18
18
 
19
19
  #### Improvements
20
20
 
@@ -12,7 +12,7 @@ plugin :rodauth do
12
12
  end
13
13
  ```
14
14
 
15
- For more info about integrating it, [check the wiki](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/SAML-Assertion-Access-Tokens).
15
+ For more info about integrating it, [check the wiki](https://gitlab.com/os85/rodauth-oauth/-/wikis/SAML-Assertion-Access-Tokens).
16
16
 
17
17
  ##### Supporting rotating keys
18
18
 
@@ -7,7 +7,7 @@
7
7
  #### Improvements
8
8
 
9
9
 
10
- * Support for the OIDC authorize [`prompt` parameter](https://openid.net/specs/openid-connect-core-1_0.html) (sectionn 3.1.2.1). It supports the `none`, `login` and `consent` out-of-the-box, while providing support for `select-account` when paired with [rodauth-select-account, a rodauth feature to handle multiple accounts in the same session](https://gitlab.com/honeyryderchuck/rodauth-select-account).
10
+ * Support for the OIDC authorize [`prompt` parameter](https://openid.net/specs/openid-connect-core-1_0.html) (sectionn 3.1.2.1). It supports the `none`, `login` and `consent` out-of-the-box, while providing support for `select-account` when paired with [rodauth-select-account, a rodauth feature to handle multiple accounts in the same session](https://gitlab.com/os85/rodauth-select-account).
11
11
 
12
12
  * Refresh Tokens are now expirable. The refresh token expiration period is governed by the `oauth_refresh_token_expires_in` option (default: 1 year), and is the period for which a refresh token can be used after its respective access token expired.
13
13
 
@@ -2,10 +2,10 @@
2
2
 
3
3
  #### RP-Initiated Logout
4
4
 
5
- The `:oidc` plugin can now do [RP-Initiated Logout](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/RP-Initiated-Logout). It's disabled by default, so read the docs to learn how to enable it.
5
+ The `:oidc` plugin can now do [RP-Initiated Logout](https://gitlab.com/os85/rodauth-oauth/-/wikis/RP-Initiated-Logout). It's disabled by default, so read the docs to learn how to enable it.
6
6
 
7
7
  #### Security
8
8
 
9
9
  The `:oauth_jwt` (and by association, `:oidc`) plugin(s) verifies the claims of used JWT tokens. This is a **very important security fix**, as without it, there is no protection against replay attacks and other types of misuse of the JWT token.
10
10
 
11
- A new auth method, `generate_jti(claims)`, was [added to the list of oauth_jwt plugin options](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/JWT-Access-Tokens#rodauth-options). By default, it'll hash the `aud` and `iat` claims together, but you can overwrite how this is done.
11
+ A new auth method, `generate_jti(claims)`, was [added to the list of oauth_jwt plugin options](https://gitlab.com/os85/rodauth-oauth/-/wikis/JWT-Access-Tokens#rodauth-options). By default, it'll hash the `aud` and `iat` claims together, but you can overwrite how this is done.
@@ -4,7 +4,7 @@
4
4
 
5
5
  * Device code grant
6
6
 
7
- `rodauth-oauth` now supports the [Device code grant RFC](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/Device-Grant), via the `oauth_device_grant` feature.
7
+ `rodauth-oauth` now supports the [Device code grant RFC](https://gitlab.com/os85/rodauth-oauth/-/wikis/Device-Grant), via the `oauth_device_grant` feature.
8
8
 
9
9
  * OAuth Tokens Management
10
10
 
@@ -12,7 +12,7 @@ An OAuth Tokens Management Dashboard is now provided (via `r.oauth_tokens` call
12
12
 
13
13
  * Assertion Framework (+ SAML and JWT Bearer Grant)
14
14
 
15
- A new plugin, `oauth_assertion_base`, was introduced to provide a baseline for implementing custom Bearer Assertion as per the [OAuth Client Assertion Framework RFC](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/Client-Assertion-Framework). This in turn was used to refactor and reintroduce the [oauth_saml_bearer_grant](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/SAML-Bearer-Assertions) and the [oauth_jwt_bearer_grant](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/wikis/JWT-Bearer-Assertions) features, which implement the respective and most recent version of the assertion RFCs.
15
+ A new plugin, `oauth_assertion_base`, was introduced to provide a baseline for implementing custom Bearer Assertion as per the [OAuth Client Assertion Framework RFC](https://gitlab.com/os85/rodauth-oauth/-/wikis/Client-Assertion-Framework). This in turn was used to refactor and reintroduce the [oauth_saml_bearer_grant](https://gitlab.com/os85/rodauth-oauth/-/wikis/SAML-Bearer-Assertions) and the [oauth_jwt_bearer_grant](https://gitlab.com/os85/rodauth-oauth/-/wikis/JWT-Bearer-Assertions) features, which implement the respective and most recent version of the assertion RFCs.
16
16
 
17
17
  (as a result, `oauth_saml` was removed, which implemented a very old draft version of the SAML Bearer spec).
18
18
 
@@ -0,0 +1,79 @@
1
+ ## 1.0.0 (15/12/2022)
2
+
3
+ ## Highlights
4
+
5
+ rodauth-oauth is now [OpenID certified](https://openid.net/certification/) for the following certification profiles:
6
+
7
+ * Basic OP
8
+ * Implicit OP
9
+ * Hybrid OP
10
+ * Config OP
11
+ * Dynamic OP
12
+ * Form Post OP
13
+
14
+ and passes the conformance tests for RP-Initiated Logout OP.
15
+
16
+ The OIDC server used to run the test can be found [here](https://gitlab.com/os85/rodauth-oauth/-/blob/master/examples/oidc/authentication_server.rb) and deployed [here](https://rodauth-oauth-oidc.onrender.com).
17
+
18
+ ### Breaking changes
19
+
20
+ The full description of breaking changes, and suggestions on how to make the migration smoother, can be found in the [migration guide](https://gitlab.com/os85/rodauth-oauth/-/blob/6465b8522a78cf0037a55d3d4b81f68f7811be68/MIGRATION-GUIDE-v1.md).
21
+
22
+ A short list of the main highlights:
23
+
24
+
25
+ * Ruby 2.5 or higher is required.
26
+ * `oauth_http_mac` feature removed.
27
+ * `oauth_tokens` table (and resource) were removed (only `oauth_applications` and `oauth_grants`, access and refresh tokens are now properties of the latter).
28
+ * access and refresh tokens hashed by default when stored in the database.
29
+ * default oauth response mode is `"form_post"`.
30
+ * oauth specific features require explicit enablement of respective features (no more `enable :oauth`)
31
+ * refresh token policy is "rotation" by default
32
+ * homepage url is no longer a client application required property.
33
+ * OIDC RP-initiated logout extracted into `oidc_rp_initiated_logout` feature.
34
+
35
+ ### Features
36
+
37
+ The following helpers are exposed in the `rodauth` object:
38
+
39
+ * `current_oauth_account` - returns the dataset row for the `rodauth` account associated to an oauth access token in the "authorization" header.
40
+ * `current_oauth_application` - returns the dataset row for the oauth application associated to an oauth access token in the "authorization" header.
41
+
42
+ When used in `rails` via `rodauth-rails`, both are exposed directly as controller helpers.
43
+
44
+ #### `oauth_resource_server` plugin
45
+
46
+ This plugin can be used as a convenience when configuring resource servers.
47
+
48
+ #### JAR support for request_uri query param
49
+
50
+ The `oauth_jwt_secured_authorization_request` plugin now supports a `request_uri` query param as well.
51
+
52
+ #### OIDC features
53
+
54
+ * The `oidc` plugin supports [essential claims](https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter), via the `claims` authorization request query parameter.
55
+ * id token built with `"c_hash"` and `"at_hash"` claims when they should.
56
+
57
+ ### Improvements
58
+
59
+ * `:oauth_introspect` plugin: OAuth introspection endpoint exposes the token's `"username"` claim.
60
+ * endpoint client authentication supports "client credentials grant" access tokens.
61
+ * `acr_values_supported` exposed in the openid configuration.
62
+ * `oauth_request_object_signing_alg_allow_none` enables `"none"` as an accepted request object signing alg when `true` (`false` by default).
63
+ * OIDC `offline_access` supported.
64
+
65
+ ### Bugfixes
66
+
67
+ * fixed `oidc` calculation of `"auth_time"` claim.
68
+ * JWT: "sub" is now always a string.
69
+ * `response_type` is now an authorization request required parameter (as per the RFC).
70
+ * `state` is now passed along when redirecting from authorization requests with `error`;
71
+ * access token can now be read from POST body or GET query params (as per the RFC).
72
+ * id token no longer shipping with claims with `null` value;
73
+ * id token no longer encoding claims by default (only when `response_type=id_token`, as per the RFC).
74
+ * support "JWT without kid" when doing jwt decoding for JWT tokens not generated in the provider (such as request objects).
75
+ * Set `iss` and `aud` claims in the Userinfo JWT response.
76
+ * Make sure errors are also delivered via form POST, when `response_mode=form_post`.
77
+ * Authorization request now shows an error page when `response_type` or `client_id` are missing, or `redirect_uri` is missing or invalid; a new `"authorize_error"` template is invoked in such cases.
78
+ * oidc: nonce present in id token when using the "id_token token" response type.
79
+ * error parameter delivered in URL fragment when failing an implicit grant autorization request.
@@ -0,0 +1,9 @@
1
+ ## 1.0.0 (10/01/2023)
2
+
3
+ ## Features
4
+
5
+ ### Loopback Interface Redirection URI support
6
+
7
+ https://www.rfc-editor.org/rfc/rfc8252#section-7.3
8
+
9
+ Redirect URIs based on loopback addresses ("127.0.0.1", "::1") are now supported when used in an authorization request with an ephemeral port (@avdigrimm).
@@ -0,0 +1,10 @@
1
+ <div class="container">
2
+ <div class="alert alert-danger">
3
+ <p>
4
+ <%= @error %>%
5
+ </p>
6
+ </div>
7
+ <p class="text-center">
8
+ <%= link_to rodauth.oauth_cancel_button, @back_url, class: "btn btn-outline-danger" %>
9
+ </p>
10
+ </div>
@@ -120,7 +120,6 @@ module Rodauth
120
120
  return super unless supported_grant_type?(grant_type, "authorization_code")
121
121
 
122
122
  grant_params = {
123
- oauth_grants_type_column => grant_type,
124
123
  oauth_grants_code_column => param("code"),
125
124
  oauth_grants_redirect_uri_column => param("redirect_uri"),
126
125
  oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column]
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "ipaddr"
3
4
  require "rodauth/oauth"
4
5
 
5
6
  module Rodauth
@@ -10,6 +11,7 @@ module Rodauth
10
11
  after "authorize"
11
12
 
12
13
  view "authorize", "Authorize", "authorize"
14
+ view "authorize_error", "Authorize Error", "authorize_error"
13
15
 
14
16
  button "Authorize", "oauth_authorize"
15
17
  button "Back to Client Application", "oauth_authorize_post"
@@ -24,7 +26,7 @@ module Rodauth
24
26
  translatable_method :oauth_applications_tos_uri_label, "Terms of service URL"
25
27
  translatable_method :oauth_applications_policy_uri_label, "Policy URL"
26
28
  translatable_method :oauth_unsupported_response_type_message, "Unsupported response type"
27
- translatable_method :oauth_authorize_parameter_required, "'%<parameter>s' is a required parameter"
29
+ translatable_method :oauth_authorize_parameter_required, "Invalid or missing '%<parameter>s'"
28
30
 
29
31
  # /authorize
30
32
  auth_server_route(:authorize) do |r|
@@ -65,7 +67,16 @@ module Rodauth
65
67
  private
66
68
 
67
69
  def validate_authorize_params
68
- redirect_response_error("invalid_request", request.referer || default_redirect) unless oauth_application && check_valid_redirect_uri?
70
+ redirect_authorize_error("client_id") unless oauth_application
71
+
72
+ redirect_uris = oauth_application[oauth_applications_redirect_uri_column].split(" ")
73
+
74
+ if (redirect_uri = param_or_nil("redirect_uri"))
75
+ normalized_redirect_uri = normalize_redirect_uri_for_comparison(redirect_uri)
76
+ redirect_authorize_error("redirect_uri") unless redirect_uris.include?(normalized_redirect_uri)
77
+ elsif redirect_uris.size > 1
78
+ redirect_authorize_error("redirect_uri")
79
+ end
69
80
 
70
81
  redirect_response_error("unsupported_response_type") unless check_valid_response_type?
71
82
 
@@ -80,18 +91,6 @@ module Rodauth
80
91
  false
81
92
  end
82
93
 
83
- def check_valid_redirect_uri?
84
- application_redirect_uris = oauth_application[oauth_applications_redirect_uri_column].split(" ")
85
-
86
- if (redirect_uri = param_or_nil("redirect_uri"))
87
- application_redirect_uris.include?(redirect_uri)
88
- else
89
- set_error_flash(oauth_authorize_parameter_required(parameter: "redirect_uri")) if application_redirect_uris.size > 1
90
-
91
- true
92
- end
93
- end
94
-
95
94
  ACCESS_TYPES = %w[offline online].freeze
96
95
 
97
96
  def check_valid_access_type?
@@ -127,6 +126,21 @@ module Rodauth
127
126
  request.env["REQUEST_METHOD"] = "POST"
128
127
  end
129
128
 
129
+ def redirect_authorize_error(parameter, referer = request.referer || default_redirect)
130
+ error_message = oauth_authorize_parameter_required(parameter: parameter)
131
+
132
+ if accepts_json?
133
+ status_code = oauth_invalid_response_status
134
+
135
+ throw_json_response_error(status_code, "invalid_request", error_message)
136
+ else
137
+ scope.instance_variable_set(:@error, error_message)
138
+ scope.instance_variable_set(:@back_url, referer)
139
+
140
+ return_response(authorize_error_view)
141
+ end
142
+ end
143
+
130
144
  def authorization_required
131
145
  if accepts_json?
132
146
  throw_json_response_error(oauth_authorization_required_error_status, "invalid_client")
@@ -199,5 +213,26 @@ module Rodauth
199
213
  end
200
214
  create_params[oauth_grants_code_column]
201
215
  end
216
+
217
+ def normalize_redirect_uri_for_comparison(redirect_uri)
218
+ uri = URI(redirect_uri)
219
+
220
+ return redirect_uri unless uri.scheme == "http" && uri.port
221
+
222
+ hostname = uri.hostname
223
+
224
+ # https://www.rfc-editor.org/rfc/rfc8252#section-7.3
225
+ # ignore (potentially ephemeral) port number for native clients per RFC8252
226
+ begin
227
+ ip = IPAddr.new(hostname)
228
+ uri.port = nil if ip.loopback?
229
+ rescue IPAddr::InvalidAddressError
230
+ # https://www.rfc-editor.org/rfc/rfc8252#section-8.3
231
+ # Although the use of localhost is NOT RECOMMENDED, it is still allowed.
232
+ uri.port = nil if hostname == "localhost"
233
+ end
234
+
235
+ uri.to_s
236
+ end
202
237
  end
203
238
  end
@@ -4,6 +4,7 @@ require "time"
4
4
  require "base64"
5
5
  require "securerandom"
6
6
  require "cgi"
7
+ require "digest/sha2"
7
8
  require "rodauth/version"
8
9
  require "rodauth/oauth"
9
10
  require "rodauth/oauth/database_extensions"
@@ -48,6 +48,16 @@ module Rodauth
48
48
  generate_token(grant_params, false)
49
49
  end
50
50
 
51
+ def _redirect_response_error(redirect_url, query_params)
52
+ response_types = param("response_type").split(/ +/)
53
+
54
+ return super if response_types.empty? || response_types == %w[code]
55
+
56
+ query_params = query_params.map { |k, v| "#{k}=#{v}" }
57
+ redirect_url.fragment = query_params.join("&")
58
+ redirect(redirect_url.to_s)
59
+ end
60
+
51
61
  def authorize_response(params, mode)
52
62
  return super unless mode == "fragment"
53
63
 
@@ -292,6 +292,11 @@ module Rodauth
292
292
  end
293
293
 
294
294
  super
295
+
296
+ return unless (response_type = param_or_nil("response_type"))
297
+ return unless response_type.include?("id_token")
298
+
299
+ redirect_response_error("invalid_request") unless param_or_nil("nonce")
295
300
  end
296
301
 
297
302
  def require_authorizable_account
@@ -440,10 +445,7 @@ module Rodauth
440
445
  _generate_access_token(oauth_grant)
441
446
  end
442
447
 
443
- {
444
- "code" => authorization_code,
445
- **json_access_token_payload(oauth_grants_token_column => access_token)
446
- }
448
+ json_access_token_payload(oauth_grants_token_column => access_token).merge("code" => authorization_code)
447
449
  end
448
450
 
449
451
  def create_token(*)
@@ -457,6 +459,9 @@ module Rodauth
457
459
 
458
460
  return unless oauth_scopes.include?("openid")
459
461
 
462
+ signing_algorithm = oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
463
+ oauth_jwt_keys.keys.first
464
+
460
465
  id_token_claims = jwt_claims(oauth_grant)
461
466
 
462
467
  id_token_claims[:nonce] = oauth_grant[oauth_grants_nonce_column] if oauth_grant[oauth_grants_nonce_column]
@@ -466,6 +471,16 @@ module Rodauth
466
471
  # Time when the End-User authentication occurred.
467
472
  id_token_claims[:auth_time] = get_oidc_account_last_login_at(oauth_grant[oauth_grants_account_id_column]).to_i
468
473
 
474
+ # Access Token hash value.
475
+ if (access_token = oauth_grant[oauth_grants_token_column])
476
+ id_token_claims[:at_hash] = id_token_hash(access_token, signing_algorithm)
477
+ end
478
+
479
+ # code hash value.
480
+ if (code = oauth_grant[oauth_grants_code_column])
481
+ id_token_claims[:c_hash] = id_token_hash(code, signing_algorithm)
482
+ end
483
+
469
484
  account = db[accounts_table].where(account_id_column => oauth_grant[oauth_grants_account_id_column]).first
470
485
 
471
486
  # this should never happen!
@@ -488,10 +503,7 @@ module Rodauth
488
503
 
489
504
  params = {
490
505
  jwks: oauth_application_jwks(oauth_application),
491
- signing_algorithm: (
492
- oauth_application[oauth_applications_id_token_signed_response_alg_column] ||
493
- oauth_jwt_keys.keys.first
494
- ),
506
+ signing_algorithm: signing_algorithm,
495
507
  encryption_algorithm: oauth_application[oauth_applications_id_token_encrypted_response_alg_column],
496
508
  encryption_method: oauth_application[oauth_applications_id_token_encrypted_response_enc_column]
497
509
  }.compact
@@ -655,7 +667,8 @@ module Rodauth
655
667
  when "id_token token"
656
668
  redirect_response_error("invalid_request") unless supports_token_response_type?
657
669
 
658
- oauth_grant = _do_authorize_token(oauth_grants_type_column => "hybrid")
670
+ grant_params = oidc_grant_params.merge(oauth_grants_type_column => "hybrid")
671
+ oauth_grant = _do_authorize_token(grant_params)
659
672
  generate_id_token(oauth_grant)
660
673
 
661
674
  response_params.replace(json_access_token_payload(oauth_grant))
@@ -664,6 +677,7 @@ module Rodauth
664
677
 
665
678
  params = create_oauth_grant_with_token
666
679
  oauth_grant = valid_oauth_grant_ds.where(oauth_grants_code_column => params["code"]).first
680
+ oauth_grant[oauth_grants_token_column] = params["access_token"]
667
681
  generate_id_token(oauth_grant)
668
682
 
669
683
  response_params.replace(params.merge("id_token" => oauth_grant[:id_token]))
@@ -785,5 +799,19 @@ module Rodauth
785
799
  end
786
800
  return_response(jwt)
787
801
  end
802
+
803
+ def id_token_hash(hash, algo)
804
+ digest = case algo
805
+ when /256/ then Digest::SHA256
806
+ when /384/ then Digest::SHA384
807
+ when /512/ then Digest::SHA512
808
+ end
809
+
810
+ return unless digest
811
+
812
+ hash = digest.digest(hash)
813
+ hash = hash[0...hash.size / 2]
814
+ Base64.urlsafe_encode64(hash).tr("=", "")
815
+ end
788
816
  end
789
817
  end
@@ -19,31 +19,35 @@ module Rodauth
19
19
  catch_error do
20
20
  validate_oidc_logout_params
21
21
 
22
- #
23
- # why this is done:
24
- #
25
- # we need to decode the id token in order to get the application, because, if the
26
- # signing key is application-specific, we don't know how to verify the signature
27
- # beforehand. Hence, we have to do it twice: decode-and-do-not-verify, initialize
28
- # the @oauth_application, and then decode-and-verify.
29
- #
30
- claims = jwt_decode(param("id_token_hint"), verify_claims: false)
31
-
32
- redirect_logout_with_error(oauth_invalid_client_message) unless claims
33
-
34
- oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["aud"]).first
35
- oauth_grant = db[oauth_grants_table]
36
- .where(
37
- oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
38
- oauth_grants_account_id_column => account_id
39
- ).first
40
-
41
- # check whether ID token belongs to currently logged-in user
42
- redirect_logout_with_error(oauth_invalid_client_message) unless oauth_grant && claims["sub"] == jwt_subject(oauth_grant,
43
- oauth_application)
44
-
45
- # When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token.
46
- redirect_logout_with_error(oauth_invalid_client_message) unless claims && claims["iss"] == oauth_jwt_issuer
22
+ oauth_application = nil
23
+
24
+ if (id_token_hint = param_or_nil("id_token_hint"))
25
+ #
26
+ # why this is done:
27
+ #
28
+ # we need to decode the id token in order to get the application, because, if the
29
+ # signing key is application-specific, we don't know how to verify the signature
30
+ # beforehand. Hence, we have to do it twice: decode-and-do-not-verify, initialize
31
+ # the @oauth_application, and then decode-and-verify.
32
+ #
33
+ claims = jwt_decode(id_token_hint, verify_claims: false)
34
+
35
+ redirect_logout_with_error(oauth_invalid_client_message) unless claims
36
+
37
+ oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => claims["aud"]).first
38
+ oauth_grant = db[oauth_grants_table]
39
+ .where(
40
+ oauth_grants_oauth_application_id_column => oauth_application[oauth_applications_id_column],
41
+ oauth_grants_account_id_column => account_id
42
+ ).first
43
+
44
+ # check whether ID token belongs to currently logged-in user
45
+ redirect_logout_with_error(oauth_invalid_client_message) unless oauth_grant && claims["sub"] == jwt_subject(oauth_grant,
46
+ oauth_application)
47
+
48
+ # When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token.
49
+ redirect_logout_with_error(oauth_invalid_client_message) unless claims && claims["iss"] == oauth_jwt_issuer
50
+ end
47
51
 
48
52
  # now let's logout from IdP
49
53
  transaction do
@@ -92,7 +96,6 @@ module Rodauth
92
96
  # Logout
93
97
 
94
98
  def validate_oidc_logout_params
95
- redirect_logout_with_error(oauth_invalid_client_message) unless param_or_nil("id_token_hint")
96
99
  # check if valid token hint type
97
100
  return unless (redirect_uri = param_or_nil("post_logout_redirect_uri"))
98
101
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rodauth
4
4
  module OAuth
5
- VERSION = "1.0.0-beta2"
5
+ VERSION = "1.1.0"
6
6
  end
7
7
  end
data/locales/en.yml CHANGED
@@ -9,6 +9,7 @@ en:
9
9
  user_code_not_found_error_flash: "No device to authorize with the given user code"
10
10
  authorize_page_title: "Authorize"
11
11
  authorize_page_lead: "The application %{name} would like to access your data."
12
+ authorize_error_page_title: "Authorize Error"
12
13
  oauth_cancel_button: "Cancel"
13
14
  oauth_applications_page_title: "Oauth Applications"
14
15
  oauth_application_page_title: "Oauth Application"
@@ -65,5 +66,5 @@ en:
65
66
  oauth_unsupported_transform_algorithm_message: "transform algorithm not supported"
66
67
  oauth_invalid_request_object_message: "request object is invalid"
67
68
  oauth_invalid_scope_message: "The Access Token expired"
68
- oauth_authorize_parameter_required: "'%{parameter}' is a required parameter"
69
+ oauth_authorize_parameter_required: "Invalid or missing '%{parameter}'"
69
70
  oauth_invalid_post_logout_redirect_uri_message: "Invalid post logout redirect URI"
data/locales/pt.yml CHANGED
@@ -9,6 +9,7 @@ pt:
9
9
  user_code_not_found_error_flash: "Não existe nenhum dispositivo a ser autorizado com o código de usuário inserido"
10
10
  authorize_page_title: "Autorizar"
11
11
  authorize_page_lead: "O aplicativo %{name} gostaria de aceder aos seus dados."
12
+ authorize_error_page_title: Erro de autorização
12
13
  oauth_cancel_button: "Cancelar"
13
14
  oauth_applications_page_title: "Aplicativos OAuth"
14
15
  oauth_application_page_title: "Aplicativo Oauth"
@@ -65,5 +66,5 @@ pt:
65
66
  oauth_unsupported_transform_algorithm_message: "algoritmo de transformação não suportado"
66
67
  oauth_invalid_request_object_message: "request_object é inválido"
67
68
  oauth_invalid_scope_message: "O Token de acesso expirou"
68
- oauth_authorize_parameter_required: "'%{parameter}' é um parâmetro obrigatório"
69
+ oauth_authorize_parameter_required: "'%{parameter}' inválido ou em falta"
69
70
  oauth_invalid_post_logout_redirect_uri_message: "URI de redireccionamento pós-logout inválido"
@@ -0,0 +1,12 @@
1
+ <div class="container">
2
+ <div class="alert alert-danger">
3
+ <p>
4
+ #{@error}
5
+ </p>
6
+ </div>
7
+ <p class="text-center">
8
+ <a href="#{@back_url}" class="btn btn-outline-danger">
9
+ #{rodauth.oauth_cancel_button}
10
+ </a>
11
+ </p>
12
+ </div>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rodauth-oauth
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.beta2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Cardoso
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-09 00:00:00.000000000 Z
11
+ date: 2023-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rodauth
@@ -66,8 +66,8 @@ extra_rdoc_files:
66
66
  - doc/release_notes/0_9_1.md
67
67
  - doc/release_notes/0_9_2.md
68
68
  - doc/release_notes/0_9_3.md
69
- - doc/release_notes/1_0_0_beta1.md
70
- - doc/release_notes/1_0_0_beta2.md
69
+ - doc/release_notes/1_0_0.md
70
+ - doc/release_notes/1_1_0.md
71
71
  files:
72
72
  - CHANGELOG.md
73
73
  - LICENSE.txt
@@ -105,12 +105,13 @@ files:
105
105
  - doc/release_notes/0_9_1.md
106
106
  - doc/release_notes/0_9_2.md
107
107
  - doc/release_notes/0_9_3.md
108
- - doc/release_notes/1_0_0_beta1.md
109
- - doc/release_notes/1_0_0_beta2.md
108
+ - doc/release_notes/1_0_0.md
109
+ - doc/release_notes/1_1_0.md
110
110
  - lib/generators/rodauth/oauth/install_generator.rb
111
111
  - lib/generators/rodauth/oauth/templates/app/models/oauth_application.rb
112
112
  - lib/generators/rodauth/oauth/templates/app/models/oauth_grant.rb
113
113
  - lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize.html.erb
114
+ - lib/generators/rodauth/oauth/templates/app/views/rodauth/authorize_error.erb
114
115
  - lib/generators/rodauth/oauth/templates/app/views/rodauth/device_search.html.erb
115
116
  - lib/generators/rodauth/oauth/templates/app/views/rodauth/device_verification.html.erb
116
117
  - lib/generators/rodauth/oauth/templates/app/views/rodauth/new_oauth_application.html.erb
@@ -155,6 +156,7 @@ files:
155
156
  - locales/en.yml
156
157
  - locales/pt.yml
157
158
  - templates/authorize.str
159
+ - templates/authorize_error.str
158
160
  - templates/client_secret_field.str
159
161
  - templates/description_field.str
160
162
  - templates/device_search.str
@@ -169,15 +171,15 @@ files:
169
171
  - templates/oauth_grants.str
170
172
  - templates/redirect_uri_field.str
171
173
  - templates/scope_field.str
172
- homepage: https://gitlab.com/honeyryderchuck/rodauth-oauth
174
+ homepage: https://gitlab.com/os85/rodauth-oauth
173
175
  licenses:
174
176
  - Apache-2.0
175
177
  metadata:
176
- homepage_uri: https://honeyryderchuck.gitlab.io/rodauth-oauth/
177
- documentation_uri: https://honeyryderchuck.gitlab.io/rodauth-oauth/rdoc/
178
- bug_tracker_uri: https://gitlab.com/honeyryderchuck/rodauth-oauth/issues
179
- source_code_uri: https://gitlab.com/honeyryderchuck/rodauth-oauth
180
- changelog_uri: https://gitlab.com/honeyryderchuck/rodauth-oauth/-/blob/master/CHANGELOG.md
178
+ homepage_uri: https://os85.gitlab.io/rodauth-oauth/
179
+ documentation_uri: https://os85.gitlab.io/rodauth-oauth/rdoc/
180
+ bug_tracker_uri: https://gitlab.com/os85/rodauth-oauth/issues
181
+ source_code_uri: https://gitlab.com/os85/rodauth-oauth
182
+ changelog_uri: https://gitlab.com/os85/rodauth-oauth/-/blob/master/CHANGELOG.md
181
183
  rubygems_mfa_required: 'true'
182
184
  post_install_message:
183
185
  rdoc_options: []
@@ -190,11 +192,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
190
192
  version: 2.5.0
191
193
  required_rubygems_version: !ruby/object:Gem::Requirement
192
194
  requirements:
193
- - - ">"
195
+ - - ">="
194
196
  - !ruby/object:Gem::Version
195
- version: 1.3.1
197
+ version: '0'
196
198
  requirements: []
197
- rubygems_version: 3.2.32
199
+ rubygems_version: 3.3.7
198
200
  signing_key:
199
201
  specification_version: 4
200
202
  summary: Implementation of the OAuth 2.0 protocol on top of rodauth.
@@ -1,38 +0,0 @@
1
- ## 1.0.0-beta1 (21/10/2022)
2
-
3
- ### Breaking changes
4
-
5
- The full description of breaking changes, and suggestions on how to make the migration smoother, can be found in the [migration guide](https://gitlab.com/honeyryderchuck/rodauth-oauth/-/blob/6465b8522a78cf0037a55d3d4b81f68f7811be68/MIGRATION-GUIDE-v1.md).
6
-
7
- A short list of the main highlights:
8
-
9
-
10
- * Ruby 2.5 or higher is required.
11
- * `oauth_http_mac` feature removed.
12
- * `oauth_tokens` table (and resource) were removed (only `oauth_applications` and `oauth_grants`, access and refresh tokens are now properties of the latter).
13
- * access and refresh tokens hashed by default when stored in the database.
14
- * default oauth response mode is `"form_post"`.
15
- * oauth specific features require explicit enablement of respective features (no more `enable :oauth`)
16
- * refresh token policy is "rotation" by default
17
-
18
- ### Features
19
-
20
- The following helpers are exposed in the `rodauth` object:
21
-
22
- * `current_oauth_account` - returns the dataset row for the `rodauth` account associated to an oauth access token in the "authorization" header.
23
- * `current_oauth_application` - returns the dataset row for the oauth application associated to an oauth access token in the "authorization" header.
24
-
25
- When used in `rails` via `rodauth-rails`, both are exposed directly as controller helpers.
26
-
27
- #### `oauth_resource_server` plugin
28
-
29
- This plugin can be used as a convenience when configuring resource servers.
30
-
31
- ### Improvements
32
-
33
- * `:oauth_introspect` plugin: OAuth introspection endpoint exposes the token's `"username"` claim.
34
- * endpoint client authentication supports "client credentials grant" access tokens.
35
-
36
- ### Bugfixes
37
-
38
- * fixed `oidc` calculation of `"auth_time"` claim.
@@ -1,34 +0,0 @@
1
- This version passes the conformance tests for the following OpenID Connect certification profiles:
2
-
3
- * Basic certification
4
- * Form-post basic certification
5
- * Config certification
6
- * Dynamic Config certification (`response_type=code`)
7
-
8
- ## Breaking Changes
9
-
10
- * homepage url is no longer a client application required property.
11
- * OIDC RP-initiated logout extracted into `oidc_rp_initiated_logout` feature.
12
-
13
- ## Features
14
-
15
- * `oauth_jwt_secured_authorization_request` now supports a `request_uri` query param as well.
16
- * `oidc` supports essential claims, via the `claims` authorization request query parameter.
17
-
18
- ## Improvements
19
-
20
- * exposing `acr_values_supported` in the openid configuration.
21
- * `oauth_request_object_signing_alg_allow_none` enables `"none"` as an accepted request object signing alg when `true` (`false` by default).
22
- * OIDC `offline_access` supported.
23
-
24
- ## Bugfixes
25
-
26
- * JWT: "sub" is now always a string.
27
- * `response_type` is now an authorization request required parameter (as per the RFC).
28
- * `state` is now passed along when redirecting from authorization requeests with `error`;
29
- * access token can now be read from POST body or GET quety params (as per the RFC).
30
- * id token no longer shipping with claims with `null` value;
31
- * id token no longer encoding claims by default (only when `response_type=id_token`, as per the RFC).
32
- * support "JWT without kid" when doing jwt decoding for JWT tokens not generated in the provider (such as request objects).
33
- * Set `iss` and `aud` claims in the Userinfo JWT response.
34
- * Make sure errors are also delivered via form POST, when `response_mode=form_post`.