From 2095732d7cd4eeb80fe74c89e2a2d012517e40d6 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 8 Nov 2025 00:28:23 -0700 Subject: [PATCH] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20yard-fence=20v0.7.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 2 +- README.md | 2 +- docs/OAuth2.html | 6 +- docs/OAuth2/AccessToken.html | 52 +- docs/OAuth2/Authenticator.html | 6 +- docs/OAuth2/Client.html | 50 +- docs/OAuth2/Error.html | 4 +- docs/OAuth2/FilteredAttributes.html | 8 +- .../FilteredAttributes/ClassMethods.html | 2 +- docs/OAuth2/Response.html | 12 +- docs/OAuth2/Strategy.html | 2 +- docs/OAuth2/Strategy/Assertion.html | 60 +- docs/OAuth2/Strategy/AuthCode.html | 22 +- docs/OAuth2/Strategy/Base.html | 2 +- docs/OAuth2/Strategy/ClientCredentials.html | 2 +- docs/OAuth2/Strategy/Implicit.html | 14 +- docs/OAuth2/Strategy/Password.html | 14 +- docs/OAuth2/Version.html | 2 +- docs/_index.html | 2 +- docs/file.CHANGELOG.html | 118 +-- docs/file.CODE_OF_CONDUCT.html | 108 +-- docs/file.CONTRIBUTING.html | 130 +-- docs/file.FUNDING.html | 14 +- docs/file.IRP.html | 34 +- docs/file.LICENSE.html | 2 +- docs/file.OIDC.html | 105 ++- docs/file.README.html | 769 +++++++++--------- docs/file.RUBOCOP.html | 21 +- docs/file.SECURITY.html | 10 +- docs/file.THREAT_MODEL.html | 32 +- docs/index.html | 769 +++++++++--------- docs/top-level-namespace.html | 2 +- 32 files changed, 1161 insertions(+), 1217 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8c4b1f4b..4b8b7ebe 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -311,7 +311,7 @@ GEM uri (1.1.1) version_gem (1.1.9) yard (0.9.37) - yard-fence (0.5.0) + yard-fence (0.7.0) rdoc (~> 6.11) version_gem (~> 1.1, >= 1.1.9) yard (~> 0.9, >= 0.9.37) diff --git a/README.md b/README.md index 65e7d6bc..75c72dda 100644 --- a/README.md +++ b/README.md @@ -471,7 +471,7 @@ Expand the examples below, or the [ruby-oauth/snaky_hash](https://gitlab.com/rub or [response_spec.rb](https://github.com/ruby-oauth/oauth2/blob/main/spec/oauth2/response_spec.rb), for more ideas, especially if you need to study the hacks for older Rubies.
-See Examples + See Examples ```ruby class MySnakyHash < SnakyHash::StringKeyed diff --git a/docs/OAuth2.html b/docs/OAuth2.html index 8f5751f6..a55e82e2 100644 --- a/docs/OAuth2.html +++ b/docs/OAuth2.html @@ -119,8 +119,8 @@

OAUTH_DEBUG =
-

When true, enables verbose HTTP logging via Faraday’s logger middleware. -Controlled by the OAUTH_DEBUG environment variable. Any case-insensitive +

When true, enables verbose HTTP logging via Faraday’s logger middleware.
+Controlled by the OAUTH_DEBUG environment variable. Any case-insensitive
value equal to “true” will enable debugging.

@@ -415,7 +415,7 @@

diff --git a/docs/OAuth2/AccessToken.html b/docs/OAuth2/AccessToken.html index 1db81ede..70a207ea 100644 --- a/docs/OAuth2/AccessToken.html +++ b/docs/OAuth2/AccessToken.html @@ -826,13 +826,13 @@

Note: -

If no token is provided, the AccessToken will be considered invalid. -This is to prevent the possibility of a token being accidentally -created with no token value. -If you want to create an AccessToken with no token value, -you can pass in an empty string or nil for the token value. -If you want to create an AccessToken with no token value and -no refresh token, you can pass in an empty string or nil for the +

If no token is provided, the AccessToken will be considered invalid.
+This is to prevent the possibility of a token being accidentally
+created with no token value.
+If you want to create an AccessToken with no token value,
+you can pass in an empty string or nil for the token value.
+If you want to create an AccessToken with no token value and
+no refresh token, you can pass in an empty string or nil for the
token value and nil for the refresh token, and raise_errors: false.

@@ -987,9 +987,9 @@

Verb-dependent Hash mode

- —

the transmission mode of the Access Token parameter value: -either one of :header, :body or :query; or a Hash with verb symbols as keys mapping to one of these symbols -(e.g., {get: :query, post: :header, delete: :header}); or a callable that accepts a request-verb parameter + —

the transmission mode of the Access Token parameter value:
+either one of :header, :body or :query; or a Hash with verb symbols as keys mapping to one of these symbols
+(e.g., {get: :query, post: :header, delete: :header}); or a callable that accepts a request-verb parameter
and returns one of these three symbols.

@@ -1020,7 +1020,7 @@

Verb-dependent Hash mode

- —

the parameter name to use for transmission of the + —

the parameter name to use for transmission of the
Access Token value in :body or :query transmission mode

@@ -1036,7 +1036,7 @@

Verb-dependent Hash mode

- —

the name of the response parameter that identifies the access token + —

the name of the response parameter that identifies the access token
When nil one of TOKEN_KEY_LOOKUP will be used

@@ -1533,21 +1533,21 @@

Note: -

The method will use the first found token key in the following order: +

The method will use the first found token key in the following order:
‘access_token’, ‘id_token’, ‘token’ (or their symbolic versions)

Note: -

If multiple token keys are present, a warning will be issued unless +

If multiple token keys are present, a warning will be issued unless
OAuth2.config.silence_extra_tokens_warning is true

Note: -

If no token keys are present, a warning will be issued unless +

If no token keys are present, a warning will be issued unless
OAuth2.config.silence_no_tokens_warning is true

@@ -2746,28 +2746,28 @@

Note: -

If the token passed to the request -is an access token, the server MAY revoke the respective refresh +

If the token passed to the request
+is an access token, the server MAY revoke the respective refresh
token as well.

Note: -

If the token passed to the request -is a refresh token and the authorization server supports the -revocation of access tokens, then the authorization server SHOULD -also invalidate all access tokens based on the same authorization +

If the token passed to the request
+is a refresh token and the authorization server supports the
+revocation of access tokens, then the authorization server SHOULD
+also invalidate all access tokens based on the same authorization
grant

Note: -

If the server responds with HTTP status code 503, your code must -assume the token still exists and may retry after a reasonable delay. -The server may include a “Retry-After” header in the response to -indicate how long the service is expected to be unavailable to the +

If the server responds with HTTP status code 503, your code must
+assume the token still exists and may retry after a reasonable delay.
+The server may include a “Retry-After” header in the response to
+indicate how long the service is expected to be unavailable to the
requesting client.

@@ -3083,7 +3083,7 @@

diff --git a/docs/OAuth2/Authenticator.html b/docs/OAuth2/Authenticator.html index d1a2fe57..18d54f5b 100644 --- a/docs/OAuth2/Authenticator.html +++ b/docs/OAuth2/Authenticator.html @@ -108,7 +108,7 @@

Overview

Builds and applies client authentication to token and revoke requests.

-

Depending on the selected mode, credentials are applied as Basic Auth +

Depending on the selected mode, credentials are applied as Basic Auth
headers, request body parameters, or only the client_id is sent (TLS).

@@ -788,7 +788,7 @@

Apply the request credentials used to authenticate to the Authorization Server

-

Depending on the configuration, this might be as request params or as an +

Depending on the configuration, this might be as request params or as an
Authorization header.

User-provided params and header take precedence.

@@ -883,7 +883,7 @@

diff --git a/docs/OAuth2/Client.html b/docs/OAuth2/Client.html index 62bc3d6f..f46b4be7 100644 --- a/docs/OAuth2/Client.html +++ b/docs/OAuth2/Client.html @@ -1243,7 +1243,7 @@

The Assertion strategy

-

This allows for assertion-based authentication where an identity provider +

This allows for assertion-based authentication where an identity provider
asserts the identity of the user or client application seeking access.

@@ -1487,7 +1487,7 @@

Note: -

The extract_access_token parameter is deprecated and will be removed in oauth2 v3. +

The extract_access_token parameter is deprecated and will be removed in oauth2 v3.
Use access_token_class on initialization instead.

@@ -1523,10 +1523,12 @@

Examples:

— -

a Hash of params for the token endpoint -* params can include a ‘headers’ key with a Hash of request headers -* params can include a ‘parse’ key with the Symbol name of response parsing strategy (default: :automatic) -* params can include a ‘snaky’ key to control snake_case conversion (default: false)

+

a Hash of params for the token endpoint

+
    +
  • params can include a ‘headers’ key with a Hash of request headers
  • +
  • params can include a ‘parse’ key with the Symbol name of response parsing strategy (default: :automatic)
  • +
  • params can include a ‘snaky’ key to control snake_case conversion (default: false)
  • +
@@ -1614,7 +1616,7 @@

Examples:

— -

the initialized AccessToken instance, or nil if token extraction fails +

the initialized AccessToken instance, or nil if token extraction fails
and raise_errors is false

@@ -1837,14 +1839,14 @@

The redirect_uri parameters, if configured

-

The redirect_uri query parameter is OPTIONAL (though encouraged) when -requesting authorization. If it is provided at authorization time it MUST +

The redirect_uri query parameter is OPTIONAL (though encouraged) when
+requesting authorization. If it is provided at authorization time it MUST
also be provided with the token exchange request.

-

OAuth 2.1 note: Authorization Servers must compare redirect URIs using exact string matching. +

OAuth 2.1 note: Authorization Servers must compare redirect URIs using exact string matching.
This client simply forwards the configured redirect_uri; the exact-match validation happens server-side.

-

Providing :redirect_uri to the OAuth2::Client instantiation will take +

Providing :redirect_uri to the OAuth2::Client instantiation will take
care of managing this.

@@ -1927,7 +1929,7 @@

Makes a request relative to the specified site root.

-

Updated HTTP 1.1 specification (IETF RFC 7231) relaxed the original constraint (IETF RFC 2616), +

Updated HTTP 1.1 specification (IETF RFC 7231) relaxed the original constraint (IETF RFC 2616),
allowing the use of relative URLs in Location headers.

@@ -2039,7 +2041,7 @@

- —

whether to raise an OAuth2::Error on 400+ status + —

whether to raise an OAuth2::Error on 400+ status
code response for this request. Overrides the client instance setting.

@@ -2241,28 +2243,28 @@

Note: -

If the token passed to the request -is an access token, the server MAY revoke the respective refresh +

If the token passed to the request
+is an access token, the server MAY revoke the respective refresh
token as well.

Note: -

If the token passed to the request -is a refresh token and the authorization server supports the -revocation of access tokens, then the authorization server SHOULD -also invalidate all access tokens based on the same authorization +

If the token passed to the request
+is a refresh token and the authorization server supports the
+revocation of access tokens, then the authorization server SHOULD
+also invalidate all access tokens based on the same authorization
grant

Note: -

If the server responds with HTTP status code 503, your code must -assume the token still exists and may retry after a reasonable delay. -The server may include a “Retry-After” header in the response to -indicate how long the service is expected to be unavailable to the +

If the server responds with HTTP status code 503, your code must
+assume the token still exists and may retry after a reasonable delay.
+The server may include a “Retry-After” header in the response to
+indicate how long the service is expected to be unavailable to the
requesting client.

@@ -2654,7 +2656,7 @@

diff --git a/docs/OAuth2/Error.html b/docs/OAuth2/Error.html index 68262584..6892113d 100644 --- a/docs/OAuth2/Error.html +++ b/docs/OAuth2/Error.html @@ -105,7 +105,7 @@

Overview

Represents an OAuth2 error condition.

-

Wraps details from an OAuth2::Response or Hash payload returned by an +

Wraps details from an OAuth2::Response or Hash payload returned by an
authorization server, exposing error code and description per RFC 6749.

@@ -772,7 +772,7 @@

diff --git a/docs/OAuth2/FilteredAttributes.html b/docs/OAuth2/FilteredAttributes.html index 13010514..e5ea7de7 100644 --- a/docs/OAuth2/FilteredAttributes.html +++ b/docs/OAuth2/FilteredAttributes.html @@ -92,8 +92,8 @@

Overview

Mixin that redacts sensitive instance variables in #inspect output.

-

Classes include this module and declare which attributes should be filtered -using filtered_attributes. Any instance variable name that includes one of +

Classes include this module and declare which attributes should be filtered
+using filtered_attributes. Any instance variable name that includes one of
those attribute names will be shown as [FILTERED] in the object’s inspect.

@@ -202,7 +202,7 @@

-

This method returns an undefined value.

Hook invoked when the module is included. Extends the including class with +

This method returns an undefined value.

Hook invoked when the module is included. Extends the including class with
class-level helpers.

@@ -335,7 +335,7 @@

diff --git a/docs/OAuth2/FilteredAttributes/ClassMethods.html b/docs/OAuth2/FilteredAttributes/ClassMethods.html index 2f746c4c..4d6ef536 100644 --- a/docs/OAuth2/FilteredAttributes/ClassMethods.html +++ b/docs/OAuth2/FilteredAttributes/ClassMethods.html @@ -280,7 +280,7 @@

diff --git a/docs/OAuth2/Response.html b/docs/OAuth2/Response.html index 79f4fe2f..a8bd2548 100644 --- a/docs/OAuth2/Response.html +++ b/docs/OAuth2/Response.html @@ -101,7 +101,7 @@

Overview

-

The Response class handles HTTP responses in the OAuth2 gem, providing methods +

The Response class handles HTTP responses in the OAuth2 gem, providing methods
to access and parse response data in various formats.

@@ -1430,22 +1430,22 @@

Note: -

The parser can be supplied as the +:parse+ option in the form of a Proc -(or other Object responding to #call) or a Symbol. In the latter case, +

The parser can be supplied as the +:parse+ option in the form of a Proc
+(or other Object responding to #call) or a Symbol. In the latter case,
the actual parser will be looked up in @@parsers by the supplied Symbol.

Note: -

If no +:parse+ option is supplied, the lookup Symbol will be determined +

If no +:parse+ option is supplied, the lookup Symbol will be determined
by looking up #content_type in @@content_types.

Note: -

If #parser is a Proc, it will be called with no arguments, just +

If #parser is a Proc, it will be called with no arguments, just
#body, or #body and #response, depending on the Proc’s arity.

@@ -1619,7 +1619,7 @@

diff --git a/docs/OAuth2/Strategy.html b/docs/OAuth2/Strategy.html index ba6f9096..a12ec766 100644 --- a/docs/OAuth2/Strategy.html +++ b/docs/OAuth2/Strategy.html @@ -107,7 +107,7 @@

Defined Under Namespace

diff --git a/docs/OAuth2/Strategy/Assertion.html b/docs/OAuth2/Strategy/Assertion.html index 24d17623..11cb1f95 100644 --- a/docs/OAuth2/Strategy/Assertion.html +++ b/docs/OAuth2/Strategy/Assertion.html @@ -105,25 +105,25 @@

Overview

The Client Assertion Strategy

-

Sample usage: - client = OAuth2::Client.new(client_id, client_secret, - :site => ‘http://localhost:8080’, +

Sample usage:
+ client = OAuth2::Client.new(client_id, client_secret,
+ :site => ‘http://localhost:8080’,
:auth_scheme => :request_body)

-

claim_set = { - :iss => “http://localhost:3001”, - :aud => “http://localhost:8080/oauth2/token”, - :sub => “me@example.com”, - :exp => Time.now.utc.to_i + 3600, +

claim_set = {
+ :iss => “http://localhost:3001”,
+ :aud => “http://localhost:8080/oauth2/token”,
+ :sub => “me@example.com”,
+ :exp => Time.now.utc.to_i + 3600,
}

-

encoding = { - :algorithm => ‘HS256’, - :key => ‘secret_key’, +

encoding = {
+ :algorithm => ‘HS256’,
+ :key => ‘secret_key’,
}

-

access = client.assertion.get_token(claim_set, encoding) - access.token # actual access_token string +

access = client.assertion.get_token(claim_set, encoding)
+ access.token # actual access_token string
access.get(“/api/stuff”) # making api calls with access token in header

@@ -292,28 +292,28 @@

Retrieve an access token given the specified client.

-

For reading on JWT and claim keys: - @see https://github.com/jwt/ruby-jwt - @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1 - @see https://datatracker.ietf.org/doc/html/rfc7523#section-3 +

For reading on JWT and claim keys:
+ @see https://github.com/jwt/ruby-jwt
+ @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
+ @see https://datatracker.ietf.org/doc/html/rfc7523#section-3
@see https://www.iana.org/assignments/jwt/jwt.xhtml

-

There are many possible claim keys, and applications may ask for their own custom keys. -Some typically required ones: - :iss (issuer) - :aud (audience) - :sub (subject) – formerly :prn https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token-06#appendix-F +

There are many possible claim keys, and applications may ask for their own custom keys.
+Some typically required ones:
+ :iss (issuer)
+ :aud (audience)
+ :sub (subject) – formerly :prn https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token-06#appendix-F
:exp, (expiration time) – in seconds, e.g. Time.now.utc.to_i + 3600

-

Note that this method does not validate presence of those four claim keys indicated as required by RFC 7523. +

Note that this method does not validate presence of those four claim keys indicated as required by RFC 7523.
There are endpoints that may not conform with this RFC, and this gem should still work for those use cases.

-

These two options are passed directly to JWT.encode. For supported encoding arguments: - @see https://github.com/jwt/ruby-jwt#algorithms-and-usage +

These two options are passed directly to JWT.encode. For supported encoding arguments:
+ @see https://github.com/jwt/ruby-jwt#algorithms-and-usage
@see https://datatracker.ietf.org/doc/html/rfc7518#section-3.1

-

The object type of :key may depend on the value of :algorithm. Sample arguments: - get_token(claim_set, {:algorithm => 'HS256', :key => 'secret_key'}) +

The object type of :key may depend on the value of :algorithm. Sample arguments:
+ get_token(claim_set, {:algorithm => 'HS256', :key => 'secret_key'})
get_token(claim_set, {:algorithm => 'RS256', :key => OpenSSL::PKCS12.new(File.read('my_key.p12'), 'not_secret')})

@@ -382,7 +382,7 @@

— -

this will be merged with the token response to create the AccessToken object +

this will be merged with the token response to create the AccessToken object
@see the access_token_opts argument to Client#get_token

@@ -437,7 +437,7 @@

- —

the url parameter scope that may be required by some endpoints + —

the url parameter scope that may be required by some endpoints
@see https://datatracker.ietf.org/doc/html/rfc7521#section-4.1

@@ -481,7 +481,7 @@

diff --git a/docs/OAuth2/Strategy/AuthCode.html b/docs/OAuth2/Strategy/AuthCode.html index 90253890..4901a6c1 100644 --- a/docs/OAuth2/Strategy/AuthCode.html +++ b/docs/OAuth2/Strategy/AuthCode.html @@ -105,15 +105,19 @@

Overview

The Authorization Code Strategy

-

OAuth 2.1 notes: -- PKCE is required for all OAuth clients using the authorization code flow (especially public clients). - This library does not enforce PKCE generation/verification; implement PKCE in your application when required. -- Redirect URIs must be compared using exact string matching by the Authorization Server. - This client forwards redirect_uri but does not perform server-side validation.

+

OAuth 2.1 notes:

+
    +
  • PKCE is required for all OAuth clients using the authorization code flow (especially public clients).
    +This library does not enforce PKCE generation/verification; implement PKCE in your application when required.
  • +
  • Redirect URIs must be compared using exact string matching by the Authorization Server.
    +This client forwards redirect_uri but does not perform server-side validation.
  • +
-

References: -- OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13 -- OAuth for native apps (RFC 8252) and PKCE (RFC 7636)

+

References:

+
    +
  • OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
  • +
  • OAuth for native apps (RFC 8252) and PKCE (RFC 7636)
  • +
@@ -479,7 +483,7 @@

diff --git a/docs/OAuth2/Strategy/Base.html b/docs/OAuth2/Strategy/Base.html index 7ad9e5bb..98b85f29 100644 --- a/docs/OAuth2/Strategy/Base.html +++ b/docs/OAuth2/Strategy/Base.html @@ -195,7 +195,7 @@

diff --git a/docs/OAuth2/Strategy/ClientCredentials.html b/docs/OAuth2/Strategy/ClientCredentials.html index 1c69124f..57fd20cd 100644 --- a/docs/OAuth2/Strategy/ClientCredentials.html +++ b/docs/OAuth2/Strategy/ClientCredentials.html @@ -343,7 +343,7 @@

diff --git a/docs/OAuth2/Strategy/Implicit.html b/docs/OAuth2/Strategy/Implicit.html index b03f9047..0fe96e09 100644 --- a/docs/OAuth2/Strategy/Implicit.html +++ b/docs/OAuth2/Strategy/Implicit.html @@ -105,13 +105,15 @@

Overview

The Implicit Strategy

-

IMPORTANT (OAuth 2.1): The Implicit grant (response_type=token) is omitted from the OAuth 2.1 draft specification. +

IMPORTANT (OAuth 2.1): The Implicit grant (response_type=token) is omitted from the OAuth 2.1 draft specification.
It remains here for backward compatibility with OAuth 2.0 providers. Prefer the Authorization Code flow with PKCE.

-

References: -- OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13 -- Why drop implicit: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1 -- Background: https://fusionauth.io/learn/expert-advice/oauth/differences-between-oauth-2-oauth-2-1/

+

References:

+
    +
  • OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
  • +
  • Why drop implicit: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1
  • +
  • Background: https://fusionauth.io/learn/expert-advice/oauth/differences-between-oauth-2-oauth-2-1/
  • +
@@ -418,7 +420,7 @@

diff --git a/docs/OAuth2/Strategy/Password.html b/docs/OAuth2/Strategy/Password.html index 83b4d0be..698b755e 100644 --- a/docs/OAuth2/Strategy/Password.html +++ b/docs/OAuth2/Strategy/Password.html @@ -105,13 +105,15 @@

Overview

The Resource Owner Password Credentials Authorization Strategy

-

IMPORTANT (OAuth 2.1): The Resource Owner Password Credentials grant is omitted in OAuth 2.1. +

IMPORTANT (OAuth 2.1): The Resource Owner Password Credentials grant is omitted in OAuth 2.1.
It remains here for backward compatibility with OAuth 2.0 providers. Prefer Authorization Code + PKCE.

-

References: -- OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13 -- Okta explainer: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs -- FusionAuth blog: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1

+

References:

+
    +
  • OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
  • +
  • Okta explainer: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs
  • +
  • FusionAuth blog: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1
  • +
@@ -372,7 +374,7 @@

diff --git a/docs/OAuth2/Version.html b/docs/OAuth2/Version.html index a997000b..fd949561 100644 --- a/docs/OAuth2/Version.html +++ b/docs/OAuth2/Version.html @@ -111,7 +111,7 @@

diff --git a/docs/_index.html b/docs/_index.html index 0535ab06..a73441e6 100644 --- a/docs/_index.html +++ b/docs/_index.html @@ -315,7 +315,7 @@

Namespace Listing A-Z

diff --git a/docs/file.CHANGELOG.html b/docs/file.CHANGELOG.html index ce161097..9720289f 100644 --- a/docs/file.CHANGELOG.html +++ b/docs/file.CHANGELOG.html @@ -63,9 +63,9 @@

All notable changes to this project will be documented in this file.

-

The format is based on Keep a Changelog, -and this project adheres to Semantic Versioning, -and yes, platform and engine support are part of the public API. +

The format is based on Keep a Changelog,
+and this project adheres to Semantic Versioning,
+and yes, platform and engine support are part of the public API.
Please file a bug if you notice a violation of semantic versioning.

Unreleased

@@ -106,7 +106,7 @@

Fixed

Security

-

+

2.0.17 - 2025-09-15

    @@ -124,7 +124,7 @@

    Added

    gh!682 - AccessToken: support Hash-based verb-dependent token transmission mode (e.g., {get: :query, post: :header})
-

+

2.0.16 - 2025-09-14

    @@ -166,7 +166,7 @@

    Changed

    gh!681 - Upgrade to kettle-dev v1.1.19
-

+

2.0.15 - 2025-09-08

    @@ -211,7 +211,7 @@

    Fixed

  • point badge to the correct workflow for Ruby 2.3 (caboose.yml)
-

+

2.0.14 - 2025-08-31

    @@ -255,7 +255,7 @@

    Added

    gh!664 - README: Add example for JHipster UAA (Spring Cloud) password grant, converted from Postman/Net::HTTP by @pboling
-

+

2.0.13 - 2025-08-30

    @@ -300,7 +300,7 @@

    Fixed

    Security

    -

    +

    2.0.12 - 2025-05-31

      @@ -341,7 +341,7 @@

      Fixed

    • Documentation Typos by @pboling
    -

    +

    2.0.11 - 2025-05-23

      @@ -402,7 +402,7 @@

      Fixed

    • Incorrect documentation related to silencing warnings (@pboling)
    -

    +

    2.0.10 - 2025-05-17

      @@ -509,7 +509,7 @@

      Fixed

      gh!646 - Change require to require_relative (improve performance) (@Aboling0)
    -

    +

    2.0.9 - 2022-09-16

      @@ -530,7 +530,7 @@

      Changed

    • Complete migration to Gitlab, updating all links, and references in VCS-managed files (@pboling)
    -

    +

    2.0.8 - 2022-09-01

      @@ -553,7 +553,7 @@

      Added

    -

    +

    2.0.7 - 2022-08-22

      @@ -581,7 +581,7 @@

      Fixed

      !625 - Fixes the printed version in the post install message (@hasghari)
    -

    +

    2.0.6 - 2022-07-13

      @@ -596,7 +596,7 @@

      Fixed

      !624 - Fixes a regression in v2.0.5, where an error would be raised in refresh_token flows due to (legitimate) lack of access_token (@pboling)
    -

    +

    2.0.5 - 2022-07-07

      @@ -626,7 +626,7 @@

      Fixed

    -

    +

    2.0.4 - 2022-07-01

      @@ -641,7 +641,7 @@

      Fixed

      !618 - In some scenarios the snaky option default value was not applied (@pboling)
    -

    +

    2.0.3 - 2022-06-28

      @@ -667,7 +667,7 @@

      Fixed

      !615 - Fix support for requests with blocks, see Faraday::Connection#run_request (@pboling)
    -

    +

    2.0.2 - 2022-06-24

      @@ -686,7 +686,7 @@

      Fixed

      !607 - CHANGELOG correction, reference to OAuth2::ConnectionError (@zavan)
    -

    +

    2.0.1 - 2022-06-22

      @@ -701,7 +701,7 @@

      Added

    • Increased test coverage to 99% (@pboling)
    -

    +

    2.0.0 - 2022-06-21

      @@ -859,7 +859,7 @@

      Removed

      !590 - Dependency: Removed multi_json (@stanhu)
    -

    +

    1.4.11 - 2022-09-16

      @@ -869,7 +869,7 @@

    • Complete migration to Gitlab, updating all links, and references in VCS-managed files (@pboling)
    -

    +

    1.4.10 - 2022-07-01

      @@ -878,7 +878,7 @@

    • FIPS Compatibility !587 (@akostadinov)
    -

    +

    1.4.9 - 2022-02-20

      @@ -896,7 +896,7 @@

    • Add Windows and MacOS to test matrix
    -

    +

    1.4.8 - 2022-02-18

      @@ -913,7 +913,7 @@

      !543 - Support for more modern Open SSL libraries (@pboling)

    -

    +

    1.4.7 - 2021-03-19

      @@ -923,7 +923,7 @@

      !541 - Backport fix to expires_at handling !533 to 1-4-stable branch. (@dobon)

    -

    +

    1.4.6 - 2021-03-19

      @@ -937,7 +937,7 @@

      !538 - Remove reliance on globally included OAuth2 in tests, analogous to !539 on main branch (@anderscarling)

    -

    +

    1.4.5 - 2021-03-18

      @@ -953,7 +953,7 @@

      !500 - Fix YARD documentation formatting (@olleolleolle)

    -

    +

    1.4.4 - 2020-02-12

      @@ -963,7 +963,7 @@

      !408 - Fixed expires_at for formatted time (@Lomey)

    -

    +

    1.4.3 - 2020-01-29

      @@ -981,7 +981,7 @@

      !433 - allow field names with square brackets and numbers in params (@asm256)

    -

    +

    1.4.2 - 2019-10-01

      @@ -995,7 +995,7 @@

    -

    +

    1.4.1 - 2018-10-13

      @@ -1039,7 +1039,7 @@

    -

    +

    1.4.0 - 2017-06-09

      @@ -1053,7 +1053,7 @@

      Dependency: Upgrade Faraday to 0.12 (@sferik)

    -

    +

    1.3.1 - 2017-03-03

      @@ -1064,7 +1064,7 @@

      Dependency: Upgrade Faraday to Faraday 0.11 (@mcfiredrill, @rhymes, @pschambacher)

    -

    +

    1.3.0 - 2016-12-28

      @@ -1080,7 +1080,7 @@

    • Add support for Faraday 0.10 (@rhymes)
    -

    +

    1.2.0 - 2016-07-01

      @@ -1091,7 +1091,7 @@

    • Use raise rather than fail to throw exceptions (@sferik)
    -

    +

    1.1.0 - 2016-01-30

      @@ -1101,7 +1101,7 @@

    • Add support for Rack 2, and bump various other dependencies (@sferik)
    -

    +

    1.0.0 - 2014-07-09

      @@ -1121,7 +1121,7 @@

      Fixed

    • Fix Base64.strict_encode64 incompatibility with Ruby 1.8.7.
    -

    +

    0.5.0 - 2011-07-29

      @@ -1144,7 +1144,7 @@

      Changed

      breaking web_server renamed to auth_code.
    -

    +

    0.4.1 - 2011-04-20

      @@ -1152,7 +1152,7 @@

    -

    +

    0.4.0 - 2011-04-20

      @@ -1160,7 +1160,7 @@

    -

    +

    0.3.0 - 2011-04-08

      @@ -1168,7 +1168,7 @@

    -

    +

    0.2.0 - 2011-04-01

      @@ -1176,7 +1176,7 @@

    -

    +

    0.1.1 - 2011-01-12

      @@ -1184,7 +1184,7 @@

    -

    +

    0.1.0 - 2010-10-13

      @@ -1192,7 +1192,7 @@

    -

    +

    0.0.13 - 2010-08-17

      @@ -1200,7 +1200,7 @@

    -

    +

    0.0.12 - 2010-08-17

      @@ -1208,7 +1208,7 @@

    -

    +

    0.0.11 - 2010-08-17

      @@ -1216,7 +1216,7 @@

    -

    +

    0.0.10 - 2010-06-19

      @@ -1224,7 +1224,7 @@

    -

    +

    0.0.9 - 2010-06-18

      @@ -1232,7 +1232,7 @@

    -

    +

    0.0.8 - 2010-04-27

      @@ -1240,7 +1240,7 @@

    -

    +

    0.0.7 - 2010-04-27

      @@ -1248,7 +1248,7 @@

    -

    +

    0.0.6 - 2010-04-25

      @@ -1256,7 +1256,7 @@

    -

    +

    0.0.5 - 2010-04-23

      @@ -1264,7 +1264,7 @@

    -

    +

    0.0.4 - 2010-04-22

      @@ -1272,7 +1272,7 @@

    -

    +

    0.0.3 - 2010-04-22

      @@ -1280,7 +1280,7 @@

    -

    +

    0.0.2 - 2010-04-22

      @@ -1288,7 +1288,7 @@

    -

    +

    0.0.1 - 2010-04-22

      @@ -1299,7 +1299,7 @@

diff --git a/docs/file.CODE_OF_CONDUCT.html b/docs/file.CODE_OF_CONDUCT.html index 732d3293..28cfa69c 100644 --- a/docs/file.CODE_OF_CONDUCT.html +++ b/docs/file.CODE_OF_CONDUCT.html @@ -61,137 +61,137 @@

Our Pledge

-

We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual +

We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.

-

We pledge to act and interact in ways that contribute to an open, welcoming, +

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

Our Standards

-

Examples of behavior that contributes to a positive environment for our +

Examples of behavior that contributes to a positive environment for our
community include:

  • Demonstrating empathy and kindness toward other people
  • Being respectful of differing opinions, viewpoints, and experiences
  • Giving and gracefully accepting constructive feedback
  • -
  • Accepting responsibility and apologizing to those affected by our mistakes, +
  • Accepting responsibility and apologizing to those affected by our mistakes,
    and learning from the experience
  • -
  • Focusing on what is best not just for us as individuals, but for the overall +
  • Focusing on what is best not just for us as individuals, but for the overall
    community

Examples of unacceptable behavior include:

    -
  • The use of sexualized language or imagery, and sexual attention or advances of +
  • The use of sexualized language or imagery, and sexual attention or advances of
    any kind
  • Trolling, insulting or derogatory comments, and personal or political attacks
  • Public or private harassment
  • -
  • Publishing others’ private information, such as a physical or email address, +
  • Publishing others’ private information, such as a physical or email address,
    without their explicit permission
  • -
  • Other conduct which could reasonably be considered inappropriate in a +
  • Other conduct which could reasonably be considered inappropriate in a
    professional setting

Enforcement Responsibilities

-

Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, +

Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

-

Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation +

Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

Scope

-

This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official email address, -posting via an official social media account, or acting as an appointed +

This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official email address,
+posting via an official social media account, or acting as an appointed
representative at an online or offline event.

Enforcement

-

Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -Contact Maintainer. +

Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+Contact Maintainer.
All complaints will be reviewed and investigated promptly and fairly.

-

All community leaders are obligated to respect the privacy and security of the +

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

Enforcement Guidelines

-

Community leaders will follow these Community Impact Guidelines in determining +

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

-

1. Correction

+

1. Correction

-

Community Impact: Use of inappropriate language or other behavior deemed +

Community Impact: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

-

Consequence: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the +

Consequence: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

-

2. Warning

+

2. Warning

-

Community Impact: A violation through a single incident or series of +

Community Impact: A violation through a single incident or series of
actions.

-

Consequence: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent +

Consequence: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
ban.

-

3. Temporary Ban

+

3. Temporary Ban

-

Community Impact: A serious violation of community standards, including +

Community Impact: A serious violation of community standards, including
sustained inappropriate behavior.

-

Consequence: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. +

Consequence: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

-

4. Permanent Ban

+

4. Permanent Ban

-

Community Impact: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an +

Community Impact: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

-

Consequence: A permanent ban from any sort of public interaction within the +

Consequence: A permanent ban from any sort of public interaction within the
community.

Attribution

-

This Code of Conduct is adapted from the Contributor Covenant, -version 2.1, available at +

This Code of Conduct is adapted from the Contributor Covenant,
+version 2.1, available at
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.

-

Community Impact Guidelines were inspired by +

Community Impact Guidelines were inspired by
Mozilla’s code of conduct enforcement ladder.

-

For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at +

For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

diff --git a/docs/file.CONTRIBUTING.html b/docs/file.CONTRIBUTING.html index 48eef79e..65efad98 100644 --- a/docs/file.CONTRIBUTING.html +++ b/docs/file.CONTRIBUTING.html @@ -59,8 +59,8 @@

Contributing

-

Bug reports and pull requests are welcome on CodeBerg, GitLab, or GitHub. -This project should be a safe, welcoming space for collaboration, so contributors agree to adhere to +

Bug reports and pull requests are welcome on CodeBerg, GitLab, or GitHub.
+This project should be a safe, welcoming space for collaboration, so contributors agree to adhere to
the code of conduct.

To submit a patch, please fork the project, create a patch with tests, and send a pull request.

@@ -85,7 +85,7 @@

Help out!

Executables vs Rake tasks

-

Executables shipped by dependencies, such as kettle-dev, and stone_checksums, are available +

Executables shipped by dependencies, such as kettle-dev, and stone_checksums, are available
after running bin/setup. These include:

    @@ -101,56 +101,68 @@

    Executables vs Rake tasks

    There are many Rake tasks available as well. You can see them by running:

    -

    shell -bin/rake -T -

    +
    bin/rake -T
    +

    Environment Variables for Local Development

    Below are the primary environment variables recognized by stone_checksums (and its integrated tools). Unless otherwise noted, set boolean values to the string “true” to enable.

    -

    General/runtime -- DEBUG: Enable extra internal logging for this library (default: false) -- REQUIRE_BENCH: Enable require_bench to profile requires (default: false) -- CI: When set to true, adjusts default rake tasks toward CI behavior

    - -

    Coverage (kettle-soup-cover / SimpleCov) -- K_SOUP_COV_DO: Enable coverage collection (default: true in .envrc) -- K_SOUP_COV_FORMATTERS: Comma-separated list of formatters (html, xml, rcov, lcov, json, tty) -- K_SOUP_COV_MIN_LINE: Minimum line coverage threshold (integer, e.g., 100) -- K_SOUP_COV_MIN_BRANCH: Minimum branch coverage threshold (integer, e.g., 100) -- K_SOUP_COV_MIN_HARD: Fail the run if thresholds are not met (true/false) -- K_SOUP_COV_MULTI_FORMATTERS: Enable multiple formatters at once (true/false) -- K_SOUP_COV_OPEN_BIN: Path to browser opener for HTML (empty disables auto-open) -- MAX_ROWS: Limit console output rows for simplecov-console (e.g., 1) - Tip: When running a single spec file locally, you may want K_SOUP_COV_MIN_HARD=false to avoid failing thresholds for a partial run.

    - -

    GitHub API and CI helpers -- GITHUB_TOKEN or GH_TOKEN: Token used by ci:act and release workflow checks to query GitHub Actions status at higher rate limits

    - -

    Releasing and signing -- SKIP_GEM_SIGNING: If set, skip gem signing during build/release -- GEM_CERT_USER: Username for selecting your public cert in certs/<USER>.pem (defaults to $USER) -- SOURCE_DATE_EPOCH: Reproducible build timestamp. - - kettle-release will set this automatically for the session. - - Not needed on bundler >= 2.7.0, as reproducible builds have become the default.

    - -

    Git hooks and commit message helpers (exe/kettle-commit-msg) -- GIT_HOOK_BRANCH_VALIDATE: Branch name validation mode (e.g., jira) or false to disable -- GIT_HOOK_FOOTER_APPEND: Append a footer to commit messages when goalie allows (true/false) -- GIT_HOOK_FOOTER_SENTINEL: Required when footer append is enabled — a unique first-line sentinel to prevent duplicates -- GIT_HOOK_FOOTER_APPEND_DEBUG: Extra debug output in the footer template (true/false)

    +

    General/runtime

    +
      +
    • DEBUG: Enable extra internal logging for this library (default: false)
    • +
    • REQUIRE_BENCH: Enable require_bench to profile requires (default: false)
    • +
    • CI: When set to true, adjusts default rake tasks toward CI behavior
    • +
    + +

    Coverage (kettle-soup-cover / SimpleCov)

    +
      +
    • K_SOUP_COV_DO: Enable coverage collection (default: true in .envrc)
    • +
    • K_SOUP_COV_FORMATTERS: Comma-separated list of formatters (html, xml, rcov, lcov, json, tty)
    • +
    • K_SOUP_COV_MIN_LINE: Minimum line coverage threshold (integer, e.g., 100)
    • +
    • K_SOUP_COV_MIN_BRANCH: Minimum branch coverage threshold (integer, e.g., 100)
    • +
    • K_SOUP_COV_MIN_HARD: Fail the run if thresholds are not met (true/false)
    • +
    • K_SOUP_COV_MULTI_FORMATTERS: Enable multiple formatters at once (true/false)
    • +
    • K_SOUP_COV_OPEN_BIN: Path to browser opener for HTML (empty disables auto-open)
    • +
    • MAX_ROWS: Limit console output rows for simplecov-console (e.g., 1)
      +Tip: When running a single spec file locally, you may want K_SOUP_COV_MIN_HARD=false to avoid failing thresholds for a partial run.
    • +
    + +

    GitHub API and CI helpers

    +
      +
    • GITHUB_TOKEN or GH_TOKEN: Token used by ci:act and release workflow checks to query GitHub Actions status at higher rate limits
    • +
    + +

    Releasing and signing

    +
      +
    • SKIP_GEM_SIGNING: If set, skip gem signing during build/release
    • +
    • GEM_CERT_USER: Username for selecting your public cert in certs/<USER>.pem (defaults to $USER)
    • +
    • SOURCE_DATE_EPOCH: Reproducible build timestamp. +
        +
      • +kettle-release will set this automatically for the session.
      • +
      • Not needed on bundler >= 2.7.0, as reproducible builds have become the default.
      • +
      +
    • +
    + +

    Git hooks and commit message helpers (exe/kettle-commit-msg)

    +
      +
    • GIT_HOOK_BRANCH_VALIDATE: Branch name validation mode (e.g., jira) or false to disable
    • +
    • GIT_HOOK_FOOTER_APPEND: Append a footer to commit messages when goalie allows (true/false)
    • +
    • GIT_HOOK_FOOTER_SENTINEL: Required when footer append is enabled — a unique first-line sentinel to prevent duplicates
    • +
    • GIT_HOOK_FOOTER_APPEND_DEBUG: Extra debug output in the footer template (true/false)
    • +

    For a quick starting point, this repository’s .envrc shows sane defaults, and .env.local can override them locally.

    Appraisals

    -

    From time to time the appraisal2 gemfiles in gemfiles/ will need to be updated. +

    From time to time the appraisal2 gemfiles in gemfiles/ will need to be updated.
    They are created and updated with the commands:

    -

    console -bin/rake appraisal:update -

    +
    bin/rake appraisal:update
    +

    When adding an appraisal to CI, check the runner tool cache to see which runner to use.

    @@ -160,17 +172,15 @@

    The Reek List

    To refresh the reek list:

    -

    console -bundle exec reek > REEK -

    +
    bundle exec reek > REEK
    +

    Run Tests

    To run all tests

    -

    console -bundle exec rake test -

    +
    bundle exec rake test
    +

    Spec organization (required)

    @@ -183,15 +193,13 @@

    Lint It

    Run all the default tasks, which includes running the gradually autocorrecting linter, rubocop-gradual.

    -

    console -bundle exec rake -

    +
    bundle exec rake
    +

    Or just run the linter.

    -

    console -bundle exec rake rubocop_gradual:autocorrect -

    +
    bundle exec rake rubocop_gradual:autocorrect
    +

    For more detailed information about using RuboCop in this project, please see the RUBOCOP.md guide. This project uses rubocop_gradual instead of vanilla RuboCop, which requires specific commands for checking violations.

    @@ -227,10 +235,10 @@

    For Maintainers

    One-time, Per-maintainer, Setup

    -

    IMPORTANT: To sign a build, -a public key for signing gems will need to be picked up by the line in the -gemspec defining the spec.cert_chain (check the relevant ENV variables there). -All releases are signed releases. +

    IMPORTANT: To sign a build,
    +a public key for signing gems will need to be picked up by the line in the
    +gemspec defining the spec.cert_chain (check the relevant ENV variables there).
    +All releases are signed releases.
    See: RubyGems Security Guide

    NOTE: To build without signing the gem set SKIP_GEM_SIGNING to any value in the environment.

    @@ -277,8 +285,8 @@

    Manual process

  • Run bundle exec rake build
  • -
  • Run bin/gem_checksums (more context 1, 2) -to create SHA-256 and SHA-512 checksums. This functionality is provided by the stone_checksums +
  • Run bin/gem_checksums (more context 1, 2)
    +to create SHA-256 and SHA-512 checksums. This functionality is provided by the stone_checksums
    gem.
    • The script automatically commits but does not push the checksums
    • @@ -289,14 +297,14 @@

      Manual process

    • sha256sum pkg/<gem name>-<version>.gem
  • -
  • Run bundle exec rake release which will create a git tag for the version, +
  • Run bundle exec rake release which will create a git tag for the version,
    push git commits and tags, and push the .gem file to the gem host configured in the gemspec.
diff --git a/docs/file.FUNDING.html b/docs/file.FUNDING.html index 6d232159..0eb9ce7d 100644 --- a/docs/file.FUNDING.html +++ b/docs/file.FUNDING.html @@ -69,13 +69,13 @@ -

🤑 A request for help

+

🤑 A request for help

-

Maintainers have teeth and need to pay their dentists. -After getting laid off in an RIF in March, and encountering difficulty finding a new one, -I began spending most of my time building open source tools. -I’m hoping to be able to pay for my kids’ health insurance this month, -so if you value the work I am doing, I need your support. +

Maintainers have teeth and need to pay their dentists.
+After getting laid off in an RIF in March, and encountering difficulty finding a new one,
+I began spending most of my time building open source tools.
+I’m hoping to be able to pay for my kids’ health insurance this month,
+so if you value the work I am doing, I need your support.
Please consider sponsoring me or the project.

To join the community or get help 👇️ Join the Discord.

@@ -99,7 +99,7 @@

Another Way to Support Open

diff --git a/docs/file.IRP.html b/docs/file.IRP.html index cd701065..43718bd2 100644 --- a/docs/file.IRP.html +++ b/docs/file.IRP.html @@ -180,11 +180,13 @@

Retrospective & continuous improvement

-

After an incident, perform a brief post-incident review covering: -- What happened and why -- What was done to contain and remediate -- What tests or process changes will prevent recurrence -- Assign owners and deadlines for follow-up tasks

+

After an incident, perform a brief post-incident review covering:

+
    +
  • What happened and why
  • +
  • What was done to contain and remediate
  • +
  • What tests or process changes will prevent recurrence
  • +
  • Assign owners and deadlines for follow-up tasks
  • +

References

    @@ -192,18 +194,24 @@

    References

Appendix: Example checklist for an incident

-
    -
  • [ ] Acknowledge report to reporter (24-72 hours)
  • -
  • [ ] Reproduce and classify severity
  • -
  • [ ] Prepare and test a fix in a branch
  • -
  • [ ] Coordinate disclosure via Tidelift
  • -
  • [ ] Publish patch release and advisory
  • -
  • [ ] Postmortem and follow-up actions
  • +
      +
    • +Acknowledge report to reporter (24-72 hours)
    • +
    • +Reproduce and classify severity
    • +
    • +Prepare and test a fix in a branch
    • +
    • +Coordinate disclosure via Tidelift
    • +
    • +Publish patch release and advisory
    • +
    • +Postmortem and follow-up actions
diff --git a/docs/file.LICENSE.html b/docs/file.LICENSE.html index ac83a495..4e01e91e 100644 --- a/docs/file.LICENSE.html +++ b/docs/file.LICENSE.html @@ -60,7 +60,7 @@
MIT License

Copyright (c) 2017-2025 Peter H. Boling, of Galtzo.com, and oauth2 contributors
Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
diff --git a/docs/file.OIDC.html b/docs/file.OIDC.html index b5ebfaf2..5068218e 100644 --- a/docs/file.OIDC.html +++ b/docs/file.OIDC.html @@ -120,11 +120,11 @@

Raw OIDC with ruby-oauth/oauth2

What you must add in your app for OIDC

    -
  • ID Token validation: This gem surfaces id_token values but does not verify them. Your app should: -1) Parse the JWT (header, payload, signature) -2) Fetch the OP JSON Web Key Set (JWKS) from discovery (or configure statically) -3) Select the correct key by kid (when present) and verify the signature and algorithm -4) Validate standard claims (iss, aud, exp, iat, nbf, azp, nonce when used, at_hash/c_hash when applicable) +
  • ID Token validation: This gem surfaces id_token values but does not verify them. Your app should:
    +1) Parse the JWT (header, payload, signature)
    +2) Fetch the OP JSON Web Key Set (JWKS) from discovery (or configure statically)
    +3) Select the correct key by kid (when present) and verify the signature and algorithm
    +4) Validate standard claims (iss, aud, exp, iat, nbf, azp, nonce when used, at_hash/c_hash when applicable)
    5) Enforce expected client_id, issuer, and clock skew policies
  • Nonce handling for Authorization Code flow with OIDC: generate a cryptographically-random nonce, bind it to the user session before redirect, include it in authorize request, and verify it in the ID Token on return.
  • PKCE is best practice and often required by OPs: generate/verifier, send challenge in authorize, send verifier in token request.
  • @@ -133,78 +133,77 @@

    Raw OIDC with ruby-oauth/oauth2

    Minimal OIDC Authorization Code example

    -

    ```ruby -require “oauth2” -require “jwt” # jwt/ruby-jwt -require “net/http” -require “json”

    - -

    client = OAuth2::Client.new( - ENV.fetch(“OIDC_CLIENT_ID”), - ENV.fetch(“OIDC_CLIENT_SECRET”), - site: ENV.fetch(“OIDC_ISSUER”), # e.g. https://accounts.example.com - authorize_url: “/authorize”, # or discovered - token_url: “/token”, # or discovered -)

    - -

    Step 1: Redirect to OP for consent/auth

    -

    state = SecureRandom.hex(16) +

    require "oauth2"
    +require "jwt"         # jwt/ruby-jwt
    +require "net/http"
    +require "json"
    +
    +client = OAuth2::Client.new(
    +  ENV.fetch("OIDC_CLIENT_ID"),
    +  ENV.fetch("OIDC_CLIENT_SECRET"),
    +  site: ENV.fetch("OIDC_ISSUER"),              # e.g. https://accounts.example.com
    +  authorize_url: "/authorize",                 # or discovered
    +  token_url: "/token",                         # or discovered
    +)
    +
    +# Step 1: Redirect to OP for consent/auth
    +state = SecureRandom.hex(16)
     nonce = SecureRandom.hex(16)
     pkce_verifier = SecureRandom.urlsafe_base64(64)
    -pkce_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(pkce_verifier)).delete(“=”)

    +pkce_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(pkce_verifier)).delete("=") -

    authz_url = client.auth_code.authorize_url( - scope: “openid profile email”, +authz_url = client.auth_code.authorize_url( + scope: "openid profile email", state: state, nonce: nonce, code_challenge: pkce_challenge, - code_challenge_method: “S256”, - redirect_uri: ENV.fetch(“OIDC_REDIRECT_URI”), + code_challenge_method: "S256", + redirect_uri: ENV.fetch("OIDC_REDIRECT_URI"), ) -# redirect_to authz_url

    +# redirect_to authz_url -

    Step 2: Handle callback

    -

    # params[:code], params[:state] -raise “state mismatch” unless params[:state] == state

    +# Step 2: Handle callback +# params[:code], params[:state] +raise "state mismatch" unless params[:state] == state -

    token = client.auth_code.get_token( +token = client.auth_code.get_token( params[:code], - redirect_uri: ENV.fetch(“OIDC_REDIRECT_URI”), + redirect_uri: ENV.fetch("OIDC_REDIRECT_URI"), code_verifier: pkce_verifier, -)

    +) -

    The token may include: access_token, id_token, refresh_token, etc.

    -

    id_token = token.params[“id_token”] || token.params[:id_token]

    +# The token may include: access_token, id_token, refresh_token, etc. +id_token = token.params["id_token"] || token.params[:id_token] -

    Step 3: Validate the ID Token (simplified – add your own checks!)

    -

    # Discover keys (example using .well-known) -issuer = ENV.fetch(“OIDC_ISSUER”) -jwks_uri = JSON.parse(Net::HTTP.get(URI.join(issuer, “/.well-known/openid-configuration”))). - fetch(“jwks_uri”) +# Step 3: Validate the ID Token (simplified – add your own checks!) +# Discover keys (example using .well-known) +issuer = ENV.fetch("OIDC_ISSUER") +jwks_uri = JSON.parse(Net::HTTP.get(URI.join(issuer, "/.well-known/openid-configuration"))). + fetch("jwks_uri") jwks = JSON.parse(Net::HTTP.get(URI(jwks_uri))) -keys = jwks.fetch(“keys”)

    +keys = jwks.fetch("keys") -

    Use ruby-jwt JWK loader

    -

    jwk_set = JWT::JWK::Set.new(keys.map { |k| JWT::JWK.import(k) })

    +# Use ruby-jwt JWK loader +jwk_set = JWT::JWK::Set.new(keys.map { |k| JWT::JWK.import(k) }) -

    decoded, headers = JWT.decode( +decoded, headers = JWT.decode( id_token, nil, true, - algorithms: [“RS256”, “ES256”, “PS256”], + algorithms: ["RS256", "ES256", "PS256"], jwks: jwk_set, verify_iss: true, iss: issuer, verify_aud: true, - aud: ENV.fetch(“OIDC_CLIENT_ID”), -)

    + aud: ENV.fetch("OIDC_CLIENT_ID"), +) -

    Verify nonce

    -

    raise “nonce mismatch” unless decoded[“nonce”] == nonce

    +# Verify nonce +raise "nonce mismatch" unless decoded["nonce"] == nonce -

    Optionally: call UserInfo

    -

    userinfo = token.get(“/userinfo”).parsed -```

    +# Optionally: call UserInfo +userinfo = token.get("/userinfo").parsed +

    Notes on discovery and registration

    @@ -257,7 +256,7 @@

    Optionally: call UserInfo

diff --git a/docs/file.README.html b/docs/file.README.html index 40081e21..a06abfa9 100644 --- a/docs/file.README.html +++ b/docs/file.README.html @@ -99,7 +99,7 @@

Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0 ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5 oauth2 Logo by Chris Messina, CC BY-SA 3.0

-

🔐 OAuth 2.0 Authorization Framework

+

🔐 OAuth 2.0 Authorization Framework

⭐️ including OAuth 2.1 draft spec & OpenID Connect (OIDC)

@@ -113,11 +113,11 @@

🔐 OAuth 2.0 Authorization Framework

OpenCollective Backers OpenCollective Sponsors Sponsor Me on Github Liberapay Goal Progress Donate on PayPal Buy me a coffee Donate on Polar Donate at ko-fi.com

-

🌻 Synopsis

+

🌻 Synopsis

-

OAuth 2.0 is the industry-standard protocol for authorization. -OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, - desktop applications, mobile phones, and living room devices. +

OAuth 2.0 is the industry-standard protocol for authorization.
+OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications,
+ desktop applications, mobile phones, and living room devices.
This is a RubyGem for implementing OAuth 2.0 clients (not servers) in Ruby applications.

Quick Examples

@@ -125,29 +125,27 @@

Quick Examples

Convert the following `curl` command into a token request using this gem... -

shell -curl --request POST \ - --url 'https://login.microsoftonline.com/REDMOND_REDACTED/oauth2/token' \ - --header 'content-type: application/x-www-form-urlencoded' \ +

curl --request POST \
+  --url 'https://login.microsoftonline.com/REDMOND_REDACTED/oauth2/token' \
+  --header 'content-type: application/x-www-form-urlencoded' \
   --data grant_type=client_credentials \
   --data client_id=REDMOND_CLIENT_ID \
   --data client_secret=REDMOND_CLIENT_SECRET \
   --data resource=REDMOND_RESOURCE_UUID
-

+

NOTE: In the ruby version below, certain params are passed to the get_token call, instead of the client creation.

-

ruby -OAuth2::Client.new( - "REDMOND_CLIENT_ID", # client_id - "REDMOND_CLIENT_SECRET", # client_secret +

OAuth2::Client.new(
+  "REDMOND_CLIENT_ID", # client_id
+  "REDMOND_CLIENT_SECRET", # client_secret
   auth_scheme: :request_body, # Other modes are supported: :basic_auth, :tls_client_auth, :private_key_jwt
-  token_url: "oauth2/token", # relative path, except with leading `/`, then absolute path
-  site: "https://login.microsoftonline.com/REDMOND_REDACTED",
+  token_url: "oauth2/token", # relative path, except with leading `/`, then absolute path
+  site: "https://login.microsoftonline.com/REDMOND_REDACTED",
 ). # The base path for token_url when it is relative
   client_credentials. # There are many other types to choose from!
-  get_token(resource: "REDMOND_RESOURCE_UUID")
-

+ get_token(resource: "REDMOND_RESOURCE_UUID") +

NOTE: header - The content type specified in the curl is already the default!

@@ -161,29 +159,26 @@

Quick Examples

  • E2E example does not ship with the released gem, so clone the source to play with it.
  • -

    console -docker compose -f docker-compose-ssl.yml up -d --wait +

    docker compose -f docker-compose-ssl.yml up -d --wait
     ruby examples/e2e.rb
     # If your machine is slow or Docker pulls are cold, increase the wait:
     E2E_WAIT_TIMEOUT=120 ruby examples/e2e.rb
     # The mock server serves HTTP on 8080; the example points to http://localhost:8080 by default.
    -

    +

    The output should be something like this:

    -

    console -➜ ruby examples/e2e.rb +

    ➜  ruby examples/e2e.rb
     Access token (truncated): eyJraWQiOiJkZWZhdWx0...
     userinfo status: 200
    -userinfo body: {"sub" => "demo-sub", "aud" => ["demo-aud"], "nbf" => 1757816758000, "iss" => "http://localhost:8080/default", "exp" => 1757820358000, "iat" => 1757816758000, "jti" => "d63b97a7-ebe5-4dea-93e6-d542caba6104"}
    +userinfo body: {"sub" => "demo-sub", "aud" => ["demo-aud"], "nbf" => 1757816758000, "iss" => "http://localhost:8080/default", "exp" => 1757820358000, "iat" => 1757816758000, "jti" => "d63b97a7-ebe5-4dea-93e6-d542caba6104"}
     E2E complete
    -

    +

    Make sure to shut down the mock server when you are done:

    -

    console -docker compose -f docker-compose-ssl.yml down -

    +
    docker compose -f docker-compose-ssl.yml down
    +

    Troubleshooting: validate connectivity to the mock server

    @@ -253,7 +248,7 @@

    Quick Examples

    oauth sibling gem for OAuth 1.0a implementations in Ruby. -

    💡 Info you can shake a stick at

    +

    💡 Info you can shake a stick at

    @@ -417,7 +412,7 @@

    Federated DVCS

    -

    Enterprise Support Tidelift +

    Enterprise Support Tidelift

    Available as part of the Tidelift Subscription.

    @@ -446,21 +441,19 @@

    ✨ Installation

    +

    ✨ Installation

    Install the gem and add to the application’s Gemfile by executing:

    -

    console -bundle add oauth2 -

    +
    bundle add oauth2
    +

    If bundler is not being used to manage dependencies, install the gem by executing:

    -

    console -gem install oauth2 -

    +
    gem install oauth2
    +
    -

    🔒 Secure Installation

    +

    🔒 Secure Installation

    For Medium or High Security Installations @@ -471,15 +464,13 @@

    🔒 Secure Installation

    Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:

    -

    console -gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem) -

    +
    gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
    +

    You only need to do that once. Then proceed to install with:

    -

    console -gem install oauth2 -P MediumSecurity -

    +
    gem install oauth2 -P MediumSecurity
    +

    The MediumSecurity trust profile will verify signed gems, but allow the installation of unsigned dependencies.

    @@ -487,9 +478,8 @@

    🔒 Secure Installation

    If you want to up your security game full-time:

    -

    console -bundle config set --global trust-policy MediumSecurity -

    +
    bundle config set --global trust-policy MediumSecurity
    +

    MediumSecurity instead of HighSecurity is necessary if not all the gems you use are signed.

    @@ -545,9 +535,9 @@

    What is new for v2.0?

    Compatibility

    -

    Targeted ruby compatibility is non-EOL versions of Ruby, currently 3.2, 3.3, and 3.4. -Compatibility is further distinguished as “Best Effort Support” or “Incidental Support” for older versions of Ruby. -This gem will install on Ruby versions >= v2.2 for 2.x releases. +

    Targeted ruby compatibility is non-EOL versions of Ruby, currently 3.2, 3.3, and 3.4.
    +Compatibility is further distinguished as “Best Effort Support” or “Incidental Support” for older versions of Ruby.
    +This gem will install on Ruby versions >= v2.2 for 2.x releases.
    See 1-4-stable branch for older rubies.

    @@ -618,98 +608,93 @@

    Compatibility

    -

    NOTE: The 1.4 series will only receive critical security updates. +

    NOTE: The 1.4 series will only receive critical security updates.
    See SECURITY.md and IRP.md.

    -

    ⚙️ Configuration

    +

    ⚙️ Configuration

    You can turn on additional warnings.

    -

    ruby -OAuth2.configure do |config| +

    OAuth2.configure do |config|
       # Turn on a warning like:
    -  #   OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key
    +  #   OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key
       config.silence_extra_tokens_warning = false # default: true
       # Set to true if you want to also show warnings about no tokens
       config.silence_no_tokens_warning = false # default: true,
     end
    -

    +
    -

    The “extra tokens” problem comes from ambiguity in the spec about which token is the right token. -Some OAuth 2.0 standards legitimately have multiple tokens. -You may need to subclass OAuth2::AccessToken, or write your own custom alternative to it, and pass it in. +

    The “extra tokens” problem comes from ambiguity in the spec about which token is the right token.
    +Some OAuth 2.0 standards legitimately have multiple tokens.
    +You may need to subclass OAuth2::AccessToken, or write your own custom alternative to it, and pass it in.
    Specify your custom class with the access_token_class option.

    -

    If you only need one token, you can, as of v2.0.10, -specify the exact token name you want to extract via the OAuth2::AccessToken using +

    If you only need one token, you can, as of v2.0.10,
    +specify the exact token name you want to extract via the OAuth2::AccessToken using
    the token_name option.

    -

    You’ll likely need to do some source diving. -This gem has 100% test coverage for lines and branches, so the specs are a great place to look for ideas. +

    You’ll likely need to do some source diving.
    +This gem has 100% test coverage for lines and branches, so the specs are a great place to look for ideas.
    If you have time and energy, please contribute to the documentation!

    -

    🔧 Basic Usage

    +

    🔧 Basic Usage

    -

    +

    authorize_url and token_url are on site root (Just Works!)

    -

    ```ruby -require “oauth2” -client = OAuth2::Client.new(“client_id”, “client_secret”, site: “https://example.org”) -# => #<OAuth2::Client:0x00000001204c8288 @id=”client_id”, @secret=”client_sec… -client.auth_code.authorize_url(redirect_uri: “http://localhost:8080/oauth2/callback”) -# => “https://example.org/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code”

    +
    require "oauth2"
    +client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org")
    +# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
    +client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
    +# => "https://example.org/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
     
    -

    access = client.auth_code.get_token(“authorization_code_value”, redirect_uri: “http://localhost:8080/oauth2/callback”, headers: {”Authorization” => “Basic some_password”}) -response = access.get(“/api/resource”, params: {”query_foo” => “bar”}) +access = client.auth_code.get_token("authorization_code_value", redirect_uri: "http://localhost:8080/oauth2/callback", headers: {"Authorization" => "Basic some_password"}) +response = access.get("/api/resource", params: {"query_foo" => "bar"}) response.class.name # => OAuth2::Response -```

    +
    -

    Relative authorize_url and token_url (Not on site root, Just Works!)

    +

    Relative authorize_url and token_url (Not on site root, Just Works!)

    In the above example, the default Authorization URL is oauth/authorize and default Access Token URL is oauth/token, and, as they are missing a leading /, both are relative.

    -

    ruby -client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/nested/directory/on/your/server") -# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec... -client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback") -# => "https://example.org/nested/directory/on/your/server/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code" -

    +
    client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/nested/directory/on/your/server")
    +# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
    +client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
    +# => "https://example.org/nested/directory/on/your/server/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
    +
    -

    Customize authorize_url and token_url +

    Customize authorize_url and token_url

    You can specify custom URLs for authorization and access token, and when using a leading / they will not be relative, as shown below:

    -

    ruby -client = OAuth2::Client.new( - "client_id", - "client_secret", - site: "https://example.org/nested/directory/on/your/server", - authorize_url: "/jaunty/authorize/", - token_url: "/stirrups/access_token", +

    client = OAuth2::Client.new(
    +  "client_id",
    +  "client_secret",
    +  site: "https://example.org/nested/directory/on/your/server",
    +  authorize_url: "/jaunty/authorize/",
    +  token_url: "/stirrups/access_token",
     )
    -# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
    -client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
    -# => "https://example.org/jaunty/authorize/?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
    +# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
    +client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
    +# => "https://example.org/jaunty/authorize/?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
     client.class.name
     # => OAuth2::Client
    -

    +
    -

    snake_case and indifferent access in Response#parsed

    +

    snake_case and indifferent access in Response#parsed

    -

    ruby -response = access.get("/api/resource", params: {"query_foo" => "bar"}) +

    response = access.get("/api/resource", params: {"query_foo" => "bar"})
     # Even if the actual response is CamelCase. it will be made available as snaky:
    -JSON.parse(response.body)         # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
    -response.parsed                   # => {"access_token"=>"aaaaaaaa", "additional_data"=>"additional"}
    -response.parsed.access_token      # => "aaaaaaaa"
    -response.parsed[:access_token]    # => "aaaaaaaa"
    -response.parsed.additional_data   # => "additional"
    -response.parsed[:additional_data] # => "additional"
    +JSON.parse(response.body)         # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
    +response.parsed                   # => {"access_token"=>"aaaaaaaa", "additional_data"=>"additional"}
    +response.parsed.access_token      # => "aaaaaaaa"
    +response.parsed[:access_token]    # => "aaaaaaaa"
    +response.parsed.additional_data   # => "additional"
    +response.parsed[:additional_data] # => "additional"
     response.parsed.class.name        # => SnakyHash::StringKeyed (from snaky_hash gem)
    -

    +

    Serialization

    @@ -721,83 +706,80 @@
    Global Serialization Config

    Globally configure SnakyHash::StringKeyed to use the serializer. Put this in your code somewhere reasonable (like an initializer for Rails).

    -

    ruby -SnakyHash::StringKeyed.class_eval do +

    SnakyHash::StringKeyed.class_eval do
       extend SnakyHash::Serializer
     end
    -

    +
    Discrete Serialization Config

    Discretely configure a custom Snaky Hash class to use the serializer.

    -

    ```ruby -class MySnakyHash < SnakyHash::StringKeyed - # Give this hash class dump and load abilities! +

    class MySnakyHash < SnakyHash::StringKeyed
    +  # Give this hash class `dump` and `load` abilities!
       extend SnakyHash::Serializer
    -end

    +end -

    And tell your client to use the custom class in each call:

    -

    client = OAuth2::Client.new(“client_id”, “client_secret”, site: “https://example.org/oauth2”) +# And tell your client to use the custom class in each call: +client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/oauth2") token = client.get_token({snaky_hash_klass: MySnakyHash}) -```

    +
    Serialization Extensions

    These extensions work regardless of whether you used the global or discrete config above.

    -

    There are a few hacks you may need in your class to support Ruby < 2.4.2 or < 2.6. -They are likely not needed if you are on a newer Ruby. -Expand the examples below, or the ruby-oauth/snaky_hash gem, +

    There are a few hacks you may need in your class to support Ruby < 2.4.2 or < 2.6.
    +They are likely not needed if you are on a newer Ruby.
    +Expand the examples below, or the ruby-oauth/snaky_hash gem,
    or response_spec.rb, for more ideas, especially if you need to study the hacks for older Rubies.

    See Examples -

    ```ruby -class MySnakyHash < SnakyHash::StringKeyed - # Give this hash class dump and load abilities! - extend SnakyHash::Serializer

    +
    class MySnakyHash < SnakyHash::StringKeyed
    +  # Give this hash class `dump` and `load` abilities!
    +  extend SnakyHash::Serializer
     
    -  

    #### Serialization Extentions + #### Serialization Extentions # # Act on the non-hash values (including the values of hashes) as they are dumped to JSON # In other words, this retains nested hashes, and only the deepest leaf nodes become bananas. # WARNING: This is a silly example! dump_value_extensions.add(:to_fruit) do |value| - “banana” # => Make values “banana” on dump - end

    + "banana" # => Make values "banana" on dump + end -

    # Act on the non-hash values (including the values of hashes) as they are loaded from the JSON dump - # In other words, this retains nested hashes, and only the deepest leaf nodes become . + # Act on the non-hash values (including the values of hashes) as they are loaded from the JSON dump + # In other words, this retains nested hashes, and only the deepest leaf nodes become ***. # WARNING: This is a silly example! load_value_extensions.add(:to_stars) do |value| - “” # Turn dumped bananas into *** when they are loaded - end

    + "***" # Turn dumped bananas into *** when they are loaded + end -

    # Act on the entire hash as it is prepared for dumping to JSON + # Act on the entire hash as it is prepared for dumping to JSON # WARNING: This is a silly example! dump_hash_extensions.add(:to_cheese) do |value| if value.is_a?(Hash) value.transform_keys do |key| - split = key.split(“_”) + split = key.split("_") first_word = split[0] - key.sub(first_word, “cheese”) + key.sub(first_word, "cheese") end else value end - end

    + end -

    # Act on the entire hash as it is loaded from the JSON dump + # Act on the entire hash as it is loaded from the JSON dump # WARNING: This is a silly example! load_hash_extensions.add(:to_pizza) do |value| if value.is_a?(Hash) res = klass.new value.keys.each_with_object(res) do |key, result| - split = key.split(“_”) + split = key.split("_") last_word = split[-1] - new_key = key.sub(last_word, “pizza”) + new_key = key.sub(last_word, "pizza") result[new_key] = value[key] end res @@ -806,64 +788,61 @@

    Serialization Extensions
    end end end -```

    +
    -

    Prefer camelCase over snake_case? => snaky: false

    +

    Prefer camelCase over snake_case? => snaky: false

    -

    ruby -response = access.get("/api/resource", params: {"query_foo" => "bar"}, snaky: false) -JSON.parse(response.body) # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"} -response.parsed # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"} -response.parsed["accessToken"] # => "aaaaaaaa" -response.parsed["additionalData"] # => "additional" +

    response = access.get("/api/resource", params: {"query_foo" => "bar"}, snaky: false)
    +JSON.parse(response.body)         # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
    +response.parsed                   # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
    +response.parsed["accessToken"]    # => "aaaaaaaa"
    +response.parsed["additionalData"] # => "additional"
     response.parsed.class.name        # => Hash (just, regular old Hash)
    -

    +
    Debugging & Logging

    Set an environment variable as per usual (e.g. with dotenv).

    -

    ruby -# will log both request and response, including bodies -ENV["OAUTH_DEBUG"] = "true" -

    +
    # will log both request and response, including bodies
    +ENV["OAUTH_DEBUG"] = "true"
    +

    By default, debug output will go to $stdout. This can be overridden when initializing your OAuth2::Client.

    -

    ruby -require "oauth2" +

    require "oauth2"
     client = OAuth2::Client.new(
    -  "client_id",
    -  "client_secret",
    -  site: "https://example.org",
    -  logger: Logger.new("example.log", "weekly"),
    +  "client_id",
    +  "client_secret",
    +  site: "https://example.org",
    +  logger: Logger.new("example.log", "weekly"),
     )
    -

    +

    OAuth2::Response

    -

    The AccessToken methods #get, #post, #put and #delete and the generic #request +

    The AccessToken methods #get, #post, #put and #delete and the generic #request
    will return an instance of the #OAuth2::Response class.

    -

    This instance contains a #parsed method that will parse the response body and -return a Hash-like SnakyHash::StringKeyed if the Content-Type is application/x-www-form-urlencoded or if -the body is a JSON object. It will return an Array if the body is a JSON +

    This instance contains a #parsed method that will parse the response body and
    +return a Hash-like SnakyHash::StringKeyed if the Content-Type is application/x-www-form-urlencoded or if
    +the body is a JSON object. It will return an Array if the body is a JSON
    array. Otherwise, it will return the original body string.

    -

    The original response body, headers, and status can be accessed via their +

    The original response body, headers, and status can be accessed via their
    respective methods.

    OAuth2::AccessToken

    -

    If you have an existing Access Token for a user, you can initialize an instance -using various class methods including the standard new, from_hash (if you have -a hash of the values), or from_kvform (if you have an +

    If you have an existing Access Token for a user, you can initialize an instance
    +using various class methods including the standard new, from_hash (if you have
    +a hash of the values), or from_kvform (if you have an
    application/x-www-form-urlencoded encoded string of the values).

    Options (since v2.0.x unless noted):

    @@ -928,14 +907,14 @@

    OAuth2::AccessToken

    OAuth2::Error

    -

    On 400+ status code responses, an OAuth2::Error will be raised. If it is a -standard OAuth2 error response, the body will be parsed and #code and #description will contain the values provided from the error and -error_description parameters. The #response property of OAuth2::Error will +

    On 400+ status code responses, an OAuth2::Error will be raised. If it is a
    +standard OAuth2 error response, the body will be parsed and #code and #description will contain the values provided from the error and
    +error_description parameters. The #response property of OAuth2::Error will
    always contain the OAuth2::Response instance.

    -

    If you do not want an error to be raised, you may use :raise_errors => false -option on initialization of the client. In this case the OAuth2::Response -instance will be returned as usual and on 400+ status code responses, the +

    If you do not want an error to be raised, you may use :raise_errors => false
    +option on initialization of the client. In this case the OAuth2::Response
    +instance will be returned as usual and on 400+ status code responses, the
    Response instance will contain the OAuth2::Error instance.

    Authorization Grants

    @@ -962,55 +941,53 @@

    Authorization Grants

  • Differences overview: https://fusionauth.io/learn/expert-advice/oauth/differences-between-oauth-2-oauth-2-1/
  • -

    Currently, the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion -authentication grant types have helper strategy classes that simplify client -use. They are available via the #auth_code, -#implicit, -#password, -#client_credentials, and +

    Currently, the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion
    +authentication grant types have helper strategy classes that simplify client
    +use. They are available via the #auth_code,
    +#implicit,
    +#password,
    +#client_credentials, and
    #assertion methods respectively.

    These aren’t full examples, but demonstrative of the differences between usage for each strategy.

    -

    ```ruby -auth_url = client.auth_code.authorize_url(redirect_uri: “http://localhost:8080/oauth/callback”) -access = client.auth_code.get_token(“code_value”, redirect_uri: “http://localhost:8080/oauth/callback”)

    +
    auth_url = client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth/callback")
    +access = client.auth_code.get_token("code_value", redirect_uri: "http://localhost:8080/oauth/callback")
     
    -

    auth_url = client.implicit.authorize_url(redirect_uri: “http://localhost:8080/oauth/callback”) +auth_url = client.implicit.authorize_url(redirect_uri: "http://localhost:8080/oauth/callback") # get the token params in the callback and -access = OAuth2::AccessToken.from_kvform(client, query_string)

    +access = OAuth2::AccessToken.from_kvform(client, query_string) -

    access = client.password.get_token(“username”, “password”)

    +access = client.password.get_token("username", "password") -

    access = client.client_credentials.get_token

    +access = client.client_credentials.get_token -

    Client Assertion Strategy

    -

    # see: https://tools.ietf.org/html/rfc7523 +# Client Assertion Strategy +# see: https://tools.ietf.org/html/rfc7523 claimset = { - iss: “http://localhost:3001”, - aud: “http://localhost:8080/oauth2/token”, - sub: “me@example.com”, + iss: "http://localhost:3001", + aud: "http://localhost:8080/oauth2/token", + sub: "me@example.com", exp: Time.now.utc.to_i + 3600, } -assertion_params = [claimset, “HS256”, “secret_key”] -access = client.assertion.get_token(assertion_params)

    +assertion_params = [claimset, "HS256", "secret_key"] +access = client.assertion.get_token(assertion_params) -

    The access (i.e. access token) is then used like so:

    -

    access.token # actual access_token string, if you need it somewhere -access.get(“/api/stuff”) # making api calls with access token -```

    +# The `access` (i.e. access token) is then used like so: +access.token # actual access_token string, if you need it somewhere +access.get("/api/stuff") # making api calls with access token +
    -

    If you want to specify additional headers to be sent out with the +

    If you want to specify additional headers to be sent out with the
    request, add a ‘headers’ hash under ‘params’:

    -

    ruby -access = client.auth_code.get_token("code_value", redirect_uri: "http://localhost:8080/oauth/callback", headers: {"Some" => "Header"}) -

    +
    access = client.auth_code.get_token("code_value", redirect_uri: "http://localhost:8080/oauth/callback", headers: {"Some" => "Header"})
    +
    -

    You can always use the #request method on the OAuth2::Client instance to make +

    You can always use the #request method on the OAuth2::Client instance to make
    requests for tokens for any Authentication grant type.

    -

    📘 Comprehensive Usage

    +

    📘 Comprehensive Usage

    Common Flows (end-to-end)

    @@ -1018,88 +995,84 @@

    Common Flows (end-to-end)

  • Authorization Code (server-side web app):
  • -

    ```ruby -require “oauth2” +

    require "oauth2"
     client = OAuth2::Client.new(
    -  ENV[“CLIENT_ID”],
    -  ENV[“CLIENT_SECRET”],
    -  site: “https://provider.example.com”,
    -  redirect_uri: “https://my.app.example.com/oauth/callback”,
    -)

    - -

    Step 1: redirect user to consent

    -

    state = SecureRandom.hex(16) -auth_url = client.auth_code.authorize_url(scope: “openid profile email”, state: state) -# redirect_to auth_url

    - -

    Step 2: handle the callback

    -

    # params[:code], params[:state] -raise “state mismatch” unless params[:state] == state -access = client.auth_code.get_token(params[:code])

    - -

    Step 3: call APIs

    -

    profile = access.get(“/api/v1/me”).parsed -```

    + ENV["CLIENT_ID"], + ENV["CLIENT_SECRET"], + site: "https://provider.example.com", + redirect_uri: "https://my.app.example.com/oauth/callback", +) + +# Step 1: redirect user to consent +state = SecureRandom.hex(16) +auth_url = client.auth_code.authorize_url(scope: "openid profile email", state: state) +# redirect_to auth_url + +# Step 2: handle the callback +# params[:code], params[:state] +raise "state mismatch" unless params[:state] == state +access = client.auth_code.get_token(params[:code]) + +# Step 3: call APIs +profile = access.get("/api/v1/me").parsed +
    • Client Credentials (machine-to-machine):
    -

    ruby -client = OAuth2::Client.new(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"], site: "https://provider.example.com") -access = client.client_credentials.get_token(audience: "https://api.example.com") -resp = access.get("/v1/things") -

    +
    client = OAuth2::Client.new(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"], site: "https://provider.example.com")
    +access = client.client_credentials.get_token(audience: "https://api.example.com")
    +resp = access.get("/v1/things")
    +
    • Resource Owner Password (legacy; avoid when possible):
    -

    ruby -access = client.password.get_token("jdoe", "s3cret", scope: "read") -

    +
    access = client.password.get_token("jdoe", "s3cret", scope: "read")
    +

    Examples

    JHipster UAA (Spring Cloud) password grant example (legacy; avoid when possible) -

    ```ruby -# This converts a Postman/Net::HTTP multipart token request to oauth2 gem usage. +

    # This converts a Postman/Net::HTTP multipart token request to oauth2 gem usage.
     # JHipster UAA typically exposes the token endpoint at /uaa/oauth/token.
     # The original snippet included:
     # - Basic Authorization header for the client (web_app:changeit)
     # - X-XSRF-TOKEN header from a cookie (some deployments require it)
     # - grant_type=password with username/password and client_id
    -# Using oauth2 gem, you don’t need to build multipart bodies; the gem sends
    -# application/x-www-form-urlencoded as required by RFC 6749.

    +# Using oauth2 gem, you don't need to build multipart bodies; the gem sends +# application/x-www-form-urlencoded as required by RFC 6749. -

    require “oauth2”

    +require "oauth2" -

    client = OAuth2::Client.new( - “web_app”, # client_id - “changeit”, # client_secret - site: “http://localhost:8080/uaa”, - token_url: “/oauth/token”, # absolute under site (or “oauth/token” relative) +client = OAuth2::Client.new( + "web_app", # client_id + "changeit", # client_secret + site: "http://localhost:8080/uaa", + token_url: "/oauth/token", # absolute under site (or "oauth/token" relative) auth_scheme: :basic_auth, # sends HTTP Basic Authorization header -)

    +) -

    If your UAA requires an XSRF header for the token call, provide it as a header.

    -

    # Often this is not required for token endpoints, but if your gateway enforces it, +# If your UAA requires an XSRF header for the token call, provide it as a header. +# Often this is not required for token endpoints, but if your gateway enforces it, # obtain the value from the XSRF-TOKEN cookie and pass it here. -xsrf_token = ENV[“X_XSRF_TOKEN”] # e.g., pulled from a prior set-cookie value

    +xsrf_token = ENV["X_XSRF_TOKEN"] # e.g., pulled from a prior set-cookie value -

    access = client.password.get_token( - “admin”, # username - “admin”, # password - headers: xsrf_token ? {”X-XSRF-TOKEN” => xsrf_token} : {}, +access = client.password.get_token( + "admin", # username + "admin", # password + headers: xsrf_token ? {"X-XSRF-TOKEN" => xsrf_token} : {}, # JHipster commonly also accepts/needs the client_id in the body; include if required: - # client_id: “web_app”, -)

    + # client_id: "web_app", +) -

    puts access.token +puts access.token puts access.to_hash # full token response -```

    +

    Notes:

    @@ -1124,61 +1097,60 @@

    Instagram API (verb‑dependent

    Example: exchanging and refreshing long‑lived Instagram tokens, and making API calls

    -

    ```ruby -require “oauth2”

    +
    require "oauth2"
     
    -

    NOTE: Users authenticate via Facebook Login to obtain a short‑lived user token (not shown here).

    -

    # See Facebook Login docs for obtaining the initial short‑lived token.

    +# NOTE: Users authenticate via Facebook Login to obtain a short‑lived user token (not shown here). +# See Facebook Login docs for obtaining the initial short‑lived token. -

    client = OAuth2::Client.new(nil, nil, site: “https://graph.instagram.com”)

    +client = OAuth2::Client.new(nil, nil, site: "https://graph.instagram.com") -

    Start with a short‑lived token you already obtained via Facebook Login

    -

    short_lived = OAuth2::AccessToken.new( +# Start with a short‑lived token you already obtained via Facebook Login +short_lived = OAuth2::AccessToken.new( client, - ENV[“IG_SHORT_LIVED_TOKEN”], + ENV["IG_SHORT_LIVED_TOKEN"], # Key part: verb‑dependent mode mode: {get: :query, post: :header, delete: :header}, -)

    +) -

    1) Exchange for a long‑lived token (Instagram requires GET with access_token in query)

    -

    # Endpoint: GET https://graph.instagram.com/access_token +# 1) Exchange for a long‑lived token (Instagram requires GET with access_token in query) +# Endpoint: GET https://graph.instagram.com/access_token # Params: grant_type=ig_exchange_token, client_secret=APP_SECRET exchange = short_lived.get( - “/access_token”, + "/access_token", params: { - grant_type: “ig_exchange_token”, - client_secret: ENV[“IG_APP_SECRET”], + grant_type: "ig_exchange_token", + client_secret: ENV["IG_APP_SECRET"], # access_token param will be added automatically by the AccessToken (mode => :query for GET) }, ) -long_lived_token_value = exchange.parsed[“access_token”]

    +long_lived_token_value = exchange.parsed["access_token"] -

    long_lived = OAuth2::AccessToken.new( +long_lived = OAuth2::AccessToken.new( client, long_lived_token_value, mode: {get: :query, post: :header, delete: :header}, -)

    +) -

    2) Refresh the long‑lived token (Instagram uses GET with token in query)

    -

    # Endpoint: GET https://graph.instagram.com/refresh_access_token +# 2) Refresh the long‑lived token (Instagram uses GET with token in query) +# Endpoint: GET https://graph.instagram.com/refresh_access_token refresh_resp = long_lived.get( - “/refresh_access_token”, - params: {grant_type: “ig_refresh_token”}, + "/refresh_access_token", + params: {grant_type: "ig_refresh_token"}, ) long_lived = OAuth2::AccessToken.new( client, - refresh_resp.parsed[“access_token”], + refresh_resp.parsed["access_token"], mode: {get: :query, post: :header, delete: :header}, -)

    +) -

    3) Typical API GET request (token in query automatically)

    -

    me = long_lived.get(“/me”, params: {fields: “id,username”}).parsed

    +# 3) Typical API GET request (token in query automatically) +me = long_lived.get("/me", params: {fields: "id,username"}).parsed -

    4) Example POST (token sent via Bearer header automatically)

    -

    # Note: Replace the path/params with a real Instagram Graph API POST you need, +# 4) Example POST (token sent via Bearer header automatically) +# Note: Replace the path/params with a real Instagram Graph API POST you need, # such as publishing media via the Graph API endpoints. -# long_lived.post(“/me/media”, body: {image_url: “https://…”, caption: “hello”}) -```

    +# long_lived.post("/me/media", body: {image_url: "https://...", caption: "hello"}) +

    Tips:

    @@ -1195,24 +1167,22 @@

    Refresh Tokens

  • Manual refresh:
  • -

    ruby -if access.expired? +

    if access.expired?
       access = access.refresh
     end
    -

    +
    • Auto-refresh wrapper pattern:
    -

    ```ruby -class AutoRefreshingToken +

    class AutoRefreshingToken
       def initialize(token_provider, store: nil)
         @token = token_provider
         @store = store # e.g., something that responds to read/write for token data
    -  end

    + end -

    def with(&blk) + def with(&blk) tok = ensure_fresh! blk ? blk.call(tok) : tok rescue OAuth2::Error => e @@ -1223,23 +1193,23 @@

    Refresh Tokens

    retry end raise - end

    + end -

    private

    +private -

    def ensure_fresh! + def ensure_fresh! if @token.expired? && @token.refresh_token @token = @token.refresh @store.write(@token.to_hash) if @store end @token end -end

    +end -

    usage

    -

    keeper = AutoRefreshingToken.new(access) -keeper.with { |tok| tok.get(“/v1/protected”) } -```

    +# usage +keeper = AutoRefreshingToken.new(access) +keeper.with { |tok| tok.get("/v1/protected") } +

    Persist the token across processes using AccessToken#to_hash and AccessToken.from_hash(client, hash).

    @@ -1247,13 +1217,12 @@

    Token Revocation (RFC 7009)

    You can revoke either the access token or the refresh token.

    -

    ```ruby -# Revoke the current access token -access.revoke(token_type_hint: :access_token)

    +
    # Revoke the current access token
    +access.revoke(token_type_hint: :access_token)
     
    -

    Or explicitly revoke the refresh token (often also invalidates associated access tokens)

    -

    access.revoke(token_type_hint: :refresh_token) -```

    +# Or explicitly revoke the refresh token (often also invalidates associated access tokens) +access.revoke(token_type_hint: :refresh_token) +

    Client Configuration Tips

    @@ -1263,35 +1232,34 @@

    Mutual TLS (mTLS) client authenti

    Example using PEM files (certificate and key):

    -

    ```ruby -require “oauth2” -require “openssl”

    +
    require "oauth2"
    +require "openssl"
     
    -

    client = OAuth2::Client.new( - ENV.fetch(“CLIENT_ID”), - ENV.fetch(“CLIENT_SECRET”), - site: “https://example.com”, - authorize_url: “/oauth/authorize/”, - token_url: “/oauth/token/”, +client = OAuth2::Client.new( + ENV.fetch("CLIENT_ID"), + ENV.fetch("CLIENT_SECRET"), + site: "https://example.com", + authorize_url: "/oauth/authorize/", + token_url: "/oauth/token/", auth_scheme: :tls_client_auth, # if your AS requires mTLS-based client authentication connection_opts: { ssl: { - client_cert: OpenSSL::X509::Certificate.new(File.read(“localhost.pem”)), - client_key: OpenSSL::PKey::RSA.new(File.read(“localhost-key.pem”)), + client_cert: OpenSSL::X509::Certificate.new(File.read("localhost.pem")), + client_key: OpenSSL::PKey::RSA.new(File.read("localhost-key.pem")), # Optional extras, uncomment as needed: - # ca_file: “/path/to/ca-bundle.pem”, # custom CA(s) + # ca_file: "/path/to/ca-bundle.pem", # custom CA(s) # verify: true # enable server cert verification (recommended) }, }, -)

    +) -

    Example token request (any grant type can be used). The mTLS handshake

    -

    # will occur automatically on HTTPS calls using the configured cert/key. -access = client.client_credentials.get_token

    +# Example token request (any grant type can be used). The mTLS handshake +# will occur automatically on HTTPS calls using the configured cert/key. +access = client.client_credentials.get_token -

    Subsequent resource requests will also use mTLS on HTTPS endpoints of site:

    -

    resp = access.get(“/v1/protected”) -```

    +# Subsequent resource requests will also use mTLS on HTTPS endpoints of `site`: +resp = access.get("/v1/protected") +

    Notes:

    @@ -1316,25 +1284,23 @@

    Authentication schemes for the token request

    -

    ruby -OAuth2::Client.new( +

    OAuth2::Client.new(
       id,
       secret,
    -  site: "https://provider.example.com",
    +  site: "https://provider.example.com",
       auth_scheme: :basic_auth, # default. Alternatives: :request_body, :tls_client_auth, :private_key_jwt
     )
    -

    +

    Faraday connection, timeouts, proxy, custom adapter/middleware:

    -

    ruby -client = OAuth2::Client.new( +

    client = OAuth2::Client.new(
       id,
       secret,
    -  site: "https://provider.example.com",
    +  site: "https://provider.example.com",
       connection_opts: {
         request: {open_timeout: 5, timeout: 15},
    -    proxy: ENV["HTTPS_PROXY"],
    +    proxy: ENV["HTTPS_PROXY"],
         ssl: {verify: true},
       },
     ) do |faraday|
    @@ -1342,19 +1308,18 @@ 

    Faraday conn # faraday.response :logger, Logger.new($stdout) # see OAUTH_DEBUG below faraday.adapter(:net_http_persistent) # or any Faraday adapter you need end -

    +

    Using flat query params (Faraday::FlatParamsEncoder)

    Some APIs expect repeated key parameters to be sent as flat params rather than arrays. Faraday provides FlatParamsEncoder for this purpose. You can configure the oauth2 client to use it when building requests.

    -

    ```ruby -require “faraday”

    +
    require "faraday"
     
    -

    client = OAuth2::Client.new( +client = OAuth2::Client.new( id, secret, - site: “https://api.example.com”, + site: "https://api.example.com", # Pass Faraday connection options to make FlatParamsEncoder the default connection_opts: { request: {params_encoder: Faraday::FlatParamsEncoder}, @@ -1362,33 +1327,32 @@

    Using flat query param ) do |faraday| faraday.request(:url_encoded) faraday.adapter(:net_http) -end

    +end -

    access = client.client_credentials.get_token

    +access = client.client_credentials.get_token -

    Example of a GET with two flat filter params (not an array):

    -

    # Results in: ?filter=order.clientCreatedTime%3E1445006997000&filter=order.clientCreatedTime%3C1445611797000 +# Example of a GET with two flat filter params (not an array): +# Results in: ?filter=order.clientCreatedTime%3E1445006997000&filter=order.clientCreatedTime%3C1445611797000 resp = access.get( - “/v1/orders”, + "/v1/orders", params: { # Provide the values as an array; FlatParamsEncoder expands them as repeated keys filter: [ - “order.clientCreatedTime>1445006997000”, - “order.clientCreatedTime<1445611797000”, + "order.clientCreatedTime>1445006997000", + "order.clientCreatedTime<1445611797000", ], }, ) -```

    +

    If you instead need to build a raw Faraday connection yourself, the equivalent configuration is:

    -

    ruby -conn = Faraday.new("https://api.example.com", request: {params_encoder: Faraday::FlatParamsEncoder}) -

    +
    conn = Faraday.new("https://api.example.com", request: {params_encoder: Faraday::FlatParamsEncoder})
    +

    Redirection

    -

    The library follows up to max_redirects (default 5). +

    The library follows up to max_redirects (default 5).
    You can override per-client via options[:max_redirects].

    Handling Responses and Errors

    @@ -1397,52 +1361,48 @@

    Handling Responses and Errors

  • Parsing:
  • -

    ruby -resp = access.get("/v1/thing") +

    resp = access.get("/v1/thing")
     resp.status     # Integer
     resp.headers    # Hash
     resp.body       # String
     resp.parsed     # SnakyHash::StringKeyed or Array when JSON array
    -

    +
    • Error handling:
    -

    ruby -begin - access.get("/v1/forbidden") +

    begin
    +  access.get("/v1/forbidden")
     rescue OAuth2::Error => e
       e.code         # OAuth2 error code (when present)
       e.description  # OAuth2 error description (when present)
       e.response     # OAuth2::Response (full access to status/headers/body)
     end
    -

    +
    • Disable raising on 4xx/5xx to inspect the response yourself:
    -

    ruby -client = OAuth2::Client.new(id, secret, site: site, raise_errors: false) -res = client.request(:get, "/v1/maybe-errors") +

    client = OAuth2::Client.new(id, secret, site: site, raise_errors: false)
    +res = client.request(:get, "/v1/maybe-errors")
     if res.status == 429
    -  sleep res.headers["retry-after"].to_i
    +  sleep res.headers["retry-after"].to_i
     end
    -

    +

    Making Raw Token Requests

    If a provider requires non-standard parameters or headers, you can call client.get_token directly:

    -

    ruby -access = client.get_token({ - grant_type: "client_credentials", - audience: "https://api.example.com", - headers: {"X-Custom" => "value"}, +

    access = client.get_token({
    +  grant_type: "client_credentials",
    +  audience: "https://api.example.com",
    +  headers: {"X-Custom" => "value"},
       parse: :json, # override parsing
     })
    -

    +

    OpenID Connect (OIDC) Notes

    @@ -1461,23 +1421,23 @@

    Debugging


    -

    🦷 FLOSS Funding

    +

    🦷 FLOSS Funding

    -

    While ruby-oauth tools are free software and will always be, the project would benefit immensely from some funding. +

    While ruby-oauth tools are free software and will always be, the project would benefit immensely from some funding.
    Raising a monthly budget of… “dollars” would make the project more sustainable.

    -

    We welcome both individual and corporate sponsors! We also offer a -wide array of funding channels to account for your preferences +

    We welcome both individual and corporate sponsors! We also offer a
    +wide array of funding channels to account for your preferences
    (although currently Open Collective is our preferred funding platform).

    -

    If you’re working in a company that’s making significant use of ruby-oauth tools we’d +

    If you’re working in a company that’s making significant use of ruby-oauth tools we’d
    appreciate it if you suggest to your company to become a ruby-oauth sponsor.

    -

    You can support the development of ruby-oauth tools via -GitHub Sponsors, -Liberapay, -PayPal, -Open Collective +

    You can support the development of ruby-oauth tools via
    +GitHub Sponsors,
    +Liberapay,
    +PayPal,
    +Open Collective
    and Tidelift.

    @@ -1500,7 +1460,7 @@

    Open Collective for Individuals

    NOTE: kettle-readme-backers updates this list every day, automatically.

    -

    No backers yet. Be the first! +

    No backers yet. Be the first!

    Open Collective for Organizations

    @@ -1510,7 +1470,7 @@

    Open Collective for Organizations

    NOTE: kettle-readme-backers updates this list every day, automatically.

    -

    No sponsors yet. Be the first! +

    No sponsors yet. Be the first!

    Another way to support open-source

    @@ -1525,24 +1485,24 @@

    Another way to support open-sourceOpenCollective Backers OpenCollective Sponsors Sponsor Me on Github Liberapay Goal Progress Donate on PayPal Buy me a coffee Donate on Polar Donate to my FLOSS efforts at ko-fi.com Donate to my FLOSS efforts using Patreon

    -

    🔐 Security

    +

    🔐 Security

    -

    To report a security vulnerability, please use the Tidelift security contact. +

    To report a security vulnerability, please use the Tidelift security contact.
    Tidelift will coordinate the fix and disclosure.

    For more see SECURITY.md, THREAT_MODEL.md, and IRP.md.

    -

    🤝 Contributing

    +

    🤝 Contributing

    -

    If you need some ideas of where to help, you could work on adding more code coverage, -or if it is already 💯 (see below) check reek, issues, or PRs, +

    If you need some ideas of where to help, you could work on adding more code coverage,
    +or if it is already 💯 (see below) check reek, issues, or PRs,
    or use the gem and think about how it could be better.

    We Keep A Changelog so if you make changes, remember to update it.

    See CONTRIBUTING.md for more detailed instructions.

    -

    🚀 Release Instructions

    +

    🚀 Release Instructions

    See CONTRIBUTING.md.

    @@ -1554,12 +1514,12 @@

    Code Coverage

    QLTY Test Coverage

    -

    🪇 Code of Conduct

    +

    🪇 Code of Conduct

    -

    Everyone interacting with this project’s codebases, issue trackers, +

    Everyone interacting with this project’s codebases, issue trackers,
    chat rooms and mailing lists agrees to follow the Contributor Covenant 2.1.

    -

    🌈 Contributors

    +

    🌈 Contributors

    Contributors

    @@ -1580,29 +1540,28 @@

    🌈 Contributors

    -

    📌 Versioning

    +

    📌 Versioning

    -

    This Library adheres to Semantic Versioning 2.0.0. -Violations of this scheme should be reported as bugs. -Specifically, if a minor or patch version is released that breaks backward compatibility, -a new version should be immediately released that restores compatibility. +

    This Library adheres to Semantic Versioning 2.0.0.
    +Violations of this scheme should be reported as bugs.
    +Specifically, if a minor or patch version is released that breaks backward compatibility,
    +a new version should be immediately released that restores compatibility.
    Breaking changes to the public API will only be introduced with new major versions.

    -

    dropping support for a platform is both obviously and objectively a breaking change
    +

    dropping support for a platform is both obviously and objectively a breaking change

    —Jordan Harband (@ljharb, maintainer of SemVer) in SemVer issue 716

    -

    I understand that policy doesn’t work universally (“exceptions to every rule!”), -but it is the policy here. -As such, in many cases it is good to specify a dependency on this library using +

    I understand that policy doesn’t work universally (“exceptions to every rule!”),
    +but it is the policy here.
    +As such, in many cases it is good to specify a dependency on this library using
    the Pessimistic Version Constraint with two digits of precision.

    For example:

    -

    ruby -spec.add_dependency("oauth2", "~> 2.0") -

    +
    spec.add_dependency("oauth2", "~> 2.0")
    +
    📌 Is "Platform Support" part of the public API? More details inside. @@ -1621,13 +1580,13 @@

    📌 Versioning

    See CHANGELOG.md for a list of releases.

    -

    📄 License

    +

    📄 License

    -

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

    The gem is available as open source under the terms of
    +the MIT License License: MIT.
    See LICENSE.txt for the official Copyright Notice.

    - +
    • @@ -1644,13 +1603,13 @@
    -

    🤑 A request for help

    +

    🤑 A request for help

    -

    Maintainers have teeth and need to pay their dentists. -After getting laid off in an RIF in March, and encountering difficulty finding a new one, -I began spending most of my time building open source tools. -I’m hoping to be able to pay for my kids’ health insurance this month, -so if you value the work I am doing, I need your support. +

    Maintainers have teeth and need to pay their dentists.
    +After getting laid off in an RIF in March, and encountering difficulty finding a new one,
    +I began spending most of my time building open source tools.
    +I’m hoping to be able to pay for my kids’ health insurance this month,
    +so if you value the work I am doing, I need your support.
    Please consider sponsoring me or the project.

    To join the community or get help 👇️ Join the Discord.

    @@ -1677,7 +1636,7 @@

    Please give the project a star ⭐ ♥ diff --git a/docs/file.RUBOCOP.html b/docs/file.RUBOCOP.html index a05b5068..05ae681e 100644 --- a/docs/file.RUBOCOP.html +++ b/docs/file.RUBOCOP.html @@ -69,20 +69,21 @@

    RuboCop Gradual

    RuboCop LTS

    -

    This project uses rubocop-lts to ensure, on a best-effort basis, compatibility with Ruby >= 1.9.2. +

    This project uses rubocop-lts to ensure, on a best-effort basis, compatibility with Ruby >= 1.9.2.
    RuboCop rules are meticulously configured by the rubocop-lts family of gems to ensure that a project is compatible with a specific version of Ruby. See: https://rubocop-lts.gitlab.io for more.

    Checking RuboCop Violations

    To check for RuboCop violations in this project, always use:

    -

    bash -bundle exec rake rubocop_gradual:check -

    +
    bundle exec rake rubocop_gradual:check
    +
    -

    Do not use the standard RuboCop commands like: -- bundle exec rubocop -- rubocop

    +

    Do not use the standard RuboCop commands like:

    +
      +
    • bundle exec rubocop
    • +
    • rubocop
    • +

    Understanding the Lock File

    @@ -121,7 +122,7 @@

    Common Commands

    Workflow

      -
    1. Before submitting a PR, run bundle exec rake rubocop_gradual:autocorrect +
    2. Before submitting a PR, run bundle exec rake rubocop_gradual:autocorrect
      a. or just the default bundle exec rake, as autocorrection is a pre-requisite of the default task.
    3. If there are new violations, either:
        @@ -149,7 +150,7 @@

        Never add inline RuboCop disables

        In general, treat the rules as guidance to follow; fix violations rather than ignore them. For example, RSpec conventions in this project expect described_class to be used in specs that target a specific class under test.

        -

        Benefits of rubocop_gradual

        +

        Benefits of rubocop_gradual

        • Allows incremental adoption of code style rules
        • @@ -160,7 +161,7 @@

          Benefits of rubocop_gradual

          diff --git a/docs/file.SECURITY.html b/docs/file.SECURITY.html index 39842b81..76ca890a 100644 --- a/docs/file.SECURITY.html +++ b/docs/file.SECURITY.html @@ -78,22 +78,22 @@

          Supported Versions

          Security contact information

          -

          To report a security vulnerability, please use the -Tidelift security contact. +

          To report a security vulnerability, please use the
          +Tidelift security contact.
          Tidelift will coordinate the fix and disclosure.

          More detailed explanation of the process is in IRP.md

          Additional Support

          -

          If you are interested in support for versions older than the latest release, -please consider sponsoring the project / maintainer @ https://liberapay.com/pboling/donate, +

          If you are interested in support for versions older than the latest release,
          +please consider sponsoring the project / maintainer @ https://liberapay.com/pboling/donate,
          or find other sponsorship links in the README.

          diff --git a/docs/file.THREAT_MODEL.html b/docs/file.THREAT_MODEL.html index 71e52de8..7bd7cc73 100644 --- a/docs/file.THREAT_MODEL.html +++ b/docs/file.THREAT_MODEL.html @@ -59,10 +59,10 @@

          Threat Model Outline for oauth2 Ruby Gem

          -

          1. Overview

          +

          1. Overview

          This document outlines the threat model for the oauth2 Ruby gem, which implements OAuth 2.0, 2.1, and OIDC Core protocols. The gem is used to facilitate secure authorization and authentication in Ruby applications.

          -

          2. Assets to Protect

          +

          2. Assets to Protect

          • OAuth access tokens, refresh tokens, and ID tokens
          • User credentials (if handled)
          • @@ -71,7 +71,7 @@

            2. Assets to Protect

          • Private keys and certificates (for signing/verifying tokens)
          -

          3. Potential Threat Actors

          +

          3. Potential Threat Actors

          • External attackers (internet-based)
          • Malicious OAuth clients or resource servers
          • @@ -79,7 +79,7 @@

            3. Potential Threat Actors

          • Compromised dependencies
          -

          4. Attack Surfaces

          +

          4. Attack Surfaces

          • OAuth endpoints (authorization, token, revocation, introspection)
          • HTTP request/response handling
          • @@ -88,9 +88,9 @@

            4. Attack Surfaces

          • Dependency supply chain
          -

          5. Threats and Mitigations

          +

          5. Threats and Mitigations

          -

          5.1 Token Leakage

          +

          5.1 Token Leakage

          • Threat: Tokens exposed via logs, URLs, or insecure storage
          • @@ -104,7 +104,7 @@

            5.1 Token Leakage

          -

          5.2 Token Replay and Forgery

          +

          5.2 Token Replay and Forgery

          • Threat: Attackers reuse or forge tokens
          • @@ -118,7 +118,7 @@

            5.2 Token Replay and Forgery

          -

          5.3 Insecure Communication

          +

          5.3 Insecure Communication

          • Threat: Data intercepted via MITM attacks
          • @@ -131,7 +131,7 @@

            5.3 Insecure Communication

          -

          5.4 Client Secret Exposure

          +

          5.4 Client Secret Exposure

          • Threat: Client secrets leaked in code or version control
          • @@ -144,7 +144,7 @@

            5.4 Client Secret Exposure

          -

          5.5 Dependency Vulnerabilities

          +

          5.5 Dependency Vulnerabilities

          • Threat: Vulnerabilities in third-party libraries
          • @@ -157,7 +157,7 @@

            5.5 Dependency Vulnerabilities

          -

          5.6 Improper Input Validation

          +

          5.6 Improper Input Validation

          • Threat: Injection attacks via untrusted input
          • @@ -170,7 +170,7 @@

            5.6 Improper Input Validation

          -

          5.7 Insufficient Logging and Monitoring

          +

          5.7 Insufficient Logging and Monitoring

          • Threat: Attacks go undetected
          • @@ -183,19 +183,19 @@

            5.7 Insufficient Logging and Monito

          -

          6. Assumptions

          +

          6. Assumptions

          • The gem is used in a secure environment with up-to-date Ruby and dependencies
          • End-users are responsible for secure configuration and deployment
          -

          7. Out of Scope

          +

          7. Out of Scope

          • Security of external OAuth providers
          • Application-level business logic
          -

          8. References

          +

          8. References

          diff --git a/docs/index.html b/docs/index.html index 57948e36..53a7821f 100644 --- a/docs/index.html +++ b/docs/index.html @@ -99,7 +99,7 @@

          Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0 ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5 oauth2 Logo by Chris Messina, CC BY-SA 3.0

          -

          🔐 OAuth 2.0 Authorization Framework

          +

          🔐 OAuth 2.0 Authorization Framework

          ⭐️ including OAuth 2.1 draft spec & OpenID Connect (OIDC)

          @@ -113,11 +113,11 @@

          🔐 OAuth 2.0 Authorization Framework

          OpenCollective Backers OpenCollective Sponsors Sponsor Me on Github Liberapay Goal Progress Donate on PayPal Buy me a coffee Donate on Polar Donate at ko-fi.com

          -

          🌻 Synopsis

          +

          🌻 Synopsis

          -

          OAuth 2.0 is the industry-standard protocol for authorization. -OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, - desktop applications, mobile phones, and living room devices. +

          OAuth 2.0 is the industry-standard protocol for authorization.
          +OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications,
          + desktop applications, mobile phones, and living room devices.
          This is a RubyGem for implementing OAuth 2.0 clients (not servers) in Ruby applications.

          Quick Examples

          @@ -125,29 +125,27 @@

          Quick Examples

          Convert the following `curl` command into a token request using this gem... -

          shell -curl --request POST \ - --url 'https://login.microsoftonline.com/REDMOND_REDACTED/oauth2/token' \ - --header 'content-type: application/x-www-form-urlencoded' \ +

          curl --request POST \
          +  --url 'https://login.microsoftonline.com/REDMOND_REDACTED/oauth2/token' \
          +  --header 'content-type: application/x-www-form-urlencoded' \
             --data grant_type=client_credentials \
             --data client_id=REDMOND_CLIENT_ID \
             --data client_secret=REDMOND_CLIENT_SECRET \
             --data resource=REDMOND_RESOURCE_UUID
          -

          +

          NOTE: In the ruby version below, certain params are passed to the get_token call, instead of the client creation.

          -

          ruby -OAuth2::Client.new( - "REDMOND_CLIENT_ID", # client_id - "REDMOND_CLIENT_SECRET", # client_secret +

          OAuth2::Client.new(
          +  "REDMOND_CLIENT_ID", # client_id
          +  "REDMOND_CLIENT_SECRET", # client_secret
             auth_scheme: :request_body, # Other modes are supported: :basic_auth, :tls_client_auth, :private_key_jwt
          -  token_url: "oauth2/token", # relative path, except with leading `/`, then absolute path
          -  site: "https://login.microsoftonline.com/REDMOND_REDACTED",
          +  token_url: "oauth2/token", # relative path, except with leading `/`, then absolute path
          +  site: "https://login.microsoftonline.com/REDMOND_REDACTED",
           ). # The base path for token_url when it is relative
             client_credentials. # There are many other types to choose from!
          -  get_token(resource: "REDMOND_RESOURCE_UUID")
          -

          + get_token(resource: "REDMOND_RESOURCE_UUID") +

          NOTE: header - The content type specified in the curl is already the default!

          @@ -161,29 +159,26 @@

          Quick Examples

        • E2E example does not ship with the released gem, so clone the source to play with it.
        -

        console -docker compose -f docker-compose-ssl.yml up -d --wait +

        docker compose -f docker-compose-ssl.yml up -d --wait
         ruby examples/e2e.rb
         # If your machine is slow or Docker pulls are cold, increase the wait:
         E2E_WAIT_TIMEOUT=120 ruby examples/e2e.rb
         # The mock server serves HTTP on 8080; the example points to http://localhost:8080 by default.
        -

        +

        The output should be something like this:

        -

        console -➜ ruby examples/e2e.rb +

        ➜  ruby examples/e2e.rb
         Access token (truncated): eyJraWQiOiJkZWZhdWx0...
         userinfo status: 200
        -userinfo body: {"sub" => "demo-sub", "aud" => ["demo-aud"], "nbf" => 1757816758000, "iss" => "http://localhost:8080/default", "exp" => 1757820358000, "iat" => 1757816758000, "jti" => "d63b97a7-ebe5-4dea-93e6-d542caba6104"}
        +userinfo body: {"sub" => "demo-sub", "aud" => ["demo-aud"], "nbf" => 1757816758000, "iss" => "http://localhost:8080/default", "exp" => 1757820358000, "iat" => 1757816758000, "jti" => "d63b97a7-ebe5-4dea-93e6-d542caba6104"}
         E2E complete
        -

        +

        Make sure to shut down the mock server when you are done:

        -

        console -docker compose -f docker-compose-ssl.yml down -

        +
        docker compose -f docker-compose-ssl.yml down
        +

        Troubleshooting: validate connectivity to the mock server

        @@ -253,7 +248,7 @@

        Quick Examples

        oauth sibling gem for OAuth 1.0a implementations in Ruby.
      -

      💡 Info you can shake a stick at

      +

      💡 Info you can shake a stick at

    @@ -417,7 +412,7 @@

    Federated DVCS

    -

    Enterprise Support Tidelift +

    Enterprise Support Tidelift

    Available as part of the Tidelift Subscription.

    @@ -446,21 +441,19 @@

    ✨ Installation

    +

    ✨ Installation

    Install the gem and add to the application’s Gemfile by executing:

    -

    console -bundle add oauth2 -

    +
    bundle add oauth2
    +

    If bundler is not being used to manage dependencies, install the gem by executing:

    -

    console -gem install oauth2 -

    +
    gem install oauth2
    +
    -

    🔒 Secure Installation

    +

    🔒 Secure Installation

    For Medium or High Security Installations @@ -471,15 +464,13 @@

    🔒 Secure Installation

    Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:

    -

    console -gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem) -

    +
    gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
    +

    You only need to do that once. Then proceed to install with:

    -

    console -gem install oauth2 -P MediumSecurity -

    +
    gem install oauth2 -P MediumSecurity
    +

    The MediumSecurity trust profile will verify signed gems, but allow the installation of unsigned dependencies.

    @@ -487,9 +478,8 @@

    🔒 Secure Installation

    If you want to up your security game full-time:

    -

    console -bundle config set --global trust-policy MediumSecurity -

    +
    bundle config set --global trust-policy MediumSecurity
    +

    MediumSecurity instead of HighSecurity is necessary if not all the gems you use are signed.

    @@ -545,9 +535,9 @@

    What is new for v2.0?

    Compatibility

    -

    Targeted ruby compatibility is non-EOL versions of Ruby, currently 3.2, 3.3, and 3.4. -Compatibility is further distinguished as “Best Effort Support” or “Incidental Support” for older versions of Ruby. -This gem will install on Ruby versions >= v2.2 for 2.x releases. +

    Targeted ruby compatibility is non-EOL versions of Ruby, currently 3.2, 3.3, and 3.4.
    +Compatibility is further distinguished as “Best Effort Support” or “Incidental Support” for older versions of Ruby.
    +This gem will install on Ruby versions >= v2.2 for 2.x releases.
    See 1-4-stable branch for older rubies.

    @@ -618,98 +608,93 @@

    Compatibility

    -

    NOTE: The 1.4 series will only receive critical security updates. +

    NOTE: The 1.4 series will only receive critical security updates.
    See SECURITY.md and IRP.md.

    -

    ⚙️ Configuration

    +

    ⚙️ Configuration

    You can turn on additional warnings.

    -

    ruby -OAuth2.configure do |config| +

    OAuth2.configure do |config|
       # Turn on a warning like:
    -  #   OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key
    +  #   OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key
       config.silence_extra_tokens_warning = false # default: true
       # Set to true if you want to also show warnings about no tokens
       config.silence_no_tokens_warning = false # default: true,
     end
    -

    +
    -

    The “extra tokens” problem comes from ambiguity in the spec about which token is the right token. -Some OAuth 2.0 standards legitimately have multiple tokens. -You may need to subclass OAuth2::AccessToken, or write your own custom alternative to it, and pass it in. +

    The “extra tokens” problem comes from ambiguity in the spec about which token is the right token.
    +Some OAuth 2.0 standards legitimately have multiple tokens.
    +You may need to subclass OAuth2::AccessToken, or write your own custom alternative to it, and pass it in.
    Specify your custom class with the access_token_class option.

    -

    If you only need one token, you can, as of v2.0.10, -specify the exact token name you want to extract via the OAuth2::AccessToken using +

    If you only need one token, you can, as of v2.0.10,
    +specify the exact token name you want to extract via the OAuth2::AccessToken using
    the token_name option.

    -

    You’ll likely need to do some source diving. -This gem has 100% test coverage for lines and branches, so the specs are a great place to look for ideas. +

    You’ll likely need to do some source diving.
    +This gem has 100% test coverage for lines and branches, so the specs are a great place to look for ideas.
    If you have time and energy, please contribute to the documentation!

    -

    🔧 Basic Usage

    +

    🔧 Basic Usage

    -

    +

    authorize_url and token_url are on site root (Just Works!)

    -

    ```ruby -require “oauth2” -client = OAuth2::Client.new(“client_id”, “client_secret”, site: “https://example.org”) -# => #<OAuth2::Client:0x00000001204c8288 @id=”client_id”, @secret=”client_sec… -client.auth_code.authorize_url(redirect_uri: “http://localhost:8080/oauth2/callback”) -# => “https://example.org/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code”

    +
    require "oauth2"
    +client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org")
    +# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
    +client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
    +# => "https://example.org/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
     
    -

    access = client.auth_code.get_token(“authorization_code_value”, redirect_uri: “http://localhost:8080/oauth2/callback”, headers: {”Authorization” => “Basic some_password”}) -response = access.get(“/api/resource”, params: {”query_foo” => “bar”}) +access = client.auth_code.get_token("authorization_code_value", redirect_uri: "http://localhost:8080/oauth2/callback", headers: {"Authorization" => "Basic some_password"}) +response = access.get("/api/resource", params: {"query_foo" => "bar"}) response.class.name # => OAuth2::Response -```

    +
    -

    Relative authorize_url and token_url (Not on site root, Just Works!)

    +

    Relative authorize_url and token_url (Not on site root, Just Works!)

    In the above example, the default Authorization URL is oauth/authorize and default Access Token URL is oauth/token, and, as they are missing a leading /, both are relative.

    -

    ruby -client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/nested/directory/on/your/server") -# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec... -client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback") -# => "https://example.org/nested/directory/on/your/server/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code" -

    +
    client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/nested/directory/on/your/server")
    +# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
    +client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
    +# => "https://example.org/nested/directory/on/your/server/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
    +
    -

    Customize authorize_url and token_url +

    Customize authorize_url and token_url

    You can specify custom URLs for authorization and access token, and when using a leading / they will not be relative, as shown below:

    -

    ruby -client = OAuth2::Client.new( - "client_id", - "client_secret", - site: "https://example.org/nested/directory/on/your/server", - authorize_url: "/jaunty/authorize/", - token_url: "/stirrups/access_token", +

    client = OAuth2::Client.new(
    +  "client_id",
    +  "client_secret",
    +  site: "https://example.org/nested/directory/on/your/server",
    +  authorize_url: "/jaunty/authorize/",
    +  token_url: "/stirrups/access_token",
     )
    -# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
    -client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
    -# => "https://example.org/jaunty/authorize/?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
    +# => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
    +client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
    +# => "https://example.org/jaunty/authorize/?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
     client.class.name
     # => OAuth2::Client
    -

    +
    -

    snake_case and indifferent access in Response#parsed

    +

    snake_case and indifferent access in Response#parsed

    -

    ruby -response = access.get("/api/resource", params: {"query_foo" => "bar"}) +

    response = access.get("/api/resource", params: {"query_foo" => "bar"})
     # Even if the actual response is CamelCase. it will be made available as snaky:
    -JSON.parse(response.body)         # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
    -response.parsed                   # => {"access_token"=>"aaaaaaaa", "additional_data"=>"additional"}
    -response.parsed.access_token      # => "aaaaaaaa"
    -response.parsed[:access_token]    # => "aaaaaaaa"
    -response.parsed.additional_data   # => "additional"
    -response.parsed[:additional_data] # => "additional"
    +JSON.parse(response.body)         # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
    +response.parsed                   # => {"access_token"=>"aaaaaaaa", "additional_data"=>"additional"}
    +response.parsed.access_token      # => "aaaaaaaa"
    +response.parsed[:access_token]    # => "aaaaaaaa"
    +response.parsed.additional_data   # => "additional"
    +response.parsed[:additional_data] # => "additional"
     response.parsed.class.name        # => SnakyHash::StringKeyed (from snaky_hash gem)
    -

    +

    Serialization

    @@ -721,83 +706,80 @@
    Global Serialization Config

    Globally configure SnakyHash::StringKeyed to use the serializer. Put this in your code somewhere reasonable (like an initializer for Rails).

    -

    ruby -SnakyHash::StringKeyed.class_eval do +

    SnakyHash::StringKeyed.class_eval do
       extend SnakyHash::Serializer
     end
    -

    +
    Discrete Serialization Config

    Discretely configure a custom Snaky Hash class to use the serializer.

    -

    ```ruby -class MySnakyHash < SnakyHash::StringKeyed - # Give this hash class dump and load abilities! +

    class MySnakyHash < SnakyHash::StringKeyed
    +  # Give this hash class `dump` and `load` abilities!
       extend SnakyHash::Serializer
    -end

    +end -

    And tell your client to use the custom class in each call:

    -

    client = OAuth2::Client.new(“client_id”, “client_secret”, site: “https://example.org/oauth2”) +# And tell your client to use the custom class in each call: +client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/oauth2") token = client.get_token({snaky_hash_klass: MySnakyHash}) -```

    +
    Serialization Extensions

    These extensions work regardless of whether you used the global or discrete config above.

    -

    There are a few hacks you may need in your class to support Ruby < 2.4.2 or < 2.6. -They are likely not needed if you are on a newer Ruby. -Expand the examples below, or the ruby-oauth/snaky_hash gem, +

    There are a few hacks you may need in your class to support Ruby < 2.4.2 or < 2.6.
    +They are likely not needed if you are on a newer Ruby.
    +Expand the examples below, or the ruby-oauth/snaky_hash gem,
    or response_spec.rb, for more ideas, especially if you need to study the hacks for older Rubies.

    See Examples -

    ```ruby -class MySnakyHash < SnakyHash::StringKeyed - # Give this hash class dump and load abilities! - extend SnakyHash::Serializer

    +
    class MySnakyHash < SnakyHash::StringKeyed
    +  # Give this hash class `dump` and `load` abilities!
    +  extend SnakyHash::Serializer
     
    -  

    #### Serialization Extentions + #### Serialization Extentions # # Act on the non-hash values (including the values of hashes) as they are dumped to JSON # In other words, this retains nested hashes, and only the deepest leaf nodes become bananas. # WARNING: This is a silly example! dump_value_extensions.add(:to_fruit) do |value| - “banana” # => Make values “banana” on dump - end

    + "banana" # => Make values "banana" on dump + end -

    # Act on the non-hash values (including the values of hashes) as they are loaded from the JSON dump - # In other words, this retains nested hashes, and only the deepest leaf nodes become . + # Act on the non-hash values (including the values of hashes) as they are loaded from the JSON dump + # In other words, this retains nested hashes, and only the deepest leaf nodes become ***. # WARNING: This is a silly example! load_value_extensions.add(:to_stars) do |value| - “” # Turn dumped bananas into *** when they are loaded - end

    + "***" # Turn dumped bananas into *** when they are loaded + end -

    # Act on the entire hash as it is prepared for dumping to JSON + # Act on the entire hash as it is prepared for dumping to JSON # WARNING: This is a silly example! dump_hash_extensions.add(:to_cheese) do |value| if value.is_a?(Hash) value.transform_keys do |key| - split = key.split(“_”) + split = key.split("_") first_word = split[0] - key.sub(first_word, “cheese”) + key.sub(first_word, "cheese") end else value end - end

    + end -

    # Act on the entire hash as it is loaded from the JSON dump + # Act on the entire hash as it is loaded from the JSON dump # WARNING: This is a silly example! load_hash_extensions.add(:to_pizza) do |value| if value.is_a?(Hash) res = klass.new value.keys.each_with_object(res) do |key, result| - split = key.split(“_”) + split = key.split("_") last_word = split[-1] - new_key = key.sub(last_word, “pizza”) + new_key = key.sub(last_word, "pizza") result[new_key] = value[key] end res @@ -806,64 +788,61 @@

    Serialization Extensions
    end end end -```

    +
    -

    Prefer camelCase over snake_case? => snaky: false

    +

    Prefer camelCase over snake_case? => snaky: false

    -

    ruby -response = access.get("/api/resource", params: {"query_foo" => "bar"}, snaky: false) -JSON.parse(response.body) # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"} -response.parsed # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"} -response.parsed["accessToken"] # => "aaaaaaaa" -response.parsed["additionalData"] # => "additional" +

    response = access.get("/api/resource", params: {"query_foo" => "bar"}, snaky: false)
    +JSON.parse(response.body)         # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
    +response.parsed                   # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
    +response.parsed["accessToken"]    # => "aaaaaaaa"
    +response.parsed["additionalData"] # => "additional"
     response.parsed.class.name        # => Hash (just, regular old Hash)
    -

    +
    Debugging & Logging

    Set an environment variable as per usual (e.g. with dotenv).

    -

    ruby -# will log both request and response, including bodies -ENV["OAUTH_DEBUG"] = "true" -

    +
    # will log both request and response, including bodies
    +ENV["OAUTH_DEBUG"] = "true"
    +

    By default, debug output will go to $stdout. This can be overridden when initializing your OAuth2::Client.

    -

    ruby -require "oauth2" +

    require "oauth2"
     client = OAuth2::Client.new(
    -  "client_id",
    -  "client_secret",
    -  site: "https://example.org",
    -  logger: Logger.new("example.log", "weekly"),
    +  "client_id",
    +  "client_secret",
    +  site: "https://example.org",
    +  logger: Logger.new("example.log", "weekly"),
     )
    -

    +

    OAuth2::Response

    -

    The AccessToken methods #get, #post, #put and #delete and the generic #request +

    The AccessToken methods #get, #post, #put and #delete and the generic #request
    will return an instance of the #OAuth2::Response class.

    -

    This instance contains a #parsed method that will parse the response body and -return a Hash-like SnakyHash::StringKeyed if the Content-Type is application/x-www-form-urlencoded or if -the body is a JSON object. It will return an Array if the body is a JSON +

    This instance contains a #parsed method that will parse the response body and
    +return a Hash-like SnakyHash::StringKeyed if the Content-Type is application/x-www-form-urlencoded or if
    +the body is a JSON object. It will return an Array if the body is a JSON
    array. Otherwise, it will return the original body string.

    -

    The original response body, headers, and status can be accessed via their +

    The original response body, headers, and status can be accessed via their
    respective methods.

    OAuth2::AccessToken

    -

    If you have an existing Access Token for a user, you can initialize an instance -using various class methods including the standard new, from_hash (if you have -a hash of the values), or from_kvform (if you have an +

    If you have an existing Access Token for a user, you can initialize an instance
    +using various class methods including the standard new, from_hash (if you have
    +a hash of the values), or from_kvform (if you have an
    application/x-www-form-urlencoded encoded string of the values).

    Options (since v2.0.x unless noted):

    @@ -928,14 +907,14 @@

    OAuth2::AccessToken

    OAuth2::Error

    -

    On 400+ status code responses, an OAuth2::Error will be raised. If it is a -standard OAuth2 error response, the body will be parsed and #code and #description will contain the values provided from the error and -error_description parameters. The #response property of OAuth2::Error will +

    On 400+ status code responses, an OAuth2::Error will be raised. If it is a
    +standard OAuth2 error response, the body will be parsed and #code and #description will contain the values provided from the error and
    +error_description parameters. The #response property of OAuth2::Error will
    always contain the OAuth2::Response instance.

    -

    If you do not want an error to be raised, you may use :raise_errors => false -option on initialization of the client. In this case the OAuth2::Response -instance will be returned as usual and on 400+ status code responses, the +

    If you do not want an error to be raised, you may use :raise_errors => false
    +option on initialization of the client. In this case the OAuth2::Response
    +instance will be returned as usual and on 400+ status code responses, the
    Response instance will contain the OAuth2::Error instance.

    Authorization Grants

    @@ -962,55 +941,53 @@

    Authorization Grants

  • Differences overview: https://fusionauth.io/learn/expert-advice/oauth/differences-between-oauth-2-oauth-2-1/
  • -

    Currently, the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion -authentication grant types have helper strategy classes that simplify client -use. They are available via the #auth_code, -#implicit, -#password, -#client_credentials, and +

    Currently, the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion
    +authentication grant types have helper strategy classes that simplify client
    +use. They are available via the #auth_code,
    +#implicit,
    +#password,
    +#client_credentials, and
    #assertion methods respectively.

    These aren’t full examples, but demonstrative of the differences between usage for each strategy.

    -

    ```ruby -auth_url = client.auth_code.authorize_url(redirect_uri: “http://localhost:8080/oauth/callback”) -access = client.auth_code.get_token(“code_value”, redirect_uri: “http://localhost:8080/oauth/callback”)

    +
    auth_url = client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth/callback")
    +access = client.auth_code.get_token("code_value", redirect_uri: "http://localhost:8080/oauth/callback")
     
    -

    auth_url = client.implicit.authorize_url(redirect_uri: “http://localhost:8080/oauth/callback”) +auth_url = client.implicit.authorize_url(redirect_uri: "http://localhost:8080/oauth/callback") # get the token params in the callback and -access = OAuth2::AccessToken.from_kvform(client, query_string)

    +access = OAuth2::AccessToken.from_kvform(client, query_string) -

    access = client.password.get_token(“username”, “password”)

    +access = client.password.get_token("username", "password") -

    access = client.client_credentials.get_token

    +access = client.client_credentials.get_token -

    Client Assertion Strategy

    -

    # see: https://tools.ietf.org/html/rfc7523 +# Client Assertion Strategy +# see: https://tools.ietf.org/html/rfc7523 claimset = { - iss: “http://localhost:3001”, - aud: “http://localhost:8080/oauth2/token”, - sub: “me@example.com”, + iss: "http://localhost:3001", + aud: "http://localhost:8080/oauth2/token", + sub: "me@example.com", exp: Time.now.utc.to_i + 3600, } -assertion_params = [claimset, “HS256”, “secret_key”] -access = client.assertion.get_token(assertion_params)

    +assertion_params = [claimset, "HS256", "secret_key"] +access = client.assertion.get_token(assertion_params) -

    The access (i.e. access token) is then used like so:

    -

    access.token # actual access_token string, if you need it somewhere -access.get(“/api/stuff”) # making api calls with access token -```

    +# The `access` (i.e. access token) is then used like so: +access.token # actual access_token string, if you need it somewhere +access.get("/api/stuff") # making api calls with access token +
    -

    If you want to specify additional headers to be sent out with the +

    If you want to specify additional headers to be sent out with the
    request, add a ‘headers’ hash under ‘params’:

    -

    ruby -access = client.auth_code.get_token("code_value", redirect_uri: "http://localhost:8080/oauth/callback", headers: {"Some" => "Header"}) -

    +
    access = client.auth_code.get_token("code_value", redirect_uri: "http://localhost:8080/oauth/callback", headers: {"Some" => "Header"})
    +
    -

    You can always use the #request method on the OAuth2::Client instance to make +

    You can always use the #request method on the OAuth2::Client instance to make
    requests for tokens for any Authentication grant type.

    -

    📘 Comprehensive Usage

    +

    📘 Comprehensive Usage

    Common Flows (end-to-end)

    @@ -1018,88 +995,84 @@

    Common Flows (end-to-end)

  • Authorization Code (server-side web app):
  • -

    ```ruby -require “oauth2” +

    require "oauth2"
     client = OAuth2::Client.new(
    -  ENV[“CLIENT_ID”],
    -  ENV[“CLIENT_SECRET”],
    -  site: “https://provider.example.com”,
    -  redirect_uri: “https://my.app.example.com/oauth/callback”,
    -)

    - -

    Step 1: redirect user to consent

    -

    state = SecureRandom.hex(16) -auth_url = client.auth_code.authorize_url(scope: “openid profile email”, state: state) -# redirect_to auth_url

    - -

    Step 2: handle the callback

    -

    # params[:code], params[:state] -raise “state mismatch” unless params[:state] == state -access = client.auth_code.get_token(params[:code])

    - -

    Step 3: call APIs

    -

    profile = access.get(“/api/v1/me”).parsed -```

    + ENV["CLIENT_ID"], + ENV["CLIENT_SECRET"], + site: "https://provider.example.com", + redirect_uri: "https://my.app.example.com/oauth/callback", +) + +# Step 1: redirect user to consent +state = SecureRandom.hex(16) +auth_url = client.auth_code.authorize_url(scope: "openid profile email", state: state) +# redirect_to auth_url + +# Step 2: handle the callback +# params[:code], params[:state] +raise "state mismatch" unless params[:state] == state +access = client.auth_code.get_token(params[:code]) + +# Step 3: call APIs +profile = access.get("/api/v1/me").parsed +
    • Client Credentials (machine-to-machine):
    -

    ruby -client = OAuth2::Client.new(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"], site: "https://provider.example.com") -access = client.client_credentials.get_token(audience: "https://api.example.com") -resp = access.get("/v1/things") -

    +
    client = OAuth2::Client.new(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"], site: "https://provider.example.com")
    +access = client.client_credentials.get_token(audience: "https://api.example.com")
    +resp = access.get("/v1/things")
    +
    • Resource Owner Password (legacy; avoid when possible):
    -

    ruby -access = client.password.get_token("jdoe", "s3cret", scope: "read") -

    +
    access = client.password.get_token("jdoe", "s3cret", scope: "read")
    +

    Examples

    JHipster UAA (Spring Cloud) password grant example (legacy; avoid when possible) -

    ```ruby -# This converts a Postman/Net::HTTP multipart token request to oauth2 gem usage. +

    # This converts a Postman/Net::HTTP multipart token request to oauth2 gem usage.
     # JHipster UAA typically exposes the token endpoint at /uaa/oauth/token.
     # The original snippet included:
     # - Basic Authorization header for the client (web_app:changeit)
     # - X-XSRF-TOKEN header from a cookie (some deployments require it)
     # - grant_type=password with username/password and client_id
    -# Using oauth2 gem, you don’t need to build multipart bodies; the gem sends
    -# application/x-www-form-urlencoded as required by RFC 6749.

    +# Using oauth2 gem, you don't need to build multipart bodies; the gem sends +# application/x-www-form-urlencoded as required by RFC 6749. -

    require “oauth2”

    +require "oauth2" -

    client = OAuth2::Client.new( - “web_app”, # client_id - “changeit”, # client_secret - site: “http://localhost:8080/uaa”, - token_url: “/oauth/token”, # absolute under site (or “oauth/token” relative) +client = OAuth2::Client.new( + "web_app", # client_id + "changeit", # client_secret + site: "http://localhost:8080/uaa", + token_url: "/oauth/token", # absolute under site (or "oauth/token" relative) auth_scheme: :basic_auth, # sends HTTP Basic Authorization header -)

    +) -

    If your UAA requires an XSRF header for the token call, provide it as a header.

    -

    # Often this is not required for token endpoints, but if your gateway enforces it, +# If your UAA requires an XSRF header for the token call, provide it as a header. +# Often this is not required for token endpoints, but if your gateway enforces it, # obtain the value from the XSRF-TOKEN cookie and pass it here. -xsrf_token = ENV[“X_XSRF_TOKEN”] # e.g., pulled from a prior set-cookie value

    +xsrf_token = ENV["X_XSRF_TOKEN"] # e.g., pulled from a prior set-cookie value -

    access = client.password.get_token( - “admin”, # username - “admin”, # password - headers: xsrf_token ? {”X-XSRF-TOKEN” => xsrf_token} : {}, +access = client.password.get_token( + "admin", # username + "admin", # password + headers: xsrf_token ? {"X-XSRF-TOKEN" => xsrf_token} : {}, # JHipster commonly also accepts/needs the client_id in the body; include if required: - # client_id: “web_app”, -)

    + # client_id: "web_app", +) -

    puts access.token +puts access.token puts access.to_hash # full token response -```

    +

    Notes:

    @@ -1124,61 +1097,60 @@

    Instagram API (verb‑dependent

    Example: exchanging and refreshing long‑lived Instagram tokens, and making API calls

    -

    ```ruby -require “oauth2”

    +
    require "oauth2"
     
    -

    NOTE: Users authenticate via Facebook Login to obtain a short‑lived user token (not shown here).

    -

    # See Facebook Login docs for obtaining the initial short‑lived token.

    +# NOTE: Users authenticate via Facebook Login to obtain a short‑lived user token (not shown here). +# See Facebook Login docs for obtaining the initial short‑lived token. -

    client = OAuth2::Client.new(nil, nil, site: “https://graph.instagram.com”)

    +client = OAuth2::Client.new(nil, nil, site: "https://graph.instagram.com") -

    Start with a short‑lived token you already obtained via Facebook Login

    -

    short_lived = OAuth2::AccessToken.new( +# Start with a short‑lived token you already obtained via Facebook Login +short_lived = OAuth2::AccessToken.new( client, - ENV[“IG_SHORT_LIVED_TOKEN”], + ENV["IG_SHORT_LIVED_TOKEN"], # Key part: verb‑dependent mode mode: {get: :query, post: :header, delete: :header}, -)

    +) -

    1) Exchange for a long‑lived token (Instagram requires GET with access_token in query)

    -

    # Endpoint: GET https://graph.instagram.com/access_token +# 1) Exchange for a long‑lived token (Instagram requires GET with access_token in query) +# Endpoint: GET https://graph.instagram.com/access_token # Params: grant_type=ig_exchange_token, client_secret=APP_SECRET exchange = short_lived.get( - “/access_token”, + "/access_token", params: { - grant_type: “ig_exchange_token”, - client_secret: ENV[“IG_APP_SECRET”], + grant_type: "ig_exchange_token", + client_secret: ENV["IG_APP_SECRET"], # access_token param will be added automatically by the AccessToken (mode => :query for GET) }, ) -long_lived_token_value = exchange.parsed[“access_token”]

    +long_lived_token_value = exchange.parsed["access_token"] -

    long_lived = OAuth2::AccessToken.new( +long_lived = OAuth2::AccessToken.new( client, long_lived_token_value, mode: {get: :query, post: :header, delete: :header}, -)

    +) -

    2) Refresh the long‑lived token (Instagram uses GET with token in query)

    -

    # Endpoint: GET https://graph.instagram.com/refresh_access_token +# 2) Refresh the long‑lived token (Instagram uses GET with token in query) +# Endpoint: GET https://graph.instagram.com/refresh_access_token refresh_resp = long_lived.get( - “/refresh_access_token”, - params: {grant_type: “ig_refresh_token”}, + "/refresh_access_token", + params: {grant_type: "ig_refresh_token"}, ) long_lived = OAuth2::AccessToken.new( client, - refresh_resp.parsed[“access_token”], + refresh_resp.parsed["access_token"], mode: {get: :query, post: :header, delete: :header}, -)

    +) -

    3) Typical API GET request (token in query automatically)

    -

    me = long_lived.get(“/me”, params: {fields: “id,username”}).parsed

    +# 3) Typical API GET request (token in query automatically) +me = long_lived.get("/me", params: {fields: "id,username"}).parsed -

    4) Example POST (token sent via Bearer header automatically)

    -

    # Note: Replace the path/params with a real Instagram Graph API POST you need, +# 4) Example POST (token sent via Bearer header automatically) +# Note: Replace the path/params with a real Instagram Graph API POST you need, # such as publishing media via the Graph API endpoints. -# long_lived.post(“/me/media”, body: {image_url: “https://…”, caption: “hello”}) -```

    +# long_lived.post("/me/media", body: {image_url: "https://...", caption: "hello"}) +

    Tips:

    @@ -1195,24 +1167,22 @@

    Refresh Tokens

  • Manual refresh:
  • -

    ruby -if access.expired? +

    if access.expired?
       access = access.refresh
     end
    -

    +
    • Auto-refresh wrapper pattern:
    -

    ```ruby -class AutoRefreshingToken +

    class AutoRefreshingToken
       def initialize(token_provider, store: nil)
         @token = token_provider
         @store = store # e.g., something that responds to read/write for token data
    -  end

    + end -

    def with(&blk) + def with(&blk) tok = ensure_fresh! blk ? blk.call(tok) : tok rescue OAuth2::Error => e @@ -1223,23 +1193,23 @@

    Refresh Tokens

    retry end raise - end

    + end -

    private

    +private -

    def ensure_fresh! + def ensure_fresh! if @token.expired? && @token.refresh_token @token = @token.refresh @store.write(@token.to_hash) if @store end @token end -end

    +end -

    usage

    -

    keeper = AutoRefreshingToken.new(access) -keeper.with { |tok| tok.get(“/v1/protected”) } -```

    +# usage +keeper = AutoRefreshingToken.new(access) +keeper.with { |tok| tok.get("/v1/protected") } +

    Persist the token across processes using AccessToken#to_hash and AccessToken.from_hash(client, hash).

    @@ -1247,13 +1217,12 @@

    Token Revocation (RFC 7009)

    You can revoke either the access token or the refresh token.

    -

    ```ruby -# Revoke the current access token -access.revoke(token_type_hint: :access_token)

    +
    # Revoke the current access token
    +access.revoke(token_type_hint: :access_token)
     
    -

    Or explicitly revoke the refresh token (often also invalidates associated access tokens)

    -

    access.revoke(token_type_hint: :refresh_token) -```

    +# Or explicitly revoke the refresh token (often also invalidates associated access tokens) +access.revoke(token_type_hint: :refresh_token) +

    Client Configuration Tips

    @@ -1263,35 +1232,34 @@

    Mutual TLS (mTLS) client authenti

    Example using PEM files (certificate and key):

    -

    ```ruby -require “oauth2” -require “openssl”

    +
    require "oauth2"
    +require "openssl"
     
    -

    client = OAuth2::Client.new( - ENV.fetch(“CLIENT_ID”), - ENV.fetch(“CLIENT_SECRET”), - site: “https://example.com”, - authorize_url: “/oauth/authorize/”, - token_url: “/oauth/token/”, +client = OAuth2::Client.new( + ENV.fetch("CLIENT_ID"), + ENV.fetch("CLIENT_SECRET"), + site: "https://example.com", + authorize_url: "/oauth/authorize/", + token_url: "/oauth/token/", auth_scheme: :tls_client_auth, # if your AS requires mTLS-based client authentication connection_opts: { ssl: { - client_cert: OpenSSL::X509::Certificate.new(File.read(“localhost.pem”)), - client_key: OpenSSL::PKey::RSA.new(File.read(“localhost-key.pem”)), + client_cert: OpenSSL::X509::Certificate.new(File.read("localhost.pem")), + client_key: OpenSSL::PKey::RSA.new(File.read("localhost-key.pem")), # Optional extras, uncomment as needed: - # ca_file: “/path/to/ca-bundle.pem”, # custom CA(s) + # ca_file: "/path/to/ca-bundle.pem", # custom CA(s) # verify: true # enable server cert verification (recommended) }, }, -)

    +) -

    Example token request (any grant type can be used). The mTLS handshake

    -

    # will occur automatically on HTTPS calls using the configured cert/key. -access = client.client_credentials.get_token

    +# Example token request (any grant type can be used). The mTLS handshake +# will occur automatically on HTTPS calls using the configured cert/key. +access = client.client_credentials.get_token -

    Subsequent resource requests will also use mTLS on HTTPS endpoints of site:

    -

    resp = access.get(“/v1/protected”) -```

    +# Subsequent resource requests will also use mTLS on HTTPS endpoints of `site`: +resp = access.get("/v1/protected") +

    Notes:

    @@ -1316,25 +1284,23 @@

    Authentication schemes for the token request

    -

    ruby -OAuth2::Client.new( +

    OAuth2::Client.new(
       id,
       secret,
    -  site: "https://provider.example.com",
    +  site: "https://provider.example.com",
       auth_scheme: :basic_auth, # default. Alternatives: :request_body, :tls_client_auth, :private_key_jwt
     )
    -

    +

    Faraday connection, timeouts, proxy, custom adapter/middleware:

    -

    ruby -client = OAuth2::Client.new( +

    client = OAuth2::Client.new(
       id,
       secret,
    -  site: "https://provider.example.com",
    +  site: "https://provider.example.com",
       connection_opts: {
         request: {open_timeout: 5, timeout: 15},
    -    proxy: ENV["HTTPS_PROXY"],
    +    proxy: ENV["HTTPS_PROXY"],
         ssl: {verify: true},
       },
     ) do |faraday|
    @@ -1342,19 +1308,18 @@ 

    Faraday conn # faraday.response :logger, Logger.new($stdout) # see OAUTH_DEBUG below faraday.adapter(:net_http_persistent) # or any Faraday adapter you need end -

    +

    Using flat query params (Faraday::FlatParamsEncoder)

    Some APIs expect repeated key parameters to be sent as flat params rather than arrays. Faraday provides FlatParamsEncoder for this purpose. You can configure the oauth2 client to use it when building requests.

    -

    ```ruby -require “faraday”

    +
    require "faraday"
     
    -

    client = OAuth2::Client.new( +client = OAuth2::Client.new( id, secret, - site: “https://api.example.com”, + site: "https://api.example.com", # Pass Faraday connection options to make FlatParamsEncoder the default connection_opts: { request: {params_encoder: Faraday::FlatParamsEncoder}, @@ -1362,33 +1327,32 @@

    Using flat query param ) do |faraday| faraday.request(:url_encoded) faraday.adapter(:net_http) -end

    +end -

    access = client.client_credentials.get_token

    +access = client.client_credentials.get_token -

    Example of a GET with two flat filter params (not an array):

    -

    # Results in: ?filter=order.clientCreatedTime%3E1445006997000&filter=order.clientCreatedTime%3C1445611797000 +# Example of a GET with two flat filter params (not an array): +# Results in: ?filter=order.clientCreatedTime%3E1445006997000&filter=order.clientCreatedTime%3C1445611797000 resp = access.get( - “/v1/orders”, + "/v1/orders", params: { # Provide the values as an array; FlatParamsEncoder expands them as repeated keys filter: [ - “order.clientCreatedTime>1445006997000”, - “order.clientCreatedTime<1445611797000”, + "order.clientCreatedTime>1445006997000", + "order.clientCreatedTime<1445611797000", ], }, ) -```

    +

    If you instead need to build a raw Faraday connection yourself, the equivalent configuration is:

    -

    ruby -conn = Faraday.new("https://api.example.com", request: {params_encoder: Faraday::FlatParamsEncoder}) -

    +
    conn = Faraday.new("https://api.example.com", request: {params_encoder: Faraday::FlatParamsEncoder})
    +

    Redirection

    -

    The library follows up to max_redirects (default 5). +

    The library follows up to max_redirects (default 5).
    You can override per-client via options[:max_redirects].

    Handling Responses and Errors

    @@ -1397,52 +1361,48 @@

    Handling Responses and Errors

  • Parsing:
  • -

    ruby -resp = access.get("/v1/thing") +

    resp = access.get("/v1/thing")
     resp.status     # Integer
     resp.headers    # Hash
     resp.body       # String
     resp.parsed     # SnakyHash::StringKeyed or Array when JSON array
    -

    +
    • Error handling:
    -

    ruby -begin - access.get("/v1/forbidden") +

    begin
    +  access.get("/v1/forbidden")
     rescue OAuth2::Error => e
       e.code         # OAuth2 error code (when present)
       e.description  # OAuth2 error description (when present)
       e.response     # OAuth2::Response (full access to status/headers/body)
     end
    -

    +
    • Disable raising on 4xx/5xx to inspect the response yourself:
    -

    ruby -client = OAuth2::Client.new(id, secret, site: site, raise_errors: false) -res = client.request(:get, "/v1/maybe-errors") +

    client = OAuth2::Client.new(id, secret, site: site, raise_errors: false)
    +res = client.request(:get, "/v1/maybe-errors")
     if res.status == 429
    -  sleep res.headers["retry-after"].to_i
    +  sleep res.headers["retry-after"].to_i
     end
    -

    +

    Making Raw Token Requests

    If a provider requires non-standard parameters or headers, you can call client.get_token directly:

    -

    ruby -access = client.get_token({ - grant_type: "client_credentials", - audience: "https://api.example.com", - headers: {"X-Custom" => "value"}, +

    access = client.get_token({
    +  grant_type: "client_credentials",
    +  audience: "https://api.example.com",
    +  headers: {"X-Custom" => "value"},
       parse: :json, # override parsing
     })
    -

    +

    OpenID Connect (OIDC) Notes

    @@ -1461,23 +1421,23 @@

    Debugging


    -

    🦷 FLOSS Funding

    +

    🦷 FLOSS Funding

    -

    While ruby-oauth tools are free software and will always be, the project would benefit immensely from some funding. +

    While ruby-oauth tools are free software and will always be, the project would benefit immensely from some funding.
    Raising a monthly budget of… “dollars” would make the project more sustainable.

    -

    We welcome both individual and corporate sponsors! We also offer a -wide array of funding channels to account for your preferences +

    We welcome both individual and corporate sponsors! We also offer a
    +wide array of funding channels to account for your preferences
    (although currently Open Collective is our preferred funding platform).

    -

    If you’re working in a company that’s making significant use of ruby-oauth tools we’d +

    If you’re working in a company that’s making significant use of ruby-oauth tools we’d
    appreciate it if you suggest to your company to become a ruby-oauth sponsor.

    -

    You can support the development of ruby-oauth tools via -GitHub Sponsors, -Liberapay, -PayPal, -Open Collective +

    You can support the development of ruby-oauth tools via
    +GitHub Sponsors,
    +Liberapay,
    +PayPal,
    +Open Collective
    and Tidelift.

    @@ -1500,7 +1460,7 @@

    Open Collective for Individuals

    NOTE: kettle-readme-backers updates this list every day, automatically.

    -

    No backers yet. Be the first! +

    No backers yet. Be the first!

    Open Collective for Organizations

    @@ -1510,7 +1470,7 @@

    Open Collective for Organizations

    NOTE: kettle-readme-backers updates this list every day, automatically.

    -

    No sponsors yet. Be the first! +

    No sponsors yet. Be the first!

    Another way to support open-source

    @@ -1525,24 +1485,24 @@

    Another way to support open-sourceOpenCollective Backers OpenCollective Sponsors Sponsor Me on Github Liberapay Goal Progress Donate on PayPal Buy me a coffee Donate on Polar Donate to my FLOSS efforts at ko-fi.com Donate to my FLOSS efforts using Patreon

    -

    🔐 Security

    +

    🔐 Security

    -

    To report a security vulnerability, please use the Tidelift security contact. +

    To report a security vulnerability, please use the Tidelift security contact.
    Tidelift will coordinate the fix and disclosure.

    For more see SECURITY.md, THREAT_MODEL.md, and IRP.md.

    -

    🤝 Contributing

    +

    🤝 Contributing

    -

    If you need some ideas of where to help, you could work on adding more code coverage, -or if it is already 💯 (see below) check reek, issues, or PRs, +

    If you need some ideas of where to help, you could work on adding more code coverage,
    +or if it is already 💯 (see below) check reek, issues, or PRs,
    or use the gem and think about how it could be better.

    We Keep A Changelog so if you make changes, remember to update it.

    See CONTRIBUTING.md for more detailed instructions.

    -

    🚀 Release Instructions

    +

    🚀 Release Instructions

    See CONTRIBUTING.md.

    @@ -1554,12 +1514,12 @@

    Code Coverage

    QLTY Test Coverage

    -

    🪇 Code of Conduct

    +

    🪇 Code of Conduct

    -

    Everyone interacting with this project’s codebases, issue trackers, +

    Everyone interacting with this project’s codebases, issue trackers,
    chat rooms and mailing lists agrees to follow the Contributor Covenant 2.1.

    -

    🌈 Contributors

    +

    🌈 Contributors

    Contributors

    @@ -1580,29 +1540,28 @@

    🌈 Contributors

    -

    📌 Versioning

    +

    📌 Versioning

    -

    This Library adheres to Semantic Versioning 2.0.0. -Violations of this scheme should be reported as bugs. -Specifically, if a minor or patch version is released that breaks backward compatibility, -a new version should be immediately released that restores compatibility. +

    This Library adheres to Semantic Versioning 2.0.0.
    +Violations of this scheme should be reported as bugs.
    +Specifically, if a minor or patch version is released that breaks backward compatibility,
    +a new version should be immediately released that restores compatibility.
    Breaking changes to the public API will only be introduced with new major versions.

    -

    dropping support for a platform is both obviously and objectively a breaking change
    +

    dropping support for a platform is both obviously and objectively a breaking change

    —Jordan Harband (@ljharb, maintainer of SemVer) in SemVer issue 716

    -

    I understand that policy doesn’t work universally (“exceptions to every rule!”), -but it is the policy here. -As such, in many cases it is good to specify a dependency on this library using +

    I understand that policy doesn’t work universally (“exceptions to every rule!”),
    +but it is the policy here.
    +As such, in many cases it is good to specify a dependency on this library using
    the Pessimistic Version Constraint with two digits of precision.

    For example:

    -

    ruby -spec.add_dependency("oauth2", "~> 2.0") -

    +
    spec.add_dependency("oauth2", "~> 2.0")
    +
    📌 Is "Platform Support" part of the public API? More details inside. @@ -1621,13 +1580,13 @@

    📌 Versioning

    See CHANGELOG.md for a list of releases.

    -

    📄 License

    +

    📄 License

    -

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

    The gem is available as open source under the terms of
    +the MIT License License: MIT.
    See LICENSE.txt for the official Copyright Notice.

    - +
    • @@ -1644,13 +1603,13 @@
    -

    🤑 A request for help

    +

    🤑 A request for help

    -

    Maintainers have teeth and need to pay their dentists. -After getting laid off in an RIF in March, and encountering difficulty finding a new one, -I began spending most of my time building open source tools. -I’m hoping to be able to pay for my kids’ health insurance this month, -so if you value the work I am doing, I need your support. +

    Maintainers have teeth and need to pay their dentists.
    +After getting laid off in an RIF in March, and encountering difficulty finding a new one,
    +I began spending most of my time building open source tools.
    +I’m hoping to be able to pay for my kids’ health insurance this month,
    +so if you value the work I am doing, I need your support.
    Please consider sponsoring me or the project.

    To join the community or get help 👇️ Join the Discord.

    @@ -1677,7 +1636,7 @@

    Please give the project a star ⭐ ♥ diff --git a/docs/top-level-namespace.html b/docs/top-level-namespace.html index 6dacf828..a8f44a8d 100644 --- a/docs/top-level-namespace.html +++ b/docs/top-level-namespace.html @@ -100,7 +100,7 @@

    Defined Under Namespace