From e07c1fa904a439f745467b0fb8258791ad1edfda Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Sat, 4 Oct 2025 01:02:08 -0600 Subject: [PATCH 01/14] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5f2a158a..fa30a8d1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -33,7 +33,7 @@ GEM rake (>= 10) thor (>= 0.14) ast (2.4.3) - backports (3.25.1) + backports (3.25.2) base64 (0.3.0) benchmark (0.4.1) bigdecimal (3.2.3) @@ -82,7 +82,7 @@ GEM dry-logic (~> 1.4) zeitwerk (~> 2.6) erb (5.0.2) - faraday (2.13.4) + faraday (2.14.0) faraday-net_http (>= 2.0, < 3.5) json logger @@ -100,7 +100,7 @@ GEM pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.13.2) + json (2.15.0) jwt (3.1.2) base64 kettle-dev (1.1.31) @@ -204,7 +204,7 @@ GEM rubocop-ast (>= 1.46.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.46.0) + rubocop-ast (1.47.1) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-gradual (0.3.6) From ca63d419d7778691b8389ce9d23103493f13ce15 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Thu, 6 Nov 2025 21:02:34 -0700 Subject: [PATCH 02/14] =?UTF-8?q?=F0=9F=90=9B=20Fixing=20markdown=20=3D>?= =?UTF-8?q?=20HTML=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .tool-versions | 2 +- .yard_gfm_support.rb | 20 ++++++++++++++++---- docs/.nojekyll | 0 docs/CNAME | 1 - test_gfm.rb | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 6 deletions(-) delete mode 100644 docs/.nojekyll delete mode 100644 docs/CNAME create mode 100644 test_gfm.rb diff --git a/.tool-versions b/.tool-versions index d1de26c4..048c75ee 100755 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ direnv 2.32.2 -ruby 3.4.5 +ruby 3.4.7 diff --git a/.yard_gfm_support.rb b/.yard_gfm_support.rb index 4f2f1403..3c8a6b0e 100644 --- a/.yard_gfm_support.rb +++ b/.yard_gfm_support.rb @@ -11,12 +11,24 @@ def initialize(source, options = {}) end end +# Ensure YARD is loaded before modifying its constants +require 'yard' unless defined?(YARD) + # Insert the new provider as the highest priority option for Markdown. # See: # - https://github.com/lsegal/yard/issues/1157 # - https://github.com/lsegal/yard/issues/1017 # - https://github.com/lsegal/yard/blob/main/lib/yard/templates/helpers/markup_helper.rb -YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown].insert( - 0, - {const: "KramdownGfmDocument"}, -) +require 'yard/templates/helpers/markup_helper' + +providers = YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown] +providers.unshift({lib: :kramdown, const: :KramdownGfmDocument}) + +# Normalize provider entries to what YARD expects (const must be a String) +providers.each do |provider| + const = provider[:const] + provider[:const] = const.to_s if const.is_a?(Symbol) +end + +# De-duplicate entries by [lib, const] +providers.uniq! { |p| [p[:lib], p[:const].to_s] } diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index 9e32e7bf..00000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -oauth2.galtzo.com \ No newline at end of file diff --git a/test_gfm.rb b/test_gfm.rb new file mode 100644 index 00000000..75c1dc67 --- /dev/null +++ b/test_gfm.rb @@ -0,0 +1,32 @@ +#!/usr/bin/env ruby +require 'bundler/setup' +require 'yard' +require 'yard/templates/helpers/markup_helper' + +puts "Before loading .yard_gfm_support.rb:" +YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown].each_with_index do |p, i| + puts " [#{i}] #{p.inspect}" +end + +require './.yard_gfm_support.rb' + +puts "\nAfter loading .yard_gfm_support.rb:" +YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown].each_with_index do |p, i| + puts " [#{i}] #{p.inspect}" +end + +puts "\nTesting KramdownGfmDocument:" + +test_md = <<-MD + # Test + + ```ruby + puts "hello" + ``` +MD + +doc = KramdownGfmDocument.new(test_md) +html = doc.to_html +puts html +puts "\nDoes output contain
? #{html.include?('
')}"
+puts "Does output contain ? #{html.include?('
Date: Fri, 7 Nov 2025 16:59:58 -0700
Subject: [PATCH 03/14] =?UTF-8?q?=F0=9F=93=9D=20Code=20fences?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CHANGELOG.md                     |  2 +-
 README.md                        | 14 +++++++-------
 lib/oauth2/access_token.rb       |  2 +-
 lib/oauth2/strategy/assertion.rb |  4 ++--
 4 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 044437fe..607363f7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,7 +49,7 @@ Please file a bug if you notice a violation of semantic versioning.
 
 ### Added
 
-- [gh!682][gh!682] - AccessToken: support Hash-based verb-dependent token transmission mode (e.g., {get: :query, post: :header})
+- [gh!682][gh!682] - AccessToken: support Hash-based verb-dependent token transmission mode (e.g., `{get: :query, post: :header}`)
 
 [gh!682]: https://github.com/ruby-oauth/oauth2/pull/682
 
diff --git a/README.md b/README.md
index 190f2420..74069300 100644
--- a/README.md
+++ b/README.md
@@ -1107,16 +1107,16 @@ resp = access.get("/v1/protected")
 ```
 
 Notes:
-- Files must contain the appropriate PEMs. The private key may be encrypted; if so, pass a password to OpenSSL::PKey::RSA.new(File.read(path), ENV["KEY_PASSWORD"]).
+- Files must contain the appropriate PEMs. The private key may be encrypted; if so, pass a password to `OpenSSL::PKey::RSA.new(File.read(path), ENV["KEY_PASSWORD"])`.
 - If your certificate and key are in a PKCS#12/PFX bundle, you can load them like:
-  - p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"])
-  - client_cert = p12.certificate; client_key = p12.key
+  - `p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"])`
+  - `client_cert = p12.certificate; client_key = p12.key`
 - Server trust:
-  - If your environment does not have system CAs, specify ca_file or ca_path inside the ssl: hash.
-  - Keep verify: true in production. Set verify: false only for local testing.
-- Faraday adapter: Any adapter that supports Ruby’s OpenSSL should work. net_http (default) and net_http_persistent are common choices.
+  - If your environment does not have system CAs, specify `ca_file` or `ca_path` inside the `ssl:` hash.
+  - Keep `verify: true` in production. Set `verify: false` only for local testing.
+- Faraday adapter: Any adapter that supports Ruby’s OpenSSL should work. `net_http` (default) and `net_http_persistent` are common choices.
 - Scope of mTLS: The SSL client cert is applied to any HTTPS request made by this client (token and resource requests) to the configured site base URL (and absolute URLs you call with the same client).
-- OIDC tie-in: Some OPs require tls_client_auth at the token endpoint per OIDC/OAuth specifications. That is enabled via auth_scheme: :tls_client_auth as shown above.
+- OIDC tie-in: Some OPs require tls_client_auth at the token endpoint per OIDC/OAuth specifications. That is enabled via `auth_scheme: :tls_client_auth` as shown above.
 
 #### Authentication schemes for the token request
 
diff --git a/lib/oauth2/access_token.rb b/lib/oauth2/access_token.rb
index c428c019..8c68e321 100644
--- a/lib/oauth2/access_token.rb
+++ b/lib/oauth2/access_token.rb
@@ -134,7 +134,7 @@ def no_tokens_warning(hash, key)
     # @option opts [FixNum, String] :expires_latency (nil) the number of seconds by which AccessToken validity will be reduced to offset latency, @version 2.0+
     # @option opts [Symbol, Hash, or callable] :mode (:header) 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
+    #    (e.g., `{get: :query, post: :header, delete: :header}`); or a callable that accepts a request-verb parameter
     #    and returns one of these three symbols.
     # @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
     #
diff --git a/lib/oauth2/strategy/assertion.rb b/lib/oauth2/strategy/assertion.rb
index 6396fd6d..0515b26b 100644
--- a/lib/oauth2/strategy/assertion.rb
+++ b/lib/oauth2/strategy/assertion.rb
@@ -66,8 +66,8 @@ def authorize_url
       #   @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'})
-      #   get_token(claim_set, {:algorithm => 'RS256', :key => OpenSSL::PKCS12.new(File.read('my_key.p12'), 'not_secret')})
+      #   `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')})`
       #
       # @param [Hash] request_opts options that will be used to assemble the request
       # @option request_opts [String] :scope the url parameter `scope` that may be required by some endpoints

From 3ae1da5f71c46aed47dda4d310fc9b1298f394da Mon Sep 17 00:00:00 2001
From: "Peter H. Boling" 
Date: Fri, 7 Nov 2025 17:04:12 -0700
Subject: [PATCH 04/14] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20deps?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Gemfile.lock | 60 ++++++++++++++++++++++++++++------------------------
 1 file changed, 32 insertions(+), 28 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index fa30a8d1..9961d0f9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -35,16 +35,16 @@ GEM
     ast (2.4.3)
     backports (3.25.2)
     base64 (0.3.0)
-    benchmark (0.4.1)
-    bigdecimal (3.2.3)
+    benchmark (0.5.0)
+    bigdecimal (3.3.1)
     bundler-audit (0.9.2)
       bundler (>= 1.2.0, < 3)
       thor (~> 1.0)
     concurrent-ruby (1.3.5)
-    crack (1.0.0)
+    crack (1.0.1)
       bigdecimal
       rexml
-    date (3.4.1)
+    date (3.5.0)
     debug (1.11.0)
       irb (~> 1.10)
       reline (>= 0.3.8)
@@ -81,13 +81,13 @@ GEM
       dry-inflector (~> 1.0)
       dry-logic (~> 1.4)
       zeitwerk (~> 2.6)
-    erb (5.0.2)
+    erb (5.1.3)
     faraday (2.14.0)
       faraday-net_http (>= 2.0, < 3.5)
       json
       logger
-    faraday-net_http (3.4.1)
-      net-http (>= 0.5.0)
+    faraday-net_http (3.4.2)
+      net-http (~> 0.5)
     gem_bench (2.0.5)
       bundler (>= 1.14)
       version_gem (~> 1.1, >= 1.1.4)
@@ -96,14 +96,14 @@ GEM
     hashdiff (1.2.1)
     hashie (5.0.0)
     io-console (0.8.1)
-    irb (1.15.2)
+    irb (1.15.3)
       pp (>= 0.6.0)
       rdoc (>= 4.0.0)
       reline (>= 0.4.2)
-    json (2.15.0)
+    json (2.16.0)
     jwt (3.1.2)
       base64
-    kettle-dev (1.1.31)
+    kettle-dev (1.1.49)
     kettle-soup-cover (1.0.10)
       simplecov (~> 0.22)
       simplecov-cobertura (~> 3.0)
@@ -113,15 +113,17 @@ GEM
       simplecov-rcov (~> 0.3, >= 0.3.7)
       simplecov_json_formatter (~> 0.1, >= 0.1.4)
       version_gem (~> 1.1, >= 1.1.8)
-    kettle-test (1.0.3)
+    kettle-test (1.0.6)
       appraisal2 (~> 3.0)
+      backports (~> 3.0)
       rspec (~> 3.0)
       rspec-block_is_expected (~> 1.0, >= 1.0.6)
+      rspec-pending_for (~> 0.1, >= 0.1.19)
       rspec-stubbed_env (~> 1.0, >= 1.0.4)
       rspec_junit_formatter (~> 0.6)
       silent_stream (~> 1.0, >= 1.0.12)
       timecop-rspec (~> 1.0, >= 1.0.3)
-      version_gem (~> 1.1, >= 1.1.8)
+      version_gem (~> 1.1, >= 1.1.9)
     kramdown (2.5.1)
       rexml (>= 3.3.9)
     kramdown-parser-gfm (1.1.0)
@@ -132,33 +134,34 @@ GEM
     multi_xml (0.7.2)
       bigdecimal (~> 3.1)
     mutex_m (0.3.0)
-    net-http (0.6.0)
+    net-http (0.7.0)
       uri
     nkf (0.2.0)
     nokogiri (1.18.10-x86_64-linux-gnu)
       racc (~> 1.4)
     ostruct (0.6.3)
     parallel (1.27.0)
-    parser (3.3.9.0)
+    parser (3.3.10.0)
       ast (~> 2.4.1)
       racc
-    pp (0.6.2)
+    pp (0.6.3)
       prettyprint
     prettyprint (0.2.0)
-    prism (1.5.1)
+    prism (1.6.0)
     psych (5.2.6)
       date
       stringio
     public_suffix (6.0.2)
     racc (1.8.1)
-    rack (3.2.1)
+    rack (3.2.4)
     rainbow (3.1.1)
-    rake (13.3.0)
+    rake (13.3.1)
     rbs (3.9.5)
       logger
-    rdoc (6.14.2)
+    rdoc (6.15.1)
       erb
       psych (>= 4.0.0)
+      tsort
     reek (6.5.0)
       dry-schema (~> 1.13)
       logger (~> 1.6)
@@ -171,17 +174,17 @@ GEM
     require_bench (1.0.4)
       version_gem (>= 1.1.3, < 4)
     rexml (3.4.4)
-    rspec (3.13.1)
+    rspec (3.13.2)
       rspec-core (~> 3.13.0)
       rspec-expectations (~> 3.13.0)
       rspec-mocks (~> 3.13.0)
     rspec-block_is_expected (1.0.6)
-    rspec-core (3.13.5)
+    rspec-core (3.13.6)
       rspec-support (~> 3.13.0)
     rspec-expectations (3.13.5)
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.13.0)
-    rspec-mocks (3.13.5)
+    rspec-mocks (3.13.7)
       diff-lcs (>= 1.2.0, < 2.0)
       rspec-support (~> 3.13.0)
     rspec-pending_for (0.1.19)
@@ -245,7 +248,7 @@ GEM
       rubocop-thread_safety (~> 0.5, >= 0.5.1)
       standard-rubocop-lts (~> 1.0, >= 1.0.7)
       version_gem (>= 1.1.3, < 3)
-    rubocop-shopify (2.17.1)
+    rubocop-shopify (2.18.0)
       rubocop (~> 1.62)
     rubocop-thread_safety (0.7.3)
       lint_roller (~> 1.1)
@@ -294,8 +297,8 @@ GEM
       standard-custom (>= 1.0.2, < 2)
       standard-performance (>= 1.3.1, < 2)
       version_gem (>= 1.1.4, < 3)
-    stone_checksums (1.0.2)
-      version_gem (~> 1.1, >= 1.1.8)
+    stone_checksums (1.0.3)
+      version_gem (~> 1.1, >= 1.1.9)
     stringio (3.1.7)
     terminal-table (4.0.0)
       unicode-display_width (>= 1.1.1, < 4)
@@ -305,14 +308,15 @@ GEM
       delegate (~> 0.1)
       rspec (~> 3.0)
       timecop (>= 0.7, < 1)
+    tsort (0.2.0)
     unicode-display_width (3.2.0)
       unicode-emoji (~> 4.1)
     unicode-emoji (4.1.0)
-    uri (1.0.3)
+    uri (1.1.1)
     vcr (6.3.1)
       base64
     version_gem (1.1.9)
-    webmock (3.25.1)
+    webmock (3.26.1)
       addressable (>= 2.8.0)
       crack (>= 0.3.2)
       hashdiff (>= 0.4.0, < 2.0.0)
@@ -320,7 +324,7 @@ GEM
     yard-relative_markdown_links (0.5.0)
       nokogiri (>= 1.14.3, < 2)
     zeitwerk (2.7.3)
-    zlib (3.2.1)
+    zlib (3.2.2)
 
 PLATFORMS
   x86_64-linux

From 23f9af35cb0dc2d5f12a76adef7ce12ca131e583 Mon Sep 17 00:00:00 2001
From: "Peter H. Boling" 
Date: Fri, 7 Nov 2025 17:04:16 -0700
Subject: [PATCH 05/14] =?UTF-8?q?=F0=9F=8E=A8=20Template=20bootstrap=20by?=
 =?UTF-8?q?=20kettle-dev-setup=20v1.1.49?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .git-hooks/commit-msg                         |  56 +--
 .git-hooks/footer-template.erb.txt            |   2 +-
 .git-hooks/prepare-commit-msg                 |  17 +-
 .github/workflows/current.yml                 |  40 +-
 .github/workflows/dep-heads.yml               |  40 +-
 .github/workflows/heads.yml                   |  36 +-
 .github/workflows/legacy.yml                  |   4 +-
 .github/workflows/style.yml                   |   2 +
 .github/workflows/truffle.yml                 |  12 +-
 .gitignore                                    |   1 +
 .junie/guidelines.md                          |  13 +-
 .yard_gfm_support.rb                          |  34 --
 .yardopts                                     |   9 +-
 Appraisal.root.gemfile                        |   2 +-
 Appraisals                                    | 119 +++---
 CONTRIBUTING.md                               |  35 +-
 FUNDING.md                                    |  17 +-
 Gemfile                                       |   4 +-
 Gemfile.lock                                  |  26 +-
 README.md                                     | 364 +++++-------------
 Rakefile                                      |   2 +-
 gemfiles/modular/documentation.gemfile        |   1 +
 gemfiles/modular/erb/r2.3/default.gemfile     |   2 +-
 gemfiles/modular/optional.gemfile             |   1 +
 gemfiles/modular/x_std_libs/r2.4/libs.gemfile |   2 +-
 oauth2.gemspec                                |  24 +-
 test_gfm.rb                                   |  32 --
 27 files changed, 351 insertions(+), 546 deletions(-)
 delete mode 100644 .yard_gfm_support.rb
 delete mode 100644 test_gfm.rb

diff --git a/.git-hooks/commit-msg b/.git-hooks/commit-msg
index 750c5bb1..5d160e67 100755
--- a/.git-hooks/commit-msg
+++ b/.git-hooks/commit-msg
@@ -16,33 +16,39 @@ begin
   # Is the first character a GitMoji?
   gitmoji_index = full_text =~ Gitmoji::Regex::REGEX
   if gitmoji_index == 0
-    exit 0
+    exit(0)
   else
-    denied = < e
-  warn("gitmoji-regex gem not found: #{e.class}: #{e.message}.\n\tSkipping gitmoji check and allowing commit to proceed.\n\tRecommendation: add 'gitmoji-regex' to your development dependencies to enable this check.")
-  exit 0
+  failure = <<~EOM
+    gitmoji-regex gem not found: #{e.class}: #{e.message}.
+      Skipping gitmoji check and allowing commit to proceed.
+      Recommendation: add 'gitmoji-regex' to your development dependencies to enable this check.
+    
+  EOM
+  warn(failure)
+  exit(0)
 end
diff --git a/.git-hooks/footer-template.erb.txt b/.git-hooks/footer-template.erb.txt
index d732d699..36cdb0ad 100644
--- a/.git-hooks/footer-template.erb.txt
+++ b/.git-hooks/footer-template.erb.txt
@@ -1,5 +1,5 @@
 ⚡️ A message from a fellow meat-based-AI ⚡️
-- [❤️] Finely-crafted open-source tools like <%= @gem_name %> (& many more) are a full-time endeavor.
+- [❤️] Finely-crafted open-source tools like <%= @gem_name %> (& many more) require time and effort.
 - [❤️] Though I adore my work, it lacks financial sustainability.
 - [❤️] Please, help me continue enhancing your tools by becoming a sponsor:
   - [💲] https://liberapay.com/pboling/donate
diff --git a/.git-hooks/prepare-commit-msg b/.git-hooks/prepare-commit-msg
index c6a15570..dbc30589 100755
--- a/.git-hooks/prepare-commit-msg
+++ b/.git-hooks/prepare-commit-msg
@@ -3,17 +3,6 @@
 # Fail on error and unset variables
 set -eu
 
-# Determine project root as the parent directory of this hook script
-PROJECT_ROOT="$(CDPATH= cd -- "$(dirname -- "$0")"/.. && pwd)"
-
-# Run the Ruby hook within the direnv context (if available),
-# so ENV from .envrc/.env.local at project root is loaded.
-# One of the things .envrc needs to do is add $PROJECT_ROOT/bin/ to the path.
-# You should have this line at the top of .envrc
-#   PATH_add bin
-# NOTE: If this project ships exe scripts it should also add that.
-if command -v direnv >/dev/null 2>&1; then
-  exec direnv exec "$PROJECT_ROOT" "kettle-commit-msg" "$@"
-else
-  raise "direnv not found. Local development of this project ($PROJECT_ROOT) with tools from the kettle-dev gem may not work properly. Please run 'brew install direnv'."
-fi
+# We are not using direnv exec here because mise and direnv can result in conflicting PATH settings:
+# See: https://mise.jdx.dev/direnv.html
+exec "kettle-commit-msg" "$@"
diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml
index f8a9e644..7dab87e0 100644
--- a/.github/workflows/current.yml
+++ b/.github/workflows/current.yml
@@ -63,11 +63,11 @@ jobs:
 
     steps:
       - name: Checkout
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         uses: actions/checkout@v5
 
       - name: Setup Ruby & RubyGems
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         uses: ruby/setup-ruby@v1
         with:
           ruby-version: ${{ matrix.ruby }}
@@ -79,11 +79,37 @@ jobs:
       # We need to do this first to get appraisal installed.
       # NOTE: This does not use the primary Gemfile at all.
       - name: Install Root Appraisal
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle
-      - name: Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+
+      - name: "[Attempt 1] Install Root Appraisal"
+        id: bundleAttempt1
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
+        run: bundle
+        # Continue to the next step on failure
+        continue-on-error: true
+
+      # Effectively an automatic retry of the previous step.
+      - name: "[Attempt 2] Install Root Appraisal"
+        id: bundleAttempt2
+        # If bundleAttempt1 failed, try again here; Otherwise skip.
+        if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
+        run: bundle
+
+      - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
+        id: bundleAppraisalAttempt1
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
+        run: bundle exec appraisal ${{ matrix.appraisal }} bundle
+        # Continue to the next step on failure
+        continue-on-error: true
+
+      # Effectively an automatic retry of the previous step.
+      - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
+        id: bundleAppraisalAttempt2
+        # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
+        if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle
-      - name: Tests for ${{ matrix.ruby }}@${{ matrix.appraisal }} via ${{ matrix.exec_cmd }}
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+
+      - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
diff --git a/.github/workflows/dep-heads.yml b/.github/workflows/dep-heads.yml
index f6915689..a3d03f5f 100644
--- a/.github/workflows/dep-heads.yml
+++ b/.github/workflows/dep-heads.yml
@@ -47,9 +47,7 @@ jobs:
             rubygems: latest
             bundler: latest
 
-          # truffleruby-24.1
-          #   (according to documentation: targets Ruby 3.3 compatibility)
-          #   (according to runtime: targets Ruby 3.2 compatibility)
+          # truffleruby-24.1 (targets Ruby 3.3 compatibility)
           - ruby: "truffleruby"
             appraisal: "dep-heads"
             exec_cmd: "rake test"
@@ -67,11 +65,11 @@ jobs:
 
     steps:
       - name: Checkout
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         uses: actions/checkout@v5
 
       - name: Setup Ruby & RubyGems
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         uses: ruby/setup-ruby@v1
         with:
           ruby-version: ${{ matrix.ruby }}
@@ -82,24 +80,38 @@ jobs:
       # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root)
       # We need to do this first to get appraisal installed.
       # NOTE: This does not use the primary Gemfile at all.
-      - name: "Install Root Appraisal"
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+      - name: Install Root Appraisal
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle
 
-      - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+      - name: "[Attempt 1] Install Root Appraisal"
         id: bundleAttempt1
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
+        run: bundle
+        # Continue to the next step on failure
+        continue-on-error: true
+
+      # Effectively an automatic retry of the previous step.
+      - name: "[Attempt 2] Install Root Appraisal"
+        id: bundleAttempt2
+        # If bundleAttempt1 failed, try again here; Otherwise skip.
+        if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
+        run: bundle
+
+      - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
+        id: bundleAppraisalAttempt1
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle
         # Continue to the next step on failure
         continue-on-error: true
 
       # Effectively an automatic retry of the previous step.
       - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
-        # If bundleAttempt1 failed, try again here; Otherwise skip.
-        if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
-        id: bundleAttempt2
+        id: bundleAppraisalAttempt2
+        # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
+        if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle
 
-      - name: Tests for ${{ matrix.ruby }}@${{ matrix.appraisal }} via ${{ matrix.exec_cmd }}
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+      - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
diff --git a/.github/workflows/heads.yml b/.github/workflows/heads.yml
index f8c92d16..104f1a7a 100644
--- a/.github/workflows/heads.yml
+++ b/.github/workflows/heads.yml
@@ -64,11 +64,11 @@ jobs:
 
     steps:
       - name: Checkout
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         uses: actions/checkout@v5
 
       - name: Setup Ruby & RubyGems
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         uses: ruby/setup-ruby@v1
         with:
           ruby-version: ${{ matrix.ruby }}
@@ -79,24 +79,38 @@ jobs:
       # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root)
       # We need to do this first to get appraisal installed.
       # NOTE: This does not use the primary Gemfile at all.
-      - name: "Install Root Appraisal"
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+      - name: Install Root Appraisal
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle
 
-      - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+      - name: "[Attempt 1] Install Root Appraisal"
         id: bundleAttempt1
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
+        run: bundle
+        # Continue to the next step on failure
+        continue-on-error: true
+
+      # Effectively an automatic retry of the previous step.
+      - name: "[Attempt 2] Install Root Appraisal"
+        id: bundleAttempt2
+        # If bundleAttempt1 failed, try again here; Otherwise skip.
+        if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
+        run: bundle
+
+      - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
+        id: bundleAppraisalAttempt1
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle
         # Continue to the next step on failure
         continue-on-error: true
 
       # Effectively an automatic retry of the previous step.
       - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
-        # If bundleAttempt1 failed, try again here; Otherwise skip.
-        if: ${{ steps.bundleAttempt1.outcome == 'failure' && !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
-        id: bundleAttempt2
+        id: bundleAppraisalAttempt2
+        # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
+        if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle
 
-      - name: Tests for ${{ matrix.ruby }}@${{ matrix.appraisal }} via ${{ matrix.exec_cmd }}
-        if: ${{ !(env.ACT && startsWith(matrix.ruby, 'jruby')) }}
+      - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
diff --git a/.github/workflows/legacy.yml b/.github/workflows/legacy.yml
index 7f1fc299..f7853f62 100644
--- a/.github/workflows/legacy.yml
+++ b/.github/workflows/legacy.yml
@@ -50,8 +50,8 @@ jobs:
             appraisal: "ruby-3-1"
             exec_cmd: "rake test"
             gemfile: "Appraisal.root"
-            rubygems: latest
-            bundler: latest
+            rubygems: '3.6.9'
+            bundler: '2.6.9'
 
     steps:
       - name: Checkout
diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml
index 2fe1e03c..4fbef86e 100644
--- a/.github/workflows/style.yml
+++ b/.github/workflows/style.yml
@@ -63,3 +63,5 @@ jobs:
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle
       - name: Run ${{ matrix.appraisal }} checks via ${{ matrix.exec_cmd }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
+      - name: Validate RBS Types
+        run: bundle exec appraisal ${{ matrix.appraisal }} bin/rbs validate
diff --git a/.github/workflows/truffle.yml b/.github/workflows/truffle.yml
index db651885..67807231 100644
--- a/.github/workflows/truffle.yml
+++ b/.github/workflows/truffle.yml
@@ -47,9 +47,11 @@ jobs:
 
     steps:
       - name: Checkout
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         uses: actions/checkout@v5
 
       - name: Setup Ruby & RubyGems
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         uses: ruby/setup-ruby@v1
         with:
           ruby-version: ${{ matrix.ruby }}
@@ -61,10 +63,12 @@ jobs:
       # We need to do this first to get appraisal installed.
       # NOTE: This does not use the primary Gemfile at all.
       - name: Install Root Appraisal
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle
 
       - name: "[Attempt 1] Install Root Appraisal"
         id: bundleAttempt1
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle
         # Continue to the next step on failure
         continue-on-error: true
@@ -73,11 +77,12 @@ jobs:
       - name: "[Attempt 2] Install Root Appraisal"
         id: bundleAttempt2
         # If bundleAttempt1 failed, try again here; Otherwise skip.
-        if: steps.bundleAttempt1.outcome == 'failure'
+        if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle
 
       - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
         id: bundleAppraisalAttempt1
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle
         # Continue to the next step on failure
         continue-on-error: true
@@ -85,9 +90,10 @@ jobs:
       # Effectively an automatic retry of the previous step.
       - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
         id: bundleAppraisalAttempt2
-        # If bundleAttempt1 failed, try again here; Otherwise skip.
-        if: steps.bundleAppraisalAttempt1.outcome == 'failure'
+        # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip.
+        if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle
 
       - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
+        if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }}
         run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }}
diff --git a/.gitignore b/.gitignore
index 15f1f941..068190dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
 *.gem
 
 # Bundler
+/vendor/bundle/
 /.bundle/
 /gemfiles/*.lock
 /gemfiles/.bundle/
diff --git a/.junie/guidelines.md b/.junie/guidelines.md
index 152e080b..42844a0a 100644
--- a/.junie/guidelines.md
+++ b/.junie/guidelines.md
@@ -47,9 +47,7 @@ This document captures project-specific knowledge to streamline setup, testing,
   - RSpec 3.13 with custom spec/spec_helper.rb configuration:
     - silent_stream: STDOUT is silenced by default for examples to keep logs clean.
       - To explicitly test console output, tag the example or group with :check_output.
-    - Global state hygiene: Around each example, FlossFunding.namespaces and FlossFunding.silenced are snapshotted and restored to prevent cross-test pollution.
     - DEBUG toggle: Set DEBUG=true to require 'debug' and avoid silencing output during your run.
-    - ENV seeding: The suite sets ENV["FLOSS_FUNDING_FLOSS_FUNDING"] = "Free-as-in-beer" so that the library’s own namespace is considered activated (avoids noisy warnings).
     - Coverage: kettle-soup-cover integrates SimpleCov; .simplecov is invoked from spec_helper when enabled by Kettle::Soup::Cover::DO_COV, which is controlled by K_SOUP_COV_DO being set to true / false.
     - RSpec.describe usage:
       - Use `describe "#"` to contain a block of specs that test instance method behavior.
@@ -73,10 +71,11 @@ This document captures project-specific knowledge to streamline setup, testing,
   - Output visibility
     - To see STDOUT from the code under test, use the :check_output tag on the example or group.
       Example:
-      RSpec.describe "output", :check_output do
-        it "prints" do
-          puts "This output should be visible"
-          expect(true).to be true
+      RSpec.describe "with output", :check_output do
+        it "has output" do
+          output = capture(:stderr) {kernel.warn("This is a warning")}
+          logs = [ "This is a warning\n" ]
+          expect(output).to(include(*logs))
         end
       end
     - Alternatively, run with DEBUG=true to disable silencing for the entire run.
@@ -94,7 +93,9 @@ This document captures project-specific knowledge to streamline setup, testing,
       include_context 'with stubbed env'
     - in a before hook, or in an example:
       stub_env("FLOSS_FUNDING_MY_NS" => "Free-as-in-beer")
+
       # example code continues
+
   - If your spec needs to assert on console output, tag it with :check_output. By default, STDOUT is silenced.
   - Use Timecop for deterministic time-sensitive behavior as needed (require config/timecop is already done by spec_helper).
 
diff --git a/.yard_gfm_support.rb b/.yard_gfm_support.rb
deleted file mode 100644
index 3c8a6b0e..00000000
--- a/.yard_gfm_support.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# Gratefully and liberally taken from the MIT-licensed https://github.com/bensheldon/good_job/pull/113/files
-require "kramdown"
-require "kramdown-parser-gfm"
-
-# Custom markup provider class that always renders Kramdown using GFM (Github Flavored Markdown).
-# GFM is needed to render markdown tables and fenced code blocks in the README.
-class KramdownGfmDocument < Kramdown::Document
-  def initialize(source, options = {})
-    options[:input] = "GFM" unless options.key?(:input)
-    super(source, options)
-  end
-end
-
-# Ensure YARD is loaded before modifying its constants
-require 'yard' unless defined?(YARD)
-
-# Insert the new provider as the highest priority option for Markdown.
-# See:
-# - https://github.com/lsegal/yard/issues/1157
-# - https://github.com/lsegal/yard/issues/1017
-# - https://github.com/lsegal/yard/blob/main/lib/yard/templates/helpers/markup_helper.rb
-require 'yard/templates/helpers/markup_helper'
-
-providers = YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown]
-providers.unshift({lib: :kramdown, const: :KramdownGfmDocument})
-
-# Normalize provider entries to what YARD expects (const must be a String)
-providers.each do |provider|
-  const = provider[:const]
-  provider[:const] = const.to_s if const.is_a?(Symbol)
-end
-
-# De-duplicate entries by [lib, const]
-providers.uniq! { |p| [p[:lib], p[:const].to_s] }
diff --git a/.yardopts b/.yardopts
index 479134df..ab259161 100644
--- a/.yardopts
+++ b/.yardopts
@@ -1,11 +1,12 @@
+--plugin fence
+-e yard/fence/hoist.rb
 --plugin junk
 --plugin relative_markdown_links
---readme README.md
+--readme tmp/yard-fence/README.md
 --charset utf-8
 --markup markdown
 --output docs
---load .yard_gfm_support.rb
 'lib/**/*.rb'
 -
-'*.md'
-'*.txt'
\ No newline at end of file
+'tmp/yard-fence/*.md'
+'tmp/yard-fence/*.txt'
diff --git a/Appraisal.root.gemfile b/Appraisal.root.gemfile
index 02afd183..a0001cd0 100644
--- a/Appraisal.root.gemfile
+++ b/Appraisal.root.gemfile
@@ -2,7 +2,7 @@
 
 git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
 
-source "https://rubygems.org"
+source "https://gem.coop"
 
 # Appraisal Root Gemfile is for running appraisal to generate the Appraisal Gemfiles
 #   in gemfiles/*gemfile.
diff --git a/Appraisals b/Appraisals
index 90e5effc..e1a741b5 100644
--- a/Appraisals
+++ b/Appraisals
@@ -47,65 +47,6 @@ appraise "dep-heads" do
   eval_gemfile "modular/runtime_heads.gemfile"
 end
 
-appraise "ruby-2-3-hashie_v0" do
-  eval_gemfile "modular/faraday_v0.gemfile"
-  eval_gemfile "modular/hashie_v0.gemfile"
-  eval_gemfile "modular/jwt_v1.gemfile"
-  eval_gemfile "modular/logger_v1_2.gemfile"
-  eval_gemfile "modular/multi_xml_v0_5.gemfile"
-  eval_gemfile "modular/rack_v1_2.gemfile"
-  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
-end
-
-appraise "ruby-2-3-hashie_v1" do
-  eval_gemfile "modular/faraday_v0.gemfile"
-  eval_gemfile "modular/hashie_v1.gemfile"
-  eval_gemfile "modular/jwt_v1.gemfile"
-  eval_gemfile "modular/logger_v1_2.gemfile"
-  eval_gemfile "modular/multi_xml_v0_5.gemfile"
-  eval_gemfile "modular/rack_v1_2.gemfile"
-  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
-end
-
-appraise "ruby-2-3-hashie_v2" do
-  eval_gemfile "modular/faraday_v0.gemfile"
-  eval_gemfile "modular/hashie_v2.gemfile"
-  eval_gemfile "modular/jwt_v1.gemfile"
-  eval_gemfile "modular/logger_v1_2.gemfile"
-  eval_gemfile "modular/multi_xml_v0_5.gemfile"
-  eval_gemfile "modular/rack_v1_2.gemfile"
-  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
-end
-
-appraise "ruby-2-3-hashie_v3" do
-  eval_gemfile "modular/faraday_v0.gemfile"
-  eval_gemfile "modular/hashie_v3.gemfile"
-  eval_gemfile "modular/jwt_v1.gemfile"
-  eval_gemfile "modular/logger_v1_2.gemfile"
-  eval_gemfile "modular/multi_xml_v0_5.gemfile"
-  eval_gemfile "modular/rack_v1_2.gemfile"
-  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
-end
-
-appraise "ruby-2-3-hashie_v4" do
-  eval_gemfile "modular/faraday_v0.gemfile"
-  eval_gemfile "modular/hashie_v4.gemfile"
-  eval_gemfile "modular/jwt_v1.gemfile"
-  eval_gemfile "modular/logger_v1_2.gemfile"
-  eval_gemfile "modular/multi_xml_v0_5.gemfile"
-  eval_gemfile "modular/rack_v1_2.gemfile"
-  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
-end
-
-appraise "ruby-2-3-hashie_v5" do
-  eval_gemfile "modular/faraday_v0.gemfile"
-  eval_gemfile "modular/hashie_v5.gemfile"
-  eval_gemfile "modular/jwt_v1.gemfile"
-  eval_gemfile "modular/logger_v1_2.gemfile"
-  eval_gemfile "modular/multi_xml_v0_5.gemfile"
-  eval_gemfile "modular/rack_v1_2.gemfile"
-  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
-end
 
 appraise "ruby-2-4" do
   eval_gemfile "modular/faraday_v1.gemfile"
@@ -217,3 +158,63 @@ appraise "style" do
   eval_gemfile "modular/style.gemfile"
   eval_gemfile "modular/x_std_libs.gemfile"
 end
+
+appraise "ruby-2-3-hashie_v0" do
+  eval_gemfile "modular/faraday_v0.gemfile"
+  eval_gemfile "modular/hashie_v0.gemfile"
+  eval_gemfile "modular/jwt_v1.gemfile"
+  eval_gemfile "modular/logger_v1_2.gemfile"
+  eval_gemfile "modular/multi_xml_v0_5.gemfile"
+  eval_gemfile "modular/rack_v1_2.gemfile"
+  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
+end
+
+appraise "ruby-2-3-hashie_v1" do
+  eval_gemfile "modular/faraday_v0.gemfile"
+  eval_gemfile "modular/hashie_v1.gemfile"
+  eval_gemfile "modular/jwt_v1.gemfile"
+  eval_gemfile "modular/logger_v1_2.gemfile"
+  eval_gemfile "modular/multi_xml_v0_5.gemfile"
+  eval_gemfile "modular/rack_v1_2.gemfile"
+  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
+end
+
+appraise "ruby-2-3-hashie_v2" do
+  eval_gemfile "modular/faraday_v0.gemfile"
+  eval_gemfile "modular/hashie_v2.gemfile"
+  eval_gemfile "modular/jwt_v1.gemfile"
+  eval_gemfile "modular/logger_v1_2.gemfile"
+  eval_gemfile "modular/multi_xml_v0_5.gemfile"
+  eval_gemfile "modular/rack_v1_2.gemfile"
+  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
+end
+
+appraise "ruby-2-3-hashie_v3" do
+  eval_gemfile "modular/faraday_v0.gemfile"
+  eval_gemfile "modular/hashie_v3.gemfile"
+  eval_gemfile "modular/jwt_v1.gemfile"
+  eval_gemfile "modular/logger_v1_2.gemfile"
+  eval_gemfile "modular/multi_xml_v0_5.gemfile"
+  eval_gemfile "modular/rack_v1_2.gemfile"
+  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
+end
+
+appraise "ruby-2-3-hashie_v4" do
+  eval_gemfile "modular/faraday_v0.gemfile"
+  eval_gemfile "modular/hashie_v4.gemfile"
+  eval_gemfile "modular/jwt_v1.gemfile"
+  eval_gemfile "modular/logger_v1_2.gemfile"
+  eval_gemfile "modular/multi_xml_v0_5.gemfile"
+  eval_gemfile "modular/rack_v1_2.gemfile"
+  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
+end
+
+appraise "ruby-2-3-hashie_v5" do
+  eval_gemfile "modular/faraday_v0.gemfile"
+  eval_gemfile "modular/hashie_v5.gemfile"
+  eval_gemfile "modular/jwt_v1.gemfile"
+  eval_gemfile "modular/logger_v1_2.gemfile"
+  eval_gemfile "modular/multi_xml_v0_5.gemfile"
+  eval_gemfile "modular/rack_v1_2.gemfile"
+  eval_gemfile "modular/x_std_libs/r2.3/libs.gemfile"
+end
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f70f8c81..4cbdfb9b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,9 +24,10 @@ Follow these instructions:
 
 ## Executables vs Rake tasks
 
-Executables shipped by oauth2 can be used with or without generating the binstubs.
-They will work when oauth2 is installed globally (i.e., `gem install oauth2`) and do not require that oauth2 be in your bundle.
+Executables shipped by dependencies, such as kettle-dev, and stone_checksums, are available
+after running `bin/setup`. These include:
 
+- gem_checksums
 - kettle-changelog
 - kettle-commit-msg
 - oauth2-setup
@@ -35,20 +36,10 @@ They will work when oauth2 is installed globally (i.e., `gem install oauth2`) an
 - kettle-readme-backers
 - kettle-release
 
-However, the rake tasks provided by oauth2 do require oauth2 to be added as a development dependency and loaded in your Rakefile.
-See the full list of rake tasks in head of Rakefile
+There are many Rake tasks available as well. You can see them by running:
 
-**Gemfile**
-```ruby
-group :development do
-  gem "oauth2", require: false
-end
-```
-
-**Rakefile**
-```ruby
-# Rakefile
-require "oauth2"
+```shell
+bin/rake -T
 ```
 
 ## Environment Variables for Local Development
@@ -118,10 +109,8 @@ bundle exec rake test
 
 ### Spec organization (required)
 
-- One spec file per class/module. For each class or module under `lib/`, keep all of its unit tests in a single spec file under `spec/` that mirrors the path and file name exactly: `lib/oauth2/release_cli.rb` -> `spec/oauth2/release_cli_spec.rb`.
-- Never add a second spec file for the same class/module. Examples of disallowed names: `*_more_spec.rb`, `*_extra_spec.rb`, `*_status_spec.rb`, or any other suffix that still targets the same class. If you find yourself wanting a second file, merge those examples into the canonical spec file for that class/module.
+- One spec file per class/module. For each class or module under `lib/`, keep all of its unit tests in a single spec file under `spec/` that mirrors the path and file name exactly: `lib/oauth2/my_class.rb` -> `spec/oauth2/my_class_spec.rb`.
 - Exception: Integration specs that intentionally span multiple classes. Place these under `spec/integration/` (or a clearly named integration folder), and do not directly mirror a single class. Name them after the scenario, not a class.
-- Migration note: If a duplicate spec file exists, move all examples into the canonical file and delete the duplicate. Do not leave stubs or empty files behind.
 
 ## Lint It
 
@@ -144,7 +133,7 @@ For more detailed information about using RuboCop in this project, please see th
 Never add `# rubocop:disable ...` / `# rubocop:enable ...` comments to code or specs (except when following the few existing `rubocop:disable` patterns for a rule already being disabled elsewhere in the code). Instead:
 
 - Prefer configuration-based exclusions when a rule should not apply to certain paths or files (e.g., via `.rubocop.yml`).
-- When a violation is temporary and you plan to fix it later, record it in `.rubocop_gradual.lock` using the gradual workflow:
+- When a violation is temporary, and you plan to fix it later, record it in `.rubocop_gradual.lock` using the gradual workflow:
   - `bundle exec rake rubocop_gradual:autocorrect` (preferred)
   - `bundle exec rake rubocop_gradual:force_update` (only when you cannot fix the violations immediately)
 
@@ -167,7 +156,7 @@ Also see GitLab Contributors: [https://gitlab.com/ruby-oauth/oauth2/-/graphs/mai
 **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 to RubyGems.org are signed releases.
+All releases are signed releases.
 See: [RubyGems Security Guide][🔒️rubygems-security-guide]
 
 NOTE: To build without signing the gem set `SKIP_GEM_SIGNING` to any value in the environment.
@@ -176,7 +165,7 @@ NOTE: To build without signing the gem set `SKIP_GEM_SIGNING` to any value in th
 
 #### Automated process
 
-1. Update version.rb to contian the correct version-to-be-released.
+1. Update version.rb to contain the correct version-to-be-released.
 2. Run `bundle exec kettle-changelog`.
 3. Run `bundle exec kettle-release`.
 
@@ -205,7 +194,7 @@ NOTE: To build without signing the gem set `SKIP_GEM_SIGNING` to any value in th
 12. Sanity check the SHA256, comparing with the output from the `bin/gem_checksums` command:
     - `sha256sum pkg/-.gem`
 13. 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 [rubygems.org][💎rubygems]
+    push git commits and tags, and push the `.gem` file to the gem host configured in the gemspec.
 
 [📜src-gl]: https://gitlab.com/ruby-oauth/oauth2/
 [📜src-cb]: https://codeberg.org/ruby-oauth/oauth2
@@ -216,7 +205,7 @@ NOTE: To build without signing the gem set `SKIP_GEM_SIGNING` to any value in th
 [🖐contributors]: https://github.com/ruby-oauth/oauth2/graphs/contributors
 [🚎contributors-gl]: https://gitlab.com/ruby-oauth/oauth2/-/graphs/main
 [🖐contributors-img]: https://contrib.rocks/image?repo=ruby-oauth/oauth2
-[💎rubygems]: https://rubygems.org
+[💎gem-coop]: https://gem.coop
 [🔒️rubygems-security-guide]: https://guides.rubygems.org/security/#building-gems
 [🔒️rubygems-checksums-pr]: https://github.com/rubygems/rubygems/pull/6022
 [🔒️rubygems-guides-pr]: https://github.com/rubygems/guides/pull/325
diff --git a/FUNDING.md b/FUNDING.md
index b7a061d1..5ddd4bca 100644
--- a/FUNDING.md
+++ b/FUNDING.md
@@ -6,7 +6,7 @@ Many paths lead to being a sponsor or a backer of this project. Are you on such
 
 [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal]
 
-[![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
+[![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
 
 [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
 [⛳liberapay]: https://liberapay.com/pboling/donate
@@ -31,11 +31,11 @@ Many paths lead to being a sponsor or a backer of this project. Are you on such
 
 
 
-# 🤑 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 filled with many dozens of rejections,
-I'm now spending ~60+ hours a week building open source tools.
+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.
@@ -44,16 +44,13 @@ To join the community or get help 👇️ Join the Discord.
 
 [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
 
-To say "thanks for maintaining such a great tool" ☝️ Join the Discord or 👇️ send money.
+To say "thanks!" ☝️ Join the Discord or 👇️ send money.
 
-[![Sponsor ruby-oauth/oauth2 on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img]
+[![Sponsor ruby-oauth/oauth2 on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
 
 # Another Way to Support Open Source Software
 
-> How wonderful it is that nobody need wait a single moment before starting to improve the world.
->—Anne Frank - -I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions — totaling 79 hours of FLOSS coding over just the past seven days, a pretty regular week for me. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats). +I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats). If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`. diff --git a/Gemfile b/Gemfile index 19875ab5..be6c1816 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,8 @@ # frozen_string_literal: true -source "https://rubygems.org" +source "https://gem.coop" -git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } +git_source(:codeberg) { |repo_name| "https://codeberg.org/#{repo_name}" } git_source(:gitlab) { |repo_name| "https://gitlab.com/#{repo_name}" } #### IMPORTANT ####################################################### diff --git a/Gemfile.lock b/Gemfile.lock index 9961d0f9..65ed6feb 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,5 +1,5 @@ GIT - remote: https://github.com/pboling/yard-junk + remote: https://github.com/pboling/yard-junk.git revision: 54ccebabbfa9a9cd44d0b991687ebbfd22c32b55 branch: next specs: @@ -23,7 +23,7 @@ PATH version_gem (~> 1.1, >= 1.1.9) GEM - remote: https://rubygems.org/ + remote: https://gem.coop/ specs: addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) @@ -41,9 +41,6 @@ GEM bundler (>= 1.2.0, < 3) thor (~> 1.0) concurrent-ruby (1.3.5) - crack (1.0.1) - bigdecimal - rexml date (3.5.0) debug (1.11.0) irb (~> 1.10) @@ -93,7 +90,6 @@ GEM version_gem (~> 1.1, >= 1.1.4) gitmoji-regex (1.0.3) version_gem (~> 1.1, >= 1.1.8) - hashdiff (1.2.1) hashie (5.0.0) io-console (0.8.1) irb (1.15.3) @@ -313,14 +309,12 @@ GEM unicode-emoji (~> 4.1) unicode-emoji (4.1.0) uri (1.1.1) - vcr (6.3.1) - base64 version_gem (1.1.9) - webmock (3.26.1) - addressable (>= 2.8.0) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) yard (0.9.37) + yard-fence (0.4.0) + rdoc (~> 6.11) + version_gem (~> 1.1, >= 1.1.9) + yard (~> 0.9, >= 0.9.37) yard-relative_markdown_links (0.5.0) nokogiri (>= 1.14.3, < 2) zeitwerk (2.7.3) @@ -330,7 +324,7 @@ PLATFORMS x86_64-linux DEPENDENCIES - addressable (~> 2.8, >= 2.8.7) + addressable (~> 2.8, >= 2.8.7, >= 2.8, < 3) appraisal2 (~> 3.0) backports (~> 3.25, >= 3.25.1) benchmark (~> 0.4, >= 0.4.1) @@ -342,7 +336,7 @@ DEPENDENCIES irb (~> 1.15, >= 1.15.2) kettle-dev (~> 1.1) kettle-soup-cover (~> 1.0, >= 1.0.10) - kettle-test (~> 1.0) + kettle-test (~> 1.0, >= 1.0.6) kramdown (~> 2.5, >= 2.5.1) kramdown-parser-gfm (~> 1.1) mutex_m (~> 0.2) @@ -353,7 +347,6 @@ DEPENDENCIES reek (~> 6.5) require_bench (~> 1.0, >= 1.0.4) rexml (~> 3.2, >= 3.2.5) - rspec-pending_for (~> 0.0, >= 0.0.17) rubocop-lts (~> 8.0) rubocop-on-rbs (~> 1.8) rubocop-packaging (~> 0.6, >= 0.6.0) @@ -363,9 +356,8 @@ DEPENDENCIES standard (>= 1.50) stone_checksums (~> 1.0, >= 1.0.2) stringio (>= 3.0) - vcr (>= 4) - webmock (>= 3) yard (~> 0.9, >= 0.9.37) + yard-fence (~> 0.4) yard-junk (~> 0.0, >= 0.0.10)! yard-relative_markdown_links (~> 0.5.0) diff --git a/README.md b/README.md index 74069300..a5ec5975 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ +| 📍 NOTE | +|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| RubyGems (the [GitHub org][rubygems-org], not the website) [suffered][draper-security] a [hostile takeover][ellen-takeover] in September 2025. | +| Ultimately [4 maintainers][simi-removed] were [hard removed][martin-removed] and a reason has been given for only 1 of those, while 2 others resigned in protest. | +| It is a [complicated story][draper-takeover] which is difficult to [parse quickly][draper-lies]. | +| I'm adding notes like this to gems because I [don't condone theft][draper-theft] of repositories or gems from their rightful owners. | +| If a similar theft happened with my repos/gems, I'd hope some would stand up for me. | +| Disenfranchised former-maintainers have started [gem.coop][gem-coop]. | +| Once available I will publish there exclusively; unless RubyCentral makes amends with the community. | +| The ["Technology for Humans: Joel Draper"][reinteractive-podcast] podcast episode by [reinteractive][reinteractive] is the most cogent summary I'm aware of. | +| See [here][gem-naming], [here][gem-coop] and [here][martin-ann] for more info on what comes next. | +| What I'm doing: A (WIP) proposal for [bundler/gem scopes][gem-scopes], and a (WIP) proposal for a federated [gem server][gem-server]. | + +[rubygems-org]: https://github.com/rubygems/ +[draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/ +[draper-takeover]: https://joel.drapper.me/p/ruby-central-takeover/ +[ellen-takeover]: https://pup-e.com/blog/goodbye-rubygems/ +[simi-removed]: https://www.reddit.com/r/ruby/s/gOk42POCaV +[martin-removed]: https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q +[draper-lies]: https://joel.drapper.me/p/ruby-central-fact-check/ +[draper-theft]: https://joel.drapper.me/p/ruby-central/ +[reinteractive]: https://reinteractive.com/ruby-on-rails +[gem-coop]: https://gem.coop +[gem-naming]: https://github.com/gem-coop/gem.coop/issues/12 +[martin-ann]: https://martinemde.com/2025/10/05/announcing-gem-coop.html +[gem-scopes]: https://github.com/galtzo-floss/bundle-namespace +[gem-server]: https://github.com/galtzo-floss/gem-server +[reinteractive-podcast]: https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6 + [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]][🖼️galtzo-discord] [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][🖼️ruby-lang-i]][🖼️ruby-lang] [![oauth2 Logo by Chris Messina, CC BY-SA 3.0][🖼️oauth2-i]][🖼️oauth2] [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg @@ -30,7 +59,7 @@ This is a RubyGem for implementing OAuth 2.0 clients (not servers) in Ruby appli ### Quick Examples -
+
Convert the following `curl` command into a token request using this gem... ```shell @@ -61,7 +90,7 @@ NOTE: `header` - The content type specified in the `curl` is already the default
-
+
+ Find this repo on federated forges (Coming soon!) -| Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions | -|-----------------------------------------------|-----------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------| -| 🧪 [ruby-oauth/oauth2 on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ | -| 🧊 [ruby-oauth/oauth2 on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ | -| 🐙 [ruby-oauth/oauth2 on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] | -| 🤼 [OAuth Ruby Google Group][⛳gg-discussions] | "Active" | ➖ | ➖ | ➖ | ➖ | [💚][⛳gg-discussions] | -| 🎮️ [Discord Server][✉️discord-invite] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] | [Let's][✉️discord-invite] | [talk][✉️discord-invite] | [about][✉️discord-invite] | [this][✉️discord-invite] | [library!][✉️discord-invite] | +| Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions | +|-------------------------------------------------|-----------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------| +| 🧪 [ruby-oauth/oauth2 on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ | +| 🧊 [ruby-oauth/oauth2 on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ | +| 🐙 [ruby-oauth/oauth2 on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] | +| 🎮️ [Discord Server][✉️discord-invite] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] | [Let's][✉️discord-invite] | [talk][✉️discord-invite] | [about][✉️discord-invite] | [this][✉️discord-invite] | [library!][✉️discord-invite] |
@@ -239,7 +201,7 @@ If you use a gem version of a core Ruby library, it should work fine! Available as part of the Tidelift Subscription. -
+
Need enterprise-level guarantees? The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. @@ -258,143 +220,6 @@ Alternatively:
-## 🚀 Release Documentation - -### Version 2.0.x - -
- 2.0.x CHANGELOG and README - -| Version | Release Date | CHANGELOG | README | -|---------|--------------|---------------------------------------|---------------------------------| -| 2.0.17 | 2025-09-15 | [v2.0.17 CHANGELOG][2.0.17-changelog] | [v2.0.17 README][2.0.17-readme] | -| 2.0.16 | 2025-09-14 | [v2.0.16 CHANGELOG][2.0.16-changelog] | [v2.0.16 README][2.0.16-readme] | -| 2.0.15 | 2025-09-08 | [v2.0.15 CHANGELOG][2.0.15-changelog] | [v2.0.15 README][2.0.15-readme] | -| 2.0.14 | 2025-08-31 | [v2.0.14 CHANGELOG][2.0.14-changelog] | [v2.0.14 README][2.0.14-readme] | -| 2.0.13 | 2025-08-30 | [v2.0.13 CHANGELOG][2.0.13-changelog] | [v2.0.13 README][2.0.13-readme] | -| 2.0.12 | 2025-05-31 | [v2.0.12 CHANGELOG][2.0.12-changelog] | [v2.0.12 README][2.0.12-readme] | -| 2.0.11 | 2025-05-23 | [v2.0.11 CHANGELOG][2.0.11-changelog] | [v2.0.11 README][2.0.11-readme] | -| 2.0.10 | 2025-05-17 | [v2.0.10 CHANGELOG][2.0.10-changelog] | [v2.0.10 README][2.0.10-readme] | -| 2.0.9 | 2022-09-16 | [v2.0.9 CHANGELOG][2.0.9-changelog] | [v2.0.9 README][2.0.9-readme] | -| 2.0.8 | 2022-09-01 | [v2.0.8 CHANGELOG][2.0.8-changelog] | [v2.0.8 README][2.0.8-readme] | -| 2.0.7 | 2022-08-22 | [v2.0.7 CHANGELOG][2.0.7-changelog] | [v2.0.7 README][2.0.7-readme] | -| 2.0.6 | 2022-07-13 | [v2.0.6 CHANGELOG][2.0.6-changelog] | [v2.0.6 README][2.0.6-readme] | -| 2.0.5 | 2022-07-07 | [v2.0.5 CHANGELOG][2.0.5-changelog] | [v2.0.5 README][2.0.5-readme] | -| 2.0.4 | 2022-07-01 | [v2.0.4 CHANGELOG][2.0.4-changelog] | [v2.0.4 README][2.0.4-readme] | -| 2.0.3 | 2022-06-28 | [v2.0.3 CHANGELOG][2.0.3-changelog] | [v2.0.3 README][2.0.3-readme] | -| 2.0.2 | 2022-06-24 | [v2.0.2 CHANGELOG][2.0.2-changelog] | [v2.0.2 README][2.0.2-readme] | -| 2.0.1 | 2022-06-22 | [v2.0.1 CHANGELOG][2.0.1-changelog] | [v2.0.1 README][2.0.1-readme] | -| 2.0.0 | 2022-06-21 | [v2.0.0 CHANGELOG][2.0.0-changelog] | [v2.0.0 README][2.0.0-readme] | - -
- -[2.0.17-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2017---2025-09-15 -[2.0.16-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2016---2025-09-14 -[2.0.15-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2015---2025-09-08 -[2.0.14-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2014---2025-08-31 -[2.0.13-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2013---2025-08-30 -[2.0.12-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2012---2025-05-31 -[2.0.11-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2011---2025-05-23 -[2.0.10-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2010---2025-05-17 -[2.0.9-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#209---2022-09-16 -[2.0.8-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#208---2022-09-01 -[2.0.7-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#207---2022-08-22 -[2.0.6-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#206---2022-07-13 -[2.0.5-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#205---2022-07-07 -[2.0.4-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#204---2022-07-01 -[2.0.3-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#203---2022-06-28 -[2.0.2-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#202---2022-06-24 -[2.0.1-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#201---2022-06-22 -[2.0.0-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#200---2022-06-21 - -[2.0.17-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.17/README.md -[2.0.16-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.16/README.md -[2.0.15-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.15/README.md -[2.0.14-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.14/README.md -[2.0.13-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.13/README.md -[2.0.12-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.12/README.md -[2.0.11-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.11/README.md -[2.0.10-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.10/README.md -[2.0.9-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.9/README.md -[2.0.8-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.8/README.md -[2.0.7-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.7/README.md -[2.0.6-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.6/README.md -[2.0.5-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.5/README.md -[2.0.4-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.4/README.md -[2.0.3-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.3/README.md -[2.0.2-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.2/README.md -[2.0.1-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.1/README.md -[2.0.0-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.0/README.md - -### Older Releases - -
- 1.4.x CHANGELOGs and READMEs - -| Version | Release Date | CHANGELOG | README | -|---------|--------------|---------------------------------------|---------------------------------| -| 1.4.11 | Sep 16, 2022 | [v1.4.11 CHANGELOG][1.4.11-changelog] | [v1.4.11 README][1.4.11-readme] | -| 1.4.10 | Jul 1, 2022 | [v1.4.10 CHANGELOG][1.4.10-changelog] | [v1.4.10 README][1.4.10-readme] | -| 1.4.9 | Feb 20, 2022 | [v1.4.9 CHANGELOG][1.4.9-changelog] | [v1.4.9 README][1.4.9-readme] | -| 1.4.8 | Feb 18, 2022 | [v1.4.8 CHANGELOG][1.4.8-changelog] | [v1.4.8 README][1.4.8-readme] | -| 1.4.7 | Mar 19, 2021 | [v1.4.7 CHANGELOG][1.4.7-changelog] | [v1.4.7 README][1.4.7-readme] | -| 1.4.6 | Mar 19, 2021 | [v1.4.6 CHANGELOG][1.4.6-changelog] | [v1.4.6 README][1.4.6-readme] | -| 1.4.5 | Mar 18, 2021 | [v1.4.5 CHANGELOG][1.4.5-changelog] | [v1.4.5 README][1.4.5-readme] | -| 1.4.4 | Feb 12, 2020 | [v1.4.4 CHANGELOG][1.4.4-changelog] | [v1.4.4 README][1.4.4-readme] | -| 1.4.3 | Jan 29, 2020 | [v1.4.3 CHANGELOG][1.4.3-changelog] | [v1.4.3 README][1.4.3-readme] | -| 1.4.2 | Oct 1, 2019 | [v1.4.2 CHANGELOG][1.4.2-changelog] | [v1.4.2 README][1.4.2-readme] | -| 1.4.1 | Oct 13, 2018 | [v1.4.1 CHANGELOG][1.4.1-changelog] | [v1.4.1 README][1.4.1-readme] | -| 1.4.0 | Jun 9, 2017 | [v1.4.0 CHANGELOG][1.4.0-changelog] | [v1.4.0 README][1.4.0-readme] | -
- -[1.4.11-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1411---2022-09-16 -[1.4.10-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1410---2022-07-01 -[1.4.9-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#149---2022-02-20 -[1.4.8-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#148---2022-02-18 -[1.4.7-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#147---2021-03-19 -[1.4.6-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#146---2021-03-19 -[1.4.5-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#145---2021-03-18 -[1.4.4-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#144---2020-02-12 -[1.4.3-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#143---2020-01-29 -[1.4.2-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#142---2019-10-01 -[1.4.1-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#141---2018-10-13 -[1.4.0-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#140---2017-06-09 - -[1.4.11-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.11/README.md -[1.4.10-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.10/README.md -[1.4.9-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.9/README.md -[1.4.8-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.8/README.md -[1.4.7-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.7/README.md -[1.4.6-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.6/README.md -[1.4.5-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.5/README.md -[1.4.4-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.4/README.md -[1.4.3-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.3/README.md -[1.4.2-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.2/README.md -[1.4.1-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.1/README.md -[1.4.0-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.0/README.md - -
- 1.3.x Readmes - -| Version | Release Date | Readme | -|---------|--------------|--------------------------------------------------------------| -| 1.3.1 | Mar 3, 2017 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.3.1/README.md | -| 1.3.0 | Dec 27, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.3.0/README.md | - -
- -
- ≤= 1.2.x Readmes (2016 and before) - -| Version | Release Date | Readme | -|---------|--------------|--------------------------------------------------------------| -| 1.2.0 | Jun 30, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.2.0/README.md | -| 1.1.0 | Jan 30, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.1.0/README.md | -| 1.0.0 | May 23, 2014 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.0.0/README.md | -| < 1.0.0 | Find here | https://gitlab.com/ruby-oauth/oauth2/-/tags | - -
- ## ✨ Installation Install the gem and add to the application's Gemfile by executing: @@ -411,14 +236,14 @@ gem install oauth2 ### 🔒 Secure Installation -
+
For Medium or High Security Installations -This gem is cryptographically signed and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by +This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by [stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with by following the instructions below. -Add my public key (if you haven’t already; will expire 2045-04-29) as a trusted certificate: +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) @@ -440,6 +265,8 @@ If you want to up your security game full-time: bundle config set --global trust-policy MediumSecurity ``` +`MediumSecurity` instead of `HighSecurity` is necessary if not all the gems you use are signed. + NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
@@ -477,7 +304,7 @@ Compatibility is further distinguished as "Best Effort Support" or "Incidental S This gem will install on Ruby versions >= v2.2 for 2.x releases. See `1-4-stable` branch for older rubies. -
+
Ruby Version Compatibility Policy If something doesn't work on one of these interpreters, it's a bug. @@ -502,6 +330,7 @@ run and pass on that implementation. When something breaks on your implementation, you will be responsible for providing patches in a timely fashion. If critical issues for a particular implementation exist at the time of a major release, support for that Ruby version may be dropped. +
| | Ruby OAuth2 Version | Maintenance Branch | Targeted Support | Best Effort Support | Incidental Support | @@ -638,7 +467,11 @@ These extensions work regardless of whether you used the global or discrete conf 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. -See [response_spec.rb](https://github.com/ruby-oauth/oauth2/blob/main/spec/oauth2/response_spec.rb) if you need to study the hacks for older Rubies. +Expand the examples below, or the [ruby-oauth/snaky_hash](https://gitlab.com/ruby-oauth/snaky_hash) gem, +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. + +
"additional" response.parsed.class.name # => Hash (just, regular old Hash) ``` -
+
Debugging & Logging Set an environment variable as per usual (e.g. with [dotenv](https://github.com/bkeepers/dotenv)). @@ -729,6 +562,7 @@ client = OAuth2::Client.new( logger: Logger.new("example.log", "weekly"), ) ``` +
### OAuth2::Response @@ -752,6 +586,7 @@ 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): + - `expires_latency` (Integer | nil): Seconds to subtract from expires_in when computing #expired? to offset latency. - `token_name` (String | Symbol | nil): When multiple token-like fields exist in responses, select the field name to use as the access token (since v2.0.10). - `mode` (Symbol | Proc | Hash): Controls how the token is transmitted on requests made via this AccessToken instance. @@ -762,6 +597,7 @@ Options (since v2.0.x unless noted): - a `Hash` with verb symbols as keys, for example `{get: :query, post: :header, delete: :header}`. Note: Verb-dependent mode supports providers like Instagram that require query mode for `GET` and header mode for `POST`/`DELETE` + - Verb-dependent mode via `Proc` was added in v2.0.15 - Verb-dependent mode via `Hash` was added in v2.0.16 @@ -780,6 +616,7 @@ Response instance will contain the `OAuth2::Error` instance. ### Authorization Grants Note on OAuth 2.1 (draft): + - PKCE is required for all OAuth clients using the authorization code flow (especially public clients). Implement PKCE in your app when required by your provider. See RFC 7636 and RFC 8252. - Redirect URIs must be compared using exact string matching by the Authorization Server. - The Implicit grant (response_type=token) and the Resource Owner Password Credentials grant are omitted from OAuth 2.1; they remain here for OAuth 2.0 compatibility but should be avoided for new apps. @@ -788,6 +625,7 @@ Note on OAuth 2.1 (draft): - The definitions of public and confidential clients are simplified to refer only to whether the client has credentials. References: + - OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13 - Aaron Parecki: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1 - FusionAuth: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1 @@ -804,6 +642,7 @@ use. They are available via the [`#auth_code`](https://gitlab.com/ruby-oauth/oau [`#assertion`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/assertion.rb) 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") @@ -887,7 +726,7 @@ access = client.password.get_token("jdoe", "s3cret", scope: "read") #### Examples -
+
JHipster UAA (Spring Cloud) password grant example (legacy; avoid when possible) ```ruby @@ -928,6 +767,7 @@ puts access.to_hash # full token response ``` Notes: + - Resource Owner Password Credentials (ROPC) is deprecated in OAuth 2.1 and discouraged. Prefer Authorization Code + PKCE. - If your deployment strictly demands the X-XSRF-TOKEN header, first fetch it from an endpoint that sets the XSRF-TOKEN cookie (often "/" or a login page) and pass it to headers. - For Basic auth, auth_scheme: :basic_auth handles the Authorization header; you do not need to base64-encode manually. @@ -937,6 +777,7 @@ Notes: ### Instagram API (verb‑dependent token mode) Providers like Instagram require the access token to be sent differently depending on the HTTP verb: + - GET requests: token must be in the query string (?access_token=...) - POST/DELETE requests: token must be in the Authorization header (Bearer ...) @@ -1001,6 +842,7 @@ me = long_lived.get("/me", params: {fields: "id,username"}).parsed ``` Tips: + - Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET. - If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }. @@ -1107,6 +949,7 @@ resp = access.get("/v1/protected") ``` Notes: + - Files must contain the appropriate PEMs. The private key may be encrypted; if so, pass a password to `OpenSSL::PKey::RSA.new(File.read(path), ENV["KEY_PASSWORD"])`. - If your certificate and key are in a PKCS#12/PFX bundle, you can load them like: - `p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"])` @@ -1280,7 +1123,7 @@ and [Tidelift][🏙️entsup-tidelift]. ### Open Collective for Individuals -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)] +Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/ruby-oauth#backer)] NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically. @@ -1290,27 +1133,19 @@ No backers yet. Be the first! ### Open Collective for Organizations -Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/kettle-rb#sponsor)] +Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/ruby-oauth#sponsor)] NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically. No sponsors yet. Be the first! - -### Open Collective for Donors - - - -[kettle-readme-backers]: https://github.com/kettle-rb/kettle-dev/blob/main/exe/kettle-readme-backers +[kettle-readme-backers]: https://github.com/ruby-oauth/oauth2/blob/main/exe/kettle-readme-backers ### Another way to support open-source -> How wonderful it is that nobody need wait a single moment before starting to improve the world.
->—Anne Frank - -I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions — totaling 79 hours of FLOSS coding over just the past seven days, a pretty regular week for me. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats). +I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats). If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`. @@ -1318,7 +1153,7 @@ I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags** -[![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon] +[![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon] ## 🔐 Security @@ -1397,12 +1232,11 @@ For example: spec.add_dependency("oauth2", "~> 2.0") ``` -
+
📌 Is "Platform Support" part of the public API? More details inside. SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms -is a *breaking change* to an API. -It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless. +is a *breaking change* to an API, and for that reason the bike shedding is endless. To get a better understanding of how SemVer is intended to work over a project's lifetime, read this article from the creator of SemVer: @@ -1423,7 +1257,7 @@ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright ## 🤑 A request for help Maintainers have teeth and need to pay their dentists. -After getting laid off in an RIF in March and filled with many dozens of rejections, -I'm now spending ~60+ hours a week building open source tools. +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. @@ -1451,7 +1285,7 @@ To join the community or get help 👇️ Join the Discord. To say "thanks!" ☝️ Join the Discord or 👇️ send money. -[![Sponsor ruby-oauth/oauth2 on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img] +[![Sponsor ruby-oauth/oauth2 on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal] ### Please give the project a star ⭐ ♥. @@ -1499,7 +1333,7 @@ Thanks for RTFM. ☺️ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780 [⛳️gem-namespace]: https://github.com/ruby-oauth/oauth2 [⛳️namespace-img]: https://img.shields.io/badge/namespace-OAuth2-3C2D2D.svg?style=square&logo=ruby&logoColor=white -[⛳️gem-name]: https://rubygems.org/gems/oauth2 +[⛳️gem-name]: https://bestgems.org/gems/oauth2 [⛳️name-img]: https://img.shields.io/badge/name-oauth2-3C2D2D.svg?style=square&logo=rubygems&logoColor=red [⛳️tag-img]: https://img.shields.io/github/tag/ruby-oauth/oauth2.svg [⛳️tag]: http://github.com/ruby-oauth/oauth2/releases @@ -1548,11 +1382,11 @@ Thanks for RTFM. ☺️ [📜gh-wiki]: https://github.com/ruby-oauth/oauth2/wiki [📜gl-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white [📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white -[👽dl-rank]: https://rubygems.org/gems/oauth2 +[👽dl-rank]: https://bestgems.org/gems/oauth2 [👽dl-ranki]: https://img.shields.io/gem/rd/oauth2.svg [👽oss-help]: https://www.codetriage.com/ruby-oauth/oauth2 [👽oss-helpi]: https://www.codetriage.com/ruby-oauth/oauth2/badges/users.svg -[👽version]: https://rubygems.org/gems/oauth2 +[👽version]: https://bestgems.org/gems/oauth2 [👽versioni]: https://img.shields.io/gem/v/oauth2.svg [🏀qlty-mnt]: https://qlty.sh/gh/ruby-oauth/projects/oauth2 [🏀qlty-mnti]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/maintainability.svg @@ -1642,8 +1476,8 @@ Thanks for RTFM. ☺️ [📌changelog]: CHANGELOG.md [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat -[📌gitmoji]:https://gitmoji.dev -[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square +[📌gitmoji]: https://gitmoji.dev +[📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.526-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue [🔐security]: SECURITY.md diff --git a/Rakefile b/Rakefile index 9f4f39b6..acb40883 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,6 @@ # frozen_string_literal: true -# kettle-dev Rakefile v1.1.24 - 2025-09-17 +# kettle-dev Rakefile v1.1.49 - 2025-11-07 # Ruby 2.3 (Safe Navigation) or higher required # # MIT License (see License.txt) diff --git a/gemfiles/modular/documentation.gemfile b/gemfiles/modular/documentation.gemfile index 78533908..47f1a9d3 100644 --- a/gemfiles/modular/documentation.gemfile +++ b/gemfiles/modular/documentation.gemfile @@ -9,3 +9,4 @@ gem "yard-relative_markdown_links", "~> 0.5.0" # Std Lib extractions gem "rdoc", "~> 6.11" +gem "yard-fence", "~> 0.4", require: false # Ruby >= 3.2 diff --git a/gemfiles/modular/erb/r2.3/default.gemfile b/gemfiles/modular/erb/r2.3/default.gemfile index a38f952f..ca868e84 100644 --- a/gemfiles/modular/erb/r2.3/default.gemfile +++ b/gemfiles/modular/erb/r2.3/default.gemfile @@ -1,5 +1,5 @@ # The cake is a lie. -# erb v2.2, the oldest release on RubyGems.org, was never compatible with Ruby 2.3. +# erb v2.2, the oldest release, was never compatible with Ruby 2.3. # In addition, erb does not follow SemVer, and old rubies get dropped in a patch. # This means we have no choice but to use the erb that shipped with Ruby 2.3 # /opt/hostedtoolcache/Ruby/2.3.8/x64/lib/ruby/gems/2.3.0/gems/erb-2.2.2/lib/erb.rb:670:in `prepare_trim_mode': undefined method `match?' for "-":String (NoMethodError) diff --git a/gemfiles/modular/optional.gemfile b/gemfiles/modular/optional.gemfile index dae6a950..2eda51c6 100644 --- a/gemfiles/modular/optional.gemfile +++ b/gemfiles/modular/optional.gemfile @@ -1 +1,2 @@ # Optional dependencies are not depended on directly, but may be used if present. +gem "addressable", ">= 2.8", "< 3" # ruby >= 2.2 diff --git a/gemfiles/modular/x_std_libs/r2.4/libs.gemfile b/gemfiles/modular/x_std_libs/r2.4/libs.gemfile index 5a3c5b6c..c1bcbd8f 100644 --- a/gemfiles/modular/x_std_libs/r2.4/libs.gemfile +++ b/gemfiles/modular/x_std_libs/r2.4/libs.gemfile @@ -1,3 +1,3 @@ -eval_gemfile "../../erb/r2.4/v2.2.gemfile" +eval_gemfile "../../erb/r2.6/v2.2.gemfile" eval_gemfile "../../mutex_m/r2.4/v0.1.gemfile" eval_gemfile "../../stringio/r2.4/v0.0.2.gemfile" diff --git a/oauth2.gemspec b/oauth2.gemspec index 0556ea5c..a1d34e52 100644 --- a/oauth2.gemspec +++ b/oauth2.gemspec @@ -43,7 +43,6 @@ Gem::Specification.new do |spec| end gl_homepage = "https://gitlab.com/ruby-oauth/#{spec.name}" - gh_mirror = spec.homepage spec.post_install_message = %{ ---+++--- oauth2 v#{gem_version} ---+++--- @@ -73,9 +72,9 @@ Thanks, @pboling / @galtzo } spec.metadata["homepage_uri"] = "https://#{spec.name.tr("_", "-")}.galtzo.com/" - spec.metadata["source_code_uri"] = "#{gh_mirror}/tree/v#{spec.version}" - spec.metadata["changelog_uri"] = "#{gh_mirror}/blob/v#{spec.version}/CHANGELOG.md" - spec.metadata["bug_tracker_uri"] = "#{gh_mirror}/issues" + spec.metadata["source_code_uri"] = "#{spec.homepage}/tree/v#{spec.version}" + spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/v#{spec.version}/CHANGELOG.md" + spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues" spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/#{spec.name}/#{spec.version}" spec.metadata["mailing_list_uri"] = "https://groups.google.com/g/oauth-ruby" spec.metadata["funding_uri"] = "https://github.com/sponsors/pboling" @@ -86,8 +85,7 @@ Thanks, @pboling / @galtzo # Specify which files are part of the released package. spec.files = Dir[ - # Executables and tasks - "exe/*", + # Code / tasks / data (NOTE: exe/ is specified via spec.bindir and spec.executables below) "lib/**/*.rb", "lib/**/*.rake", # Signatures @@ -109,6 +107,7 @@ Thanks, @pboling / @galtzo "REEK", "RUBOCOP.md", "SECURITY.md", + "THREAT_MODEL.md", ] spec.rdoc_options += [ "--title", @@ -136,7 +135,7 @@ Thanks, @pboling / @galtzo spec.add_dependency("version_gem", "~> 1.1", ">= 1.1.9") # ruby >= 2.2.0 # NOTE: It is preferable to list development dependencies in the gemspec due to increased - # visibility and discoverability on RubyGems.org. + # visibility and discoverability. # However, development dependencies in gemspec will install on # all versions of Ruby that will run in CI. # This gem, and its gemspec runtime dependencies, will install on Ruby down to 2.2.0. @@ -153,7 +152,7 @@ Thanks, @pboling / @galtzo spec.add_development_dependency("rexml", "~> 3.2", ">= 3.2.5") # ruby >= 0 # Dev, Test, & Release Tasks - spec.add_development_dependency("kettle-dev", "~> 1.1") # ruby >= 2.3.0 + spec.add_development_dependency("kettle-dev", "~> 1.1") # ruby >= 2.3.0 # Security spec.add_development_dependency("bundler-audit", "~> 0.9.2") # ruby >= 2.0.0 @@ -166,8 +165,7 @@ Thanks, @pboling / @galtzo # Testing spec.add_development_dependency("appraisal2", "~> 3.0") # ruby >= 1.8.7, for testing against multiple versions of dependencies - spec.add_development_dependency("kettle-test", "~> 1.0") # ruby >= 2.3 - spec.add_development_dependency("rspec-pending_for", "~> 0.0", ">= 0.0.17") # ruby >= 2.3, used to skip specs on incompatible Rubies + spec.add_development_dependency("kettle-test", "~> 1.0", ">= 1.0.6") # ruby >= 2.3 # Releasing spec.add_development_dependency("ruby-progressbar", "~> 1.13") # ruby >= 0 @@ -179,7 +177,7 @@ Thanks, @pboling / @galtzo # spec.add_dependency("git", ">= 1.19.1") # ruby >= 2.3 # Development tasks - # The cake is a lie. erb v2.2, the oldest release on RubyGems.org, was never compatible with Ruby 2.3. + # The cake is a lie. erb v2.2, the oldest release, was never compatible with Ruby 2.3. # This means we have no choice but to use the erb that shipped with Ruby 2.3 # /opt/hostedtoolcache/Ruby/2.3.8/x64/lib/ruby/gems/2.3.0/gems/erb-2.2.2/lib/erb.rb:670:in `prepare_trim_mode': undefined method `match?' for "-":String (NoMethodError) # spec.add_development_dependency("erb", ">= 2.2") # ruby >= 2.3.0, not SemVer, old rubies get dropped in a patch. @@ -201,6 +199,6 @@ Thanks, @pboling / @galtzo # In Ruby 3.5 (HEAD) the CGI library has been pared down, so we also need to depend on gem "cgi" for ruby@head # This is done in the "head" appraisal. # See: https://github.com/vcr/vcr/issues/1057 - spec.add_development_dependency("vcr", ">= 4") # 6.0 claims to support ruby >= 2.3, but fails on ruby 2.4 - spec.add_development_dependency("webmock", ">= 3") # Last version to support ruby >= 2.3 + # spec.add_development_dependency("vcr", ">= 4") # 6.0 claims to support ruby >= 2.3, but fails on ruby 2.4 + # spec.add_development_dependency("webmock", ">= 3") # Last version to support ruby >= 2.3 end diff --git a/test_gfm.rb b/test_gfm.rb deleted file mode 100644 index 75c1dc67..00000000 --- a/test_gfm.rb +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env ruby -require 'bundler/setup' -require 'yard' -require 'yard/templates/helpers/markup_helper' - -puts "Before loading .yard_gfm_support.rb:" -YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown].each_with_index do |p, i| - puts " [#{i}] #{p.inspect}" -end - -require './.yard_gfm_support.rb' - -puts "\nAfter loading .yard_gfm_support.rb:" -YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown].each_with_index do |p, i| - puts " [#{i}] #{p.inspect}" -end - -puts "\nTesting KramdownGfmDocument:" - -test_md = <<-MD - # Test - - ```ruby - puts "hello" - ``` -MD - -doc = KramdownGfmDocument.new(test_md) -html = doc.to_html -puts html -puts "\nDoes output contain
? #{html.include?('
')}"
-puts "Does output contain ? #{html.include?('
Date: Fri, 7 Nov 2025 18:22:32 -0700
Subject: [PATCH 06/14] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20kettle-dev?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 65ed6feb..e6e1832d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -99,7 +99,7 @@ GEM
     json (2.16.0)
     jwt (3.1.2)
       base64
-    kettle-dev (1.1.49)
+    kettle-dev (1.1.50)
     kettle-soup-cover (1.0.10)
       simplecov (~> 0.22)
       simplecov-cobertura (~> 3.0)

From 46d75d104137846e0141625277b0f35d50d3f754 Mon Sep 17 00:00:00 2001
From: "Peter H. Boling" 
Date: Fri, 7 Nov 2025 18:57:25 -0700
Subject: [PATCH 07/14] =?UTF-8?q?=F0=9F=8E=A8=20Template=20bootstrap=20by?=
 =?UTF-8?q?=20kettle-dev-setup=20v1.1.50?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Appraisals      | 1 -
 CONTRIBUTING.md | 7 +++++--
 Rakefile        | 2 +-
 3 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/Appraisals b/Appraisals
index e1a741b5..5cacc191 100644
--- a/Appraisals
+++ b/Appraisals
@@ -47,7 +47,6 @@ appraise "dep-heads" do
   eval_gemfile "modular/runtime_heads.gemfile"
 end
 
-
 appraise "ruby-2-4" do
   eval_gemfile "modular/faraday_v1.gemfile"
   eval_gemfile "modular/hashie_v1.gemfile"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4cbdfb9b..fbc87e94 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -30,7 +30,7 @@ after running `bin/setup`. These include:
 - gem_checksums
 - kettle-changelog
 - kettle-commit-msg
-- oauth2-setup
+- kettle-dev-setup
 - kettle-dvcs
 - kettle-pre-release
 - kettle-readme-backers
@@ -68,7 +68,9 @@ GitHub API and CI helpers
 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/.pem` (defaults to $USER)
-- SOURCE_DATE_EPOCH: Reproducible build timestamp. `kettle-release` will set this automatically for the session.
+- 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
@@ -168,6 +170,7 @@ NOTE: To build without signing the gem set `SKIP_GEM_SIGNING` to any value in th
 1. Update version.rb to contain the correct version-to-be-released.
 2. Run `bundle exec kettle-changelog`.
 3. Run `bundle exec kettle-release`.
+4. Stay awake and monitor the release process for any errors, and answer any prompts.
 
 #### Manual process
 
diff --git a/Rakefile b/Rakefile
index acb40883..786d1916 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-# kettle-dev Rakefile v1.1.49 - 2025-11-07
+# kettle-dev Rakefile v1.1.50 - 2025-11-07
 # Ruby 2.3 (Safe Navigation) or higher required
 #
 # MIT License (see License.txt)

From 6263de80bc38d10d05aa9e85590f32771eca6ce5 Mon Sep 17 00:00:00 2001
From: "Peter H. Boling" 
Date: Fri, 7 Nov 2025 19:03:12 -0700
Subject: [PATCH 08/14] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Upgrade=20kettle-dev?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index e6e1832d..dbb04aaf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -99,7 +99,7 @@ GEM
     json (2.16.0)
     jwt (3.1.2)
       base64
-    kettle-dev (1.1.50)
+    kettle-dev (1.1.51)
     kettle-soup-cover (1.0.10)
       simplecov (~> 0.22)
       simplecov-cobertura (~> 3.0)

From 27164bbe95d928c40bd51d0c7f90f56c02b7c120 Mon Sep 17 00:00:00 2001
From: "Peter H. Boling" 
Date: Fri, 7 Nov 2025 19:03:17 -0700
Subject: [PATCH 09/14] =?UTF-8?q?=F0=9F=8E=A8=20Template=20bootstrap=20by?=
 =?UTF-8?q?=20kettle-dev-setup=20v1.1.51?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Rakefile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Rakefile b/Rakefile
index 786d1916..631e8b13 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-# kettle-dev Rakefile v1.1.50 - 2025-11-07
+# kettle-dev Rakefile v1.1.51 - 2025-11-07
 # Ruby 2.3 (Safe Navigation) or higher required
 #
 # MIT License (see License.txt)

From c110574a27bfbd7a301fbdf5239e2f4d215ee107 Mon Sep 17 00:00:00 2001
From: "Peter H. Boling" 
Date: Fri, 7 Nov 2025 19:12:51 -0700
Subject: [PATCH 10/14] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20site?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 docs/OAuth2.html                              |    8 +-
 docs/OAuth2/AccessToken.html                  |   54 +-
 docs/OAuth2/Authenticator.html                |    8 +-
 docs/OAuth2/Client.html                       |   52 +-
 docs/OAuth2/Error.html                        |    6 +-
 docs/OAuth2/FilteredAttributes.html           |   10 +-
 .../FilteredAttributes/ClassMethods.html      |    4 +-
 docs/OAuth2/Response.html                     |   14 +-
 docs/OAuth2/Strategy.html                     |    4 +-
 docs/OAuth2/Strategy/Assertion.html           |   64 +-
 docs/OAuth2/Strategy/AuthCode.html            |   24 +-
 docs/OAuth2/Strategy/Base.html                |    4 +-
 docs/OAuth2/Strategy/ClientCredentials.html   |    4 +-
 docs/OAuth2/Strategy/Implicit.html            |   16 +-
 docs/OAuth2/Strategy/Password.html            |   16 +-
 docs/OAuth2/Version.html                      |    4 +-
 docs/_index.html                              |   85 +-
 docs/file.CHANGELOG.html                      |  122 +-
 docs/file.CODE_OF_CONDUCT.html                |  110 +-
 docs/file.CONTRIBUTING.html                   |  156 +-
 docs/file.FUNDING.html                        |   29 +-
 docs/file.IRP.html                            |   36 +-
 docs/file.LICENSE.html                        |    4 +-
 docs/file.OIDC.html                           |  257 +-
 docs/file.README.html                         | 2142 ++++++++---------
 docs/file.RUBOCOP.html                        |   23 +-
 docs/file.SECURITY.html                       |   16 +-
 docs/file.THREAT_MODEL.html                   |   34 +-
 docs/file_list.html                           |  135 --
 docs/index.html                               | 2142 ++++++++---------
 docs/top-level-namespace.html                 |    4 +-
 31 files changed, 2598 insertions(+), 2989 deletions(-)

diff --git a/docs/OAuth2.html b/docs/OAuth2.html
index 3ca5c041..11eebbb6 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,9 +415,9 @@

diff --git a/docs/OAuth2/AccessToken.html b/docs/OAuth2/AccessToken.html index 7d07ab1e..4eb602c5 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., :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,9 +3083,9 @@

diff --git a/docs/OAuth2/Authenticator.html b/docs/OAuth2/Authenticator.html index 0cb01dbe..7e023866 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,9 +883,9 @@

diff --git a/docs/OAuth2/Client.html b/docs/OAuth2/Client.html index c50ad592..beff8225 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,12 +1523,10 @@

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)

@@ -1616,7 +1614,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

@@ -1839,14 +1837,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.

@@ -1929,7 +1927,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.

@@ -2041,7 +2039,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.

@@ -2243,28 +2241,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.

@@ -2656,9 +2654,9 @@

diff --git a/docs/OAuth2/Error.html b/docs/OAuth2/Error.html index 2e23dff5..78c78b30 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,9 +772,9 @@

diff --git a/docs/OAuth2/FilteredAttributes.html b/docs/OAuth2/FilteredAttributes.html index 93942485..55a0ab89 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,9 +335,9 @@

diff --git a/docs/OAuth2/FilteredAttributes/ClassMethods.html b/docs/OAuth2/FilteredAttributes/ClassMethods.html index fc2ad592..94807c82 100644 --- a/docs/OAuth2/FilteredAttributes/ClassMethods.html +++ b/docs/OAuth2/FilteredAttributes/ClassMethods.html @@ -280,9 +280,9 @@

diff --git a/docs/OAuth2/Response.html b/docs/OAuth2/Response.html index f2f77289..0a0d9c6e 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,9 +1619,9 @@

diff --git a/docs/OAuth2/Strategy.html b/docs/OAuth2/Strategy.html index a75d5e8d..33004cea 100644 --- a/docs/OAuth2/Strategy.html +++ b/docs/OAuth2/Strategy.html @@ -107,9 +107,9 @@

Defined Under Namespace

diff --git a/docs/OAuth2/Strategy/Assertion.html b/docs/OAuth2/Strategy/Assertion.html index 8f3917bb..9b50b18f 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,29 +292,29 @@

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, => ‘HS256’, :key => ‘secret_key’)
- get_token(claim_set, => ‘RS256’, :key => OpenSSL::PKCS12.new(File.read(‘my_key.p12’), ‘not_secret’))

+

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,9 +481,9 @@

diff --git a/docs/OAuth2/Strategy/AuthCode.html b/docs/OAuth2/Strategy/AuthCode.html index 9cf98f7c..66f488e8 100644 --- a/docs/OAuth2/Strategy/AuthCode.html +++ b/docs/OAuth2/Strategy/AuthCode.html @@ -105,19 +105,15 @@

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)

@@ -483,9 +479,9 @@

diff --git a/docs/OAuth2/Strategy/Base.html b/docs/OAuth2/Strategy/Base.html index 4b7954d8..25d0e02c 100644 --- a/docs/OAuth2/Strategy/Base.html +++ b/docs/OAuth2/Strategy/Base.html @@ -195,9 +195,9 @@

diff --git a/docs/OAuth2/Strategy/ClientCredentials.html b/docs/OAuth2/Strategy/ClientCredentials.html index 32ccd5ca..0ae17856 100644 --- a/docs/OAuth2/Strategy/ClientCredentials.html +++ b/docs/OAuth2/Strategy/ClientCredentials.html @@ -343,9 +343,9 @@

diff --git a/docs/OAuth2/Strategy/Implicit.html b/docs/OAuth2/Strategy/Implicit.html index 8c336a7e..1c4f1a99 100644 --- a/docs/OAuth2/Strategy/Implicit.html +++ b/docs/OAuth2/Strategy/Implicit.html @@ -105,15 +105,13 @@

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/

@@ -420,9 +418,9 @@

diff --git a/docs/OAuth2/Strategy/Password.html b/docs/OAuth2/Strategy/Password.html index c063b0a7..040c2902 100644 --- a/docs/OAuth2/Strategy/Password.html +++ b/docs/OAuth2/Strategy/Password.html @@ -105,15 +105,13 @@

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

@@ -374,9 +372,9 @@

diff --git a/docs/OAuth2/Version.html b/docs/OAuth2/Version.html index 3959a0a5..cd15b2c4 100644 --- a/docs/OAuth2/Version.html +++ b/docs/OAuth2/Version.html @@ -111,9 +111,9 @@

diff --git a/docs/_index.html b/docs/_index.html index cd86d203..1f2eec5b 100644 --- a/docs/_index.html +++ b/docs/_index.html @@ -93,87 +93,6 @@

File Listing

  • LICENSE
  • -
  • CITATION
  • - - -
  • oauth2-2.0.10.gem
  • - - -
  • oauth2-2.0.11.gem
  • - - -
  • oauth2-2.0.12.gem
  • - - -
  • oauth2-2.0.13.gem
  • - - -
  • oauth2-2.0.14.gem
  • - - -
  • oauth2-2.0.15.gem
  • - - -
  • oauth2-2.0.16.gem
  • - - -
  • oauth2-2.0.17.gem
  • - - -
  • oauth2-2.0.10.gem
  • - - -
  • oauth2-2.0.11.gem
  • - - -
  • oauth2-2.0.12.gem
  • - - -
  • oauth2-2.0.13.gem
  • - - -
  • oauth2-2.0.14.gem
  • - - -
  • oauth2-2.0.15.gem
  • - - -
  • oauth2-2.0.16.gem
  • - - -
  • oauth2-2.0.17.gem
  • - - -
  • REEK
  • - - -
  • access_token
  • - - -
  • authenticator
  • - - -
  • client
  • - - -
  • error
  • - - -
  • filtered_attributes
  • - - -
  • response
  • - - -
  • strategy
  • - - -
  • version
  • - - -
  • oauth2
  • - -
    @@ -396,9 +315,9 @@

    Namespace Listing A-Z

    diff --git a/docs/file.CHANGELOG.html b/docs/file.CHANGELOG.html index 43671ea7..e953df4b 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

    @@ -97,7 +97,7 @@

    Fixed

    Security

    -

    +

    2.0.17 - 2025-09-15

      @@ -112,10 +112,10 @@

      Added

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

      +

      2.0.16 - 2025-09-14

        @@ -157,7 +157,7 @@

        Changed

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

      +

      2.0.15 - 2025-09-08

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

        Fixed

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

      +

      2.0.14 - 2025-08-31

        @@ -246,7 +246,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

        @@ -291,7 +291,7 @@

        Fixed

        Security

        -

        +

        2.0.12 - 2025-05-31

          @@ -332,7 +332,7 @@

          Fixed

        • Documentation Typos by @pboling
        -

        +

        2.0.11 - 2025-05-23

          @@ -393,7 +393,7 @@

          Fixed

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

        +

        2.0.10 - 2025-05-17

          @@ -500,7 +500,7 @@

          Fixed

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

        +

        2.0.9 - 2022-09-16

          @@ -521,7 +521,7 @@

          Changed

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

        +

        2.0.8 - 2022-09-01

          @@ -544,7 +544,7 @@

          Added

        -

        +

        2.0.7 - 2022-08-22

          @@ -572,7 +572,7 @@

          Fixed

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

        +

        2.0.6 - 2022-07-13

          @@ -587,7 +587,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

          @@ -617,7 +617,7 @@

          Fixed

        -

        +

        2.0.4 - 2022-07-01

          @@ -632,7 +632,7 @@

          Fixed

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

        +

        2.0.3 - 2022-06-28

          @@ -658,7 +658,7 @@

          Fixed

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

        +

        2.0.2 - 2022-06-24

          @@ -677,7 +677,7 @@

          Fixed

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

        +

        2.0.1 - 2022-06-22

          @@ -692,7 +692,7 @@

          Added

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

        +

        2.0.0 - 2022-06-21

          @@ -850,7 +850,7 @@

          Removed

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

        +

        1.4.11 - 2022-09-16

          @@ -860,7 +860,7 @@

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

        +

        1.4.10 - 2022-07-01

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

        • FIPS Compatibility !587 (@akostadinov)
        -

        +

        1.4.9 - 2022-02-20

          @@ -887,7 +887,7 @@

        • Add Windows and MacOS to test matrix
        -

        +

        1.4.8 - 2022-02-18

          @@ -904,7 +904,7 @@

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

        -

        +

        1.4.7 - 2021-03-19

          @@ -914,7 +914,7 @@

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

        -

        +

        1.4.6 - 2021-03-19

          @@ -928,7 +928,7 @@

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

        -

        +

        1.4.5 - 2021-03-18

          @@ -944,7 +944,7 @@

          !500 - Fix YARD documentation formatting (@olleolleolle)

        -

        +

        1.4.4 - 2020-02-12

          @@ -954,7 +954,7 @@

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

        -

        +

        1.4.3 - 2020-01-29

          @@ -972,7 +972,7 @@

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

        -

        +

        1.4.2 - 2019-10-01

          @@ -986,7 +986,7 @@

        -

        +

        1.4.1 - 2018-10-13

          @@ -1030,7 +1030,7 @@

        -

        +

        1.4.0 - 2017-06-09

          @@ -1044,7 +1044,7 @@

          Dependency: Upgrade Faraday to 0.12 (@sferik)

        -

        +

        1.3.1 - 2017-03-03

          @@ -1055,7 +1055,7 @@

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

        -

        +

        1.3.0 - 2016-12-28

          @@ -1071,7 +1071,7 @@

        • Add support for Faraday 0.10 (@rhymes)
        -

        +

        1.2.0 - 2016-07-01

          @@ -1082,7 +1082,7 @@

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

        +

        1.1.0 - 2016-01-30

          @@ -1092,7 +1092,7 @@

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

        +

        1.0.0 - 2014-07-09

          @@ -1112,7 +1112,7 @@

          Fixed

        • Fix Base64.strict_encode64 incompatibility with Ruby 1.8.7.
        -

        +

        0.5.0 - 2011-07-29

          @@ -1135,7 +1135,7 @@

          Changed

          breaking web_server renamed to auth_code.
        -

        +

        0.4.1 - 2011-04-20

          @@ -1143,7 +1143,7 @@

        -

        +

        0.4.0 - 2011-04-20

          @@ -1151,7 +1151,7 @@

        -

        +

        0.3.0 - 2011-04-08

          @@ -1159,7 +1159,7 @@

        -

        +

        0.2.0 - 2011-04-01

          @@ -1167,7 +1167,7 @@

        -

        +

        0.1.1 - 2011-01-12

          @@ -1175,7 +1175,7 @@

        -

        +

        0.1.0 - 2010-10-13

          @@ -1183,7 +1183,7 @@

        -

        +

        0.0.13 - 2010-08-17

          @@ -1191,7 +1191,7 @@

        -

        +

        0.0.12 - 2010-08-17

          @@ -1199,7 +1199,7 @@

        -

        +

        0.0.11 - 2010-08-17

          @@ -1207,7 +1207,7 @@

        -

        +

        0.0.10 - 2010-06-19

          @@ -1215,7 +1215,7 @@

        -

        +

        0.0.9 - 2010-06-18

          @@ -1223,7 +1223,7 @@

        -

        +

        0.0.8 - 2010-04-27

          @@ -1231,7 +1231,7 @@

        -

        +

        0.0.7 - 2010-04-27

          @@ -1239,7 +1239,7 @@

        -

        +

        0.0.6 - 2010-04-25

          @@ -1247,7 +1247,7 @@

        -

        +

        0.0.5 - 2010-04-23

          @@ -1255,7 +1255,7 @@

        -

        +

        0.0.4 - 2010-04-22

          @@ -1263,7 +1263,7 @@

        -

        +

        0.0.3 - 2010-04-22

          @@ -1271,7 +1271,7 @@

        -

        +

        0.0.2 - 2010-04-22

          @@ -1279,7 +1279,7 @@

        -

        +

        0.0.1 - 2010-04-22

          @@ -1290,9 +1290,9 @@

    diff --git a/docs/file.CODE_OF_CONDUCT.html b/docs/file.CODE_OF_CONDUCT.html index 4e34bb2e..dbbd6d87 100644 --- a/docs/file.CODE_OF_CONDUCT.html +++ b/docs/file.CODE_OF_CONDUCT.html @@ -61,139 +61,139 @@

    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 69803d2c..0a22870a 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,86 +85,72 @@

    Help out!

    Executables vs Rake tasks

    -

    Executables shipped by oauth2 can be used with or without generating the binstubs.
    -They will work when oauth2 is installed globally (i.e., gem install oauth2) and do not require that oauth2 be in your bundle.

    +

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

      +
    • gem_checksums
    • kettle-changelog
    • kettle-commit-msg
    • -
    • oauth2-setup
    • +
    • kettle-dev-setup
    • kettle-dvcs
    • kettle-pre-release
    • kettle-readme-backers
    • kettle-release
    -

    However, the rake tasks provided by oauth2 do require oauth2 to be added as a development dependency and loaded in your Rakefile.
    -See the full list of rake tasks in head of Rakefile

    +

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

    -

    Gemfile

    -
    group :development do
    -  gem "oauth2", require: false
    -end
    -
    - -

    Rakefile

    -
    # Rakefile
    -require "oauth2"
    -
    +

    shell +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.
    • -
    - -

    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:

    -
    bin/rake appraisal:update
    -
    +

    console +bin/rake appraisal:update +

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

    @@ -174,38 +160,40 @@

    The Reek List

    To refresh the reek list:

    -
    bundle exec reek > REEK
    -
    +

    console +bundle exec reek > REEK +

    Run Tests

    To run all tests

    -
    bundle exec rake test
    -
    +

    console +bundle exec rake test +

    Spec organization (required)

      -
    • One spec file per class/module. For each class or module under lib/, keep all of its unit tests in a single spec file under spec/ that mirrors the path and file name exactly: lib/oauth2/release_cli.rb -> spec/oauth2/release_cli_spec.rb.
    • -
    • Never add a second spec file for the same class/module. Examples of disallowed names: *_more_spec.rb, *_extra_spec.rb, *_status_spec.rb, or any other suffix that still targets the same class. If you find yourself wanting a second file, merge those examples into the canonical spec file for that class/module.
    • +
    • One spec file per class/module. For each class or module under lib/, keep all of its unit tests in a single spec file under spec/ that mirrors the path and file name exactly: lib/oauth2/my_class.rb -> spec/oauth2/my_class_spec.rb.
    • Exception: Integration specs that intentionally span multiple classes. Place these under spec/integration/ (or a clearly named integration folder), and do not directly mirror a single class. Name them after the scenario, not a class.
    • -
    • Migration note: If a duplicate spec file exists, move all examples into the canonical file and delete the duplicate. Do not leave stubs or empty files behind.

    Lint It

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

    -
    bundle exec rake
    -
    +

    console +bundle exec rake +

    Or just run the linter.

    -
    bundle exec rake rubocop_gradual:autocorrect
    -
    +

    console +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.

    +

    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.

    Important: Do not add inline RuboCop disables

    @@ -213,7 +201,7 @@

    Important: Do not add inli
    • Prefer configuration-based exclusions when a rule should not apply to certain paths or files (e.g., via .rubocop.yml).
    • -
    • When a violation is temporary and you plan to fix it later, record it in .rubocop_gradual.lock using the gradual workflow: +
    • When a violation is temporary, and you plan to fix it later, record it in .rubocop_gradual.lock using the gradual workflow:
      • bundle exec rake rubocop_gradual:autocorrect (preferred)
      • @@ -239,10 +227,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 to RubyGems.org 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.

        @@ -252,9 +240,10 @@

        To release a new version:

        Automated process

          -
        1. Update version.rb to contian the correct version-to-be-released.
        2. +
        3. Update version.rb to contain the correct version-to-be-released.
        4. Run bundle exec kettle-changelog.
        5. Run bundle exec kettle-release.
        6. +
        7. Stay awake and monitor the release process for any errors, and answer any prompts.

        Manual process

        @@ -288,8 +277,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
        • @@ -300,17 +289,16 @@

          Manual process

        • sha256sum pkg/<gem name>-<version>.gem
      • -
      • 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 rubygems.org -
      • +
      • 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 aab97aa9..3a6b386d 100644 --- a/docs/file.FUNDING.html +++ b/docs/file.FUNDING.html @@ -65,35 +65,30 @@

    OpenCollective Backers OpenCollective Sponsors Sponsor Me on Github Liberapay Goal Progress Donate on PayPal

    -

    Buy me a coffee Donate on Polar Donate to my FLOSS or refugee efforts at ko-fi.com Donate to my FLOSS or refugee efforts using Patreon

    +

    Buy me a coffee Donate on Polar Donate to my FLOSS efforts at ko-fi.com Donate to my FLOSS efforts using Patreon

    -

    🤑 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 filled with many dozens of rejections,
    -I’m now spending ~60+ hours a week 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.

    Live Chat on Discord

    -

    To say “thanks for maintaining such a great tool” ☝️ Join the Discord or 👇️ send money.

    +

    To say “thanks!” ☝️ Join the Discord or 👇️ send money.

    -

    Sponsor ruby-oauth/oauth2 on Open Source Collective 💌 Sponsor me on GitHub Sponsors 💌 Sponsor me on Liberapay 💌 Donate on PayPal

    +

    Sponsor ruby-oauth/oauth2 on Open Source Collective 💌 Sponsor me on GitHub Sponsors 💌 Sponsor me on Liberapay 💌 Donate on PayPal

    Another Way to Support Open Source Software

    -
    -

    How wonderful it is that nobody need wait a single moment before starting to improve the world.

    -—Anne Frank

    -
    - -

    I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions — totaling 79 hours of FLOSS coding over just the past seven days, a pretty regular week for me. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).

    +

    I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).

    If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in bundle fund.

    @@ -104,9 +99,9 @@

    Another Way to Support Open

    diff --git a/docs/file.IRP.html b/docs/file.IRP.html index b2fb8145..38d6f80d 100644 --- a/docs/file.IRP.html +++ b/docs/file.IRP.html @@ -180,13 +180,11 @@

    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

      @@ -194,26 +192,20 @@

      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 db020c50..8befb1fa 100644 --- a/docs/file.LICENSE.html +++ b/docs/file.LICENSE.html @@ -60,9 +60,9 @@
    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 60f480af..2f34a113 100644 --- a/docs/file.OIDC.html +++ b/docs/file.OIDC.html @@ -80,176 +80,153 @@

    Raw OIDC with ruby-oauth/oauth2

    This document complements the inline documentation by focusing on OpenID Connect (OIDC) 1.0 usage patterns when using this gem as an OAuth 2.0 client library.

    -

    Scope of this document

    -
      -
    • Audience: Developers building an OAuth 2.0/OIDC Relying Party (RP, aka client) in Ruby.
    • -
    • Non-goals: This gem does not implement an OIDC Provider (OP, aka Authorization Server); for OP/server see other projects (e.g., doorkeeper + oidc extensions).
    • -
    • Status: Informational documentation with links to normative specs. The gem intentionally remains protocol-agnostic beyond OAuth 2.0; OIDC specifics (like ID Token validation) must be handled by your application.
    • -
    - -

    Key concepts refresher

    -
      -
    • OAuth 2.0 delegates authorization; it does not define authentication of the end-user.
    • -
    • OIDC layers an identity layer on top of OAuth 2.0, introducing: -
        -
      • ID Token: a JWT carrying claims about the authenticated end-user and the authentication event.
      • -
      • Standardized scopes: openid (mandatory), profile, email, address, phone, offline_access, and others.
      • -
      • UserInfo endpoint: a protected resource for retrieving user profile claims.
      • -
      • Discovery and Dynamic Client Registration (optional for providers/clients that support them).
      • -
      -
    • -
    - -

    What this gem provides for OIDC

    -
      -
    • All OAuth 2.0 client capabilities required for OIDC flows: building authorization requests, exchanging authorization codes, refreshing tokens, and making authenticated resource requests.
    • -
    • Transport and parsing conveniences (snaky hash, Faraday integration, error handling, etc.).
    • -
    • Optional client authentication schemes useful with OIDC deployments: -
        -
      • basic_auth (default)
      • -
      • request_body (legacy)
      • -
      • tls_client_auth (MTLS)
      • -
      • private_key_jwt (OIDC-compliant when configured per OP requirements)
      • -
      -
    • -
    - -

    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)
      -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.
    • -
    • Session/state management: continue to validate state to mitigate CSRF; use exact redirect_uri matching.
    • -
    +

    Scope of this document +- Audience: Developers building an OAuth 2.0/OIDC Relying Party (RP, aka client) in Ruby. +- Non-goals: This gem does not implement an OIDC Provider (OP, aka Authorization Server); for OP/server see other projects (e.g., doorkeeper + oidc extensions). +- Status: Informational documentation with links to normative specs. The gem intentionally remains protocol-agnostic beyond OAuth 2.0; OIDC specifics (like ID Token validation) must be handled by your application.

    + +

    Key concepts refresher +- OAuth 2.0 delegates authorization; it does not define authentication of the end-user. +- OIDC layers an identity layer on top of OAuth 2.0, introducing: + - ID Token: a JWT carrying claims about the authenticated end-user and the authentication event. + - Standardized scopes: openid (mandatory), profile, email, address, phone, offline_access, and others. + - UserInfo endpoint: a protected resource for retrieving user profile claims. + - Discovery and Dynamic Client Registration (optional for providers/clients that support them).

    + +

    What this gem provides for OIDC +- All OAuth 2.0 client capabilities required for OIDC flows: building authorization requests, exchanging authorization codes, refreshing tokens, and making authenticated resource requests. +- Transport and parsing conveniences (snaky hash, Faraday integration, error handling, etc.). +- Optional client authentication schemes useful with OIDC deployments: + - basic_auth (default) + - request_body (legacy) + - tls_client_auth (MTLS) + - private_key_jwt (OIDC-compliant when configured per OP requirements)

    + +

    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) + 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. +- Session/state management: continue to validate state to mitigate CSRF; use exact redirect_uri matching.

    Minimal OIDC Authorization Code example

    -
    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)
    +

    ```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) 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"), -) - -# Verify nonce -raise "nonce mismatch" unless decoded["nonce"] == nonce - -# Optionally: call UserInfo -userinfo = token.get("/userinfo").parsed -

    - -

    Notes on discovery and registration

    -
      -
    • Discovery: Most OPs publish configuration at issuer/.well-known/openid-configuration (OIDC Discovery 1.0). From there, resolve authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, etc.
    • -
    • Dynamic Client Registration: Some OPs allow registering clients programmatically (OIDC Dynamic Client Registration 1.0). This gem does not implement registration; use a plain HTTP client or Faraday and store credentials securely.
    • -
    - -

    Common pitfalls and tips

    -
      -
    • Always request the openid scope when you expect an ID Token. Without it, the OP may behave as vanilla OAuth 2.0.
    • -
    • Validate ID Token signature and claims before trusting any identity data. Do not rely solely on the presence of an id_token field.
    • -
    • Prefer Authorization Code + PKCE. Avoid Implicit; it is discouraged in modern guidance and may be disabled by providers.
    • -
    • Use exact redirect_uri matching, and keep your allow-list short.
    • -
    • For public clients that use refresh tokens, prefer sender-constrained tokens (DPoP/MTLS) or rotation with one-time-use refresh tokens, per modern best practices.
    • -
    • When using private_key_jwt, ensure the “aud” (or token_url) and “iss/sub” claims are set per the OP’s rules, and include kid in the JWT header when required so the OP can select the right key.
    • -
    - -

    Relevant specifications and references

    -
      -
    • OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html
    • -
    • OIDC Core (final): https://openid.net/specs/openid-connect-core-1_0-final.html
    • -
    • How OIDC works: https://openid.net/developers/how-connect-works/
    • -
    • OpenID Connect home: https://openid.net/connect/
    • -
    • OIDC Discovery 1.0: https://openid.net/specs/openid-connect-discovery-1_0.html
    • -
    • OIDC Dynamic Client Registration 1.0: https://openid.net/specs/openid-connect-registration-1_0.html
    • -
    • OIDC Session Management 1.0: https://openid.net/specs/openid-connect-session-1_0.html
    • -
    • OIDC RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
    • -
    • OIDC Back-Channel Logout 1.0: https://openid.net/specs/openid-connect-backchannel-1_0.html
    • -
    • OIDC Front-Channel Logout 1.0: https://openid.net/specs/openid-connect-frontchannel-1_0.html
    • -
    • Auth0 OIDC overview: https://auth0.com/docs/authenticate/protocols/openid-connect-protocol
    • -
    • Spring Authorization Server’s list of OAuth2/OIDC specs: https://github.com/spring-projects/spring-authorization-server/wiki/OAuth2-and-OIDC-Specifications
    • -
    - -

    See also

    -
      -
    • README sections on OAuth 2.1 notes and OIDC notes
    • -
    • Strategy classes under lib/oauth2/strategy for flow helpers
    • -
    • Specs under spec/oauth2 for concrete usage patterns
    • -
    - -

    Contributions welcome

    -
      -
    • If you discover provider-specific nuances, consider contributing examples or clarifications (without embedding provider-specific hacks into the library).
    • -
    + aud: ENV.fetch(“OIDC_CLIENT_ID”), +)

    + +

    Verify nonce

    +

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

    + +

    Optionally: call UserInfo

    +

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

    + +

    Notes on discovery and registration +- Discovery: Most OPs publish configuration at {issuer}/.well-known/openid-configuration (OIDC Discovery 1.0). From there, resolve authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, etc. +- Dynamic Client Registration: Some OPs allow registering clients programmatically (OIDC Dynamic Client Registration 1.0). This gem does not implement registration; use a plain HTTP client or Faraday and store credentials securely.

    + +

    Common pitfalls and tips +- Always request the openid scope when you expect an ID Token. Without it, the OP may behave as vanilla OAuth 2.0. +- Validate ID Token signature and claims before trusting any identity data. Do not rely solely on the presence of an id_token field. +- Prefer Authorization Code + PKCE. Avoid Implicit; it is discouraged in modern guidance and may be disabled by providers. +- Use exact redirect_uri matching, and keep your allow-list short. +- For public clients that use refresh tokens, prefer sender-constrained tokens (DPoP/MTLS) or rotation with one-time-use refresh tokens, per modern best practices. +- When using private_key_jwt, ensure the “aud” (or token_url) and “iss/sub” claims are set per the OP’s rules, and include kid in the JWT header when required so the OP can select the right key.

    + +

    Relevant specifications and references +- OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html +- OIDC Core (final): https://openid.net/specs/openid-connect-core-1_0-final.html +- How OIDC works: https://openid.net/developers/how-connect-works/ +- OpenID Connect home: https://openid.net/connect/ +- OIDC Discovery 1.0: https://openid.net/specs/openid-connect-discovery-1_0.html +- OIDC Dynamic Client Registration 1.0: https://openid.net/specs/openid-connect-registration-1_0.html +- OIDC Session Management 1.0: https://openid.net/specs/openid-connect-session-1_0.html +- OIDC RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html +- OIDC Back-Channel Logout 1.0: https://openid.net/specs/openid-connect-backchannel-1_0.html +- OIDC Front-Channel Logout 1.0: https://openid.net/specs/openid-connect-frontchannel-1_0.html +- Auth0 OIDC overview: https://auth0.com/docs/authenticate/protocols/openid-connect-protocol +- Spring Authorization Server’s list of OAuth2/OIDC specs: https://github.com/spring-projects/spring-authorization-server/wiki/OAuth2-and-OIDC-Specifications

    + +

    See also +- README sections on OAuth 2.1 notes and OIDC notes +- Strategy classes under lib/oauth2/strategy for flow helpers +- Specs under spec/oauth2 for concrete usage patterns

    + +

    Contributions welcome +- If you discover provider-specific nuances, consider contributing examples or clarifications (without embedding provider-specific hacks into the library).

    diff --git a/docs/file.README.html b/docs/file.README.html index 1801f9bb..2dc94f80 100644 --- a/docs/file.README.html +++ b/docs/file.README.html @@ -57,13 +57,53 @@
    -

    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

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    📍 NOTE
    RubyGems (the GitHub org, not the website) suffered a hostile takeover in September 2025.
    Ultimately 4 maintainers were hard removed and a reason has been given for only 1 of those, while 2 others resigned in protest.
    It is a complicated story which is difficult to parse quickly.
    I’m adding notes like this to gems because I don’t condone theft of repositories or gems from their rightful owners.
    If a similar theft happened with my repos/gems, I’d hope some would stand up for me.
    Disenfranchised former-maintainers have started gem.coop.
    Once available I will publish there exclusively; unless RubyCentral makes amends with the community.
    The “Technology for Humans: Joel Draper” podcast episode by reinteractive is the most cogent summary I’m aware of.
    See here, here and here for more info on what comes next.
    What I’m doing: A (WIP) proposal for bundler/gem scopes, and a (WIP) proposal for a federated gem server.
    + +

    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

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

    -

    [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]

    +

    Version GitHub tag (latest SemVer) License: MIT Downloads Rank Open Source Helpers CodeCov Test Coverage Coveralls Test Coverage QLTY Test Coverage QLTY Maintainability CI Heads CI Runtime Dependencies @ HEAD CI Current CI Truffle Ruby CI JRuby Deps Locked Deps Unlocked CI Supported CI Legacy CI Unsupported CI Ancient CI Test Coverage CI Style CodeQL Apache SkyWalking Eyes License Compatibility Check

    if ci_badges.map(&:color).detect { it != "green"} ☝️ let me know, as I may have missed the discord notification.

    @@ -71,13 +111,13 @@

    🔐 OAuth 2.0 Authorization Framewor

    if ci_badges.map(&:color).all? { it == "green"} 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.

    -

    [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]

    +

    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

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

    Quick Examples

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

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

    Quick Examples

    --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. +

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

    -```ruby +

    ruby OAuth2::Client.new( "REDMOND_CLIENT_ID", # client_id "REDMOND_CLIENT_SECRET", # client_secret @@ -107,469 +147,485 @@

    Quick Examples

    ). # 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") -``` +

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

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

    -
    +

    <details markdown=”1>

    Complete E2E single file script against mock-oauth2-server -- E2E example uses [navikt/mock-oauth2-server](https://github.com/navikt/mock-oauth2-server), which was added in v2.0.11 -- E2E example does not ship with the released gem, so clone the source to play with it. +
      +
    • E2E example uses navikt/mock-oauth2-server, which was added in v2.0.11
    • +
    • E2E example does not ship with the released gem, so clone the source to play with it.
    • +
    -```console +

    console 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: +

    The output should be something like this:

    -```console +

    console ➜ ruby examples/e2e.rb Access token (truncated): eyJraWQiOiJkZWZhdWx0... userinfo status: 200 -userinfo body: => "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: +

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

    -```console +

    console docker compose -f docker-compose-ssl.yml down -``` - -Troubleshooting: validate connectivity to the mock server - -- Check container status and port mapping: - - `docker compose -f docker-compose-ssl.yml ps` -- From the host, try the discovery URL directly (this is what the example uses by default): - - `curl -v http://localhost:8080/default/.well-known/openid-configuration` - - If that fails immediately, also try: `curl -v --connect-timeout 2 http://127.0.0.1:8080/default/.well-known/openid-configuration` -- From inside the container (to distinguish container vs. host networking): - - `docker exec -it oauth2-mock-oauth2-server-1 curl -v http://127.0.0.1:8080/default/.well-known/openid-configuration` -- Simple TCP probe from the host: - - `nc -vz localhost 8080 # or: ruby -rsocket -e 'TCPSocket.new("localhost",8080).close; puts "tcp ok"'` -- Inspect which host port 8080 is bound to (should be 8080): - - `docker inspect -f '(index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }' oauth2-mock-oauth2-server-1` -- Look at server logs for readiness/errors: - - `docker logs -n 200 oauth2-mock-oauth2-server-1` -- On Linux, ensure nothing else is bound to 8080 and that firewall/SELinux aren’t blocking: - - `ss -ltnp | grep :8080` - -Notes -- Discovery URL pattern is: `http://localhost:8080//.well-known/openid-configuration`, where `` defaults to `default`. -- You can change these with env vars when running the example: - - `E2E_ISSUER_BASE` (default: http://localhost:8080) - - `E2E_REALM` (default: default) - -</details> - -If it seems like you are in the wrong place, you might try one of these: - -* [OAuth 2.0 Spec][oauth2-spec] -* [doorkeeper gem][doorkeeper-gem] for OAuth 2.0 server/provider implementation. -* [oauth sibling gem][sibling-gem] for OAuth 1.0a implementations in Ruby. - -[oauth2-spec]: https://oauth.net/2/ -[sibling-gem]: https://gitlab.com/ruby-oauth/oauth -[doorkeeper-gem]: https://github.com/doorkeeper-gem/doorkeeper - -## 💡 Info you can shake a stick at - -| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] | -|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i]
    [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] | -| Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i]
    [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] | -| Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] | -| Works with MRI Ruby 2 | ![Ruby 2.2 Compat][💎ruby-2.2i]
    [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] | -| Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] | -| Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] | -| Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] | -| Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Incident Response Plan][🔐irp-img]][🔐irp] [![Security Policy][🔐security-img]][🔐security] [![Threat Model][🔐threat-model-img]][🔐threat-model] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] | -| Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] | -| Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] | -| `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] | - -### Compatibility - -* Operating Systems: Linux, macOS, Windows -* MRI Ruby @ v2.3, v2.4, v2.5, v2.6, v2.7, v3.0, v3.1, v3.2, v3.3, v3.4, HEAD - * NOTE: This gem may still _install_ and _run_ on ruby v2.2, but vanilla GitHub Actions no longer supports testing against it, so YMMV. Accept patches so long as they don't break the platforms that do run in CI. -* JRuby @ v9.4, v10.0, HEAD - * NOTE: This gem may still _install_ and _run_ on JRuby v9.2 and v9.3, but they are EOL, builds are flaky, and GitHub Actions [doesn't have][GHA-continue-on-error-ui] a proper [`allow-failures` feature][GHA-allow-failure], and until they do flaky EOL-platform builds get dropped, so YMMV. Accept patches so long as they don't break the platforms that do run in CI. -* TruffleRuby @ v23.1, v24.1, HEAD - * NOTE: This gem may still _install_ and _run_ on Truffleruby v22.3 and v23.0, but they are EOL, builds are flaky, and GitHub Actions [doesn't have][GHA-continue-on-error-ui] a proper [`allow-failures` feature][GHA-allow-failure], and until they do flaky EOL-platform builds get dropped, so YMMV. Accept patches so long as they don't break the platforms that do run in CI. -* gem `faraday` @ v0, v1, v2, HEAD ⏩️ [lostisland/faraday](https://github.com/lostisland/faraday) -* gem `jwt` @ v1, v2, v3, HEAD ⏩️ [jwt/ruby-jwt](https://github.com/jwt/ruby-jwt) -* gem `logger` @ v1.2, v1.5, v1.7, HEAD ⏩️ [ruby/logger](https://github.com/ruby/logger) -* gem `multi_xml` @ v0.5, v0.6, v0.7, HEAD ⏩️ [sferik/multi_xml](https://github.com/sferik/multi_xml) -* gem `rack` @ v1.2, v1.6, v2, v3, HEAD ⏩️ [rack/rack](https://github.com/rack/rack) -* gem `snaky_hash` @ v2, HEAD ⏩️ [ruby-oauth/snaky_hash](https://gitlab.com/ruby-oauth/snaky_hash) -* gem `version_gem` @ v1, HEAD ⏩️ [ruby-oauth/version_gem](https://gitlab.com/ruby-oauth/version_gem) - -The last two were extracted from this gem. They are part of the `ruby-oauth` org, -and are developed in tight collaboration with this gem. - -Also, where reasonable, tested against the runtime dependencies of those dependencies: - -* gem `hashie` @ v0, v1, v2, v3, v4, v5, HEAD ⏩️ [hashie/hashie](https://github.com/hashie/hashie) - -[GHA-continue-on-error-ui]: https://github.com/actions/runner/issues/2347#issuecomment-2653479732 -[GHA-allow-failure]: https://github.com/orgs/community/discussions/15452 - -#### Upgrading Runtime Gem Dependencies - -This project sits underneath a large portion of the authorization systems on the internet. -According to GitHub's project tracking, which I believe only reports on public projects, -[100,000+ projects](https://github.com/ruby-oauth/oauth2/network/dependents), and -[500+ packages](https://github.com/ruby-oauth/oauth2/network/dependents?dependent_type=PACKAGE) depend on this project. - -That means it is painful for the Ruby community when this gem forces updates to its runtime dependencies. - -As a result, great care, and a lot of time, have been invested to ensure this gem is working with all the -leading versions per each minor version of Ruby of all the runtime dependencies it can install with. - -What does that mean specifically for the runtime dependencies? - -We have 100% test coverage of lines and branches, and this test suite runs across a very large matrix. -It wouldn't be possible without appraisal2. - -| 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 | -|------------------------------------------------|--------------------------------------------------------| -| 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ | - -#### You should upgrade this gem with confidence\*. - -- This gem follows a _strict & correct_ (according to the maintainer of SemVer; [more info][sv-pub-api]) interpretation of SemVer. - - Dropping support for **any** of the runtime dependency versions above will be a major version bump. - - If you aren't on one of the minor versions above, make getting there a priority. -- You should upgrade the dependencies of this gem with confidence\*. -- Please do upgrade, and then, when it goes smooth as butter [please sponsor me][🖇sponsor]. Thanks! - -[sv-pub-api]: #-versioning - -\* MIT license; The only guarantees I make are for [enterprise support](#enterprise-support). +

    -
    - Standard Library Dependencies - -The various versions of each are tested via the Ruby test matrix, along with whatever Ruby includes them. - -* base64 -* cgi -* json -* time -* logger (removed from stdlib in Ruby 3.5 so added as runtime dependency in v2.0.10) +

    Troubleshooting: validate connectivity to the mock server

    -If you use a gem version of a core Ruby library, it should work fine! - -
    +
      +
    • Check container status and port mapping: +
        +
      • docker compose -f docker-compose-ssl.yml ps
      • +
      +
    • +
    • From the host, try the discovery URL directly (this is what the example uses by default): +
        +
      • curl -v http://localhost:8080/default/.well-known/openid-configuration
      • +
      • If that fails immediately, also try: curl -v --connect-timeout 2 http://127.0.0.1:8080/default/.well-known/openid-configuration +
      • +
      +
    • +
    • From inside the container (to distinguish container vs. host networking): +
        +
      • docker exec -it oauth2-mock-oauth2-server-1 curl -v http://127.0.0.1:8080/default/.well-known/openid-configuration
      • +
      +
    • +
    • Simple TCP probe from the host: +
        +
      • nc -vz localhost 8080 # or: ruby -rsocket -e 'TCPSocket.new("localhost",8080).close; puts "tcp ok"'
      • +
      +
    • +
    • Inspect which host port 8080 is bound to (should be 8080): +
        +
      • docker inspect -f '{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' oauth2-mock-oauth2-server-1
      • +
      +
    • +
    • Look at server logs for readiness/errors: +
        +
      • docker logs -n 200 oauth2-mock-oauth2-server-1
      • +
      +
    • +
    • On Linux, ensure nothing else is bound to 8080 and that firewall/SELinux aren’t blocking: +
        +
      • ss -ltnp | grep :8080
      • +
      +
    • +
    -### Federated DVCS +

    Notes

    -
    - Find this repo on federated forges - -| Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions | -|-----------------------------------------------|-----------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------| -| 🧪 [ruby-oauth/oauth2 on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ | -| 🧊 [ruby-oauth/oauth2 on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ | -| 🐙 [ruby-oauth/oauth2 on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] | -| 🤼 [OAuth Ruby Google Group][⛳gg-discussions] | "Active" | ➖ | ➖ | ➖ | ➖ | [💚][⛳gg-discussions] | -| 🎮️ [Discord Server][✉️discord-invite] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] | [Let's][✉️discord-invite] | [talk][✉️discord-invite] | [about][✉️discord-invite] | [this][✉️discord-invite] | [library!][✉️discord-invite] | +
      +
    • Discovery URL pattern is: http://localhost:8080/<realm>/.well-known/openid-configuration, where <realm> defaults to default.
    • +
    • You can change these with env vars when running the example: +
        +
      • +E2E_ISSUER_BASE (default: http://localhost:8080)
      • +
      • +E2E_REALM (default: default)
      • +
      +
    • +
    -
    +

    </details>

    -[gh-discussions]: https://github.com/ruby-oauth/oauth2/discussions +

    If it seems like you are in the wrong place, you might try one of these:

    -### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/oauth2)](https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=readme) + -Available as part of the Tidelift Subscription. +

    💡 Info you can shake a stick at

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Tokens to Remember +Gem name Gem namespace +
    Works with JRuby +JRuby 9.1 Compat JRuby 9.2 Compat JRuby 9.3 Compat
    JRuby 9.4 Compat JRuby 10.0 Compat JRuby HEAD Compat +
    Works with Truffle Ruby +Truffle Ruby 22.3 Compat Truffle Ruby 23.0 Compat
    Truffle Ruby 23.1 Compat Truffle Ruby 24.1 Compat +
    Works with MRI Ruby 3 +Ruby 3.0 Compat Ruby 3.1 Compat Ruby 3.2 Compat Ruby 3.3 Compat Ruby 3.4 Compat Ruby HEAD Compat +
    Works with MRI Ruby 2 +Ruby 2.2 Compat
    Ruby 2.3 Compat Ruby 2.4 Compat Ruby 2.5 Compat Ruby 2.6 Compat Ruby 2.7 Compat +
    Support & Community +Join Me on Daily.dev's RubyFriends Live Chat on Discord Get help from me on Upwork Get help from me on Codementor +
    Source +Source on GitLab.com Source on CodeBerg.org Source on Github.com The best SHA: dQw4w9WgXcQ! +
    Documentation +Current release on RubyDoc.info YARD on Galtzo.com Maintainer Blog GitLab Wiki GitHub Wiki +
    Compliance +License: MIT Compatible with Apache Software Projects: Verified by SkyWalking Eyes 📄ilo-declaration-img Security Policy Contributor Covenant 2.1 SemVer 2.0.0 +
    Style +Enforced Code Style Linter Keep-A-Changelog 1.0.0 Gitmoji Commits Compatibility appraised by: appraisal2 +
    Maintainer 🎖️ +Follow Me on LinkedIn Follow Me on Ruby.Social Follow Me on Bluesky Contact Maintainer My technical writing +
    +... 💖 +Find Me on WellFound: Find Me on CrunchBase My LinkTree More About Me 🧊 🐙 🛖 🧪 +
    + +

    Compatibility

    + +

    Compatible with MRI Ruby 2.2.0+, and concordant releases of JRuby, and TruffleRuby.

    + + + + + + + + + + + + + + +
    🚚 Amazing test matrix was brought to you by🔎 appraisal2 🔎 and the color 💚 green 💚
    👟 Check it out!github.com/appraisal-rb/appraisal2
    + +

    Federated DVCS

    - Need enterprise-level guarantees? - -The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. - -[![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift] - -- 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies -- 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar] -- 💡Tidelift pays maintainers to maintain the software you depend on!
    📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers - -Alternatively: - -- [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] -- [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] -- [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] + Find this repo on federated forges (Coming soon!) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Federated DVCS RepositoryStatusIssuesPRsWikiCIDiscussions
    🧪 ruby-oauth/oauth2 on GitLab +The Truth💚💚💚🐭 Tiny Matrix
    🧊 ruby-oauth/oauth2 on CodeBerg +An Ethical Mirror (Donate)💚💚⭕️ No Matrix
    🐙 ruby-oauth/oauth2 on GitHub +Another Mirror💚💚💚💯 Full Matrix💚
    🎮️ Discord Server +Live Chat on DiscordLet’stalkaboutthislibrary!
    -## 🚀 Release Documentation +

    Enterprise Support Tidelift +

    -### Version 2.0.x +

    Available as part of the Tidelift Subscription.

    - 2.0.x CHANGELOG and README - -| Version | Release Date | CHANGELOG | README | -|---------|--------------|---------------------------------------|---------------------------------| -| 2.0.17 | 2025-09-15 | [v2.0.17 CHANGELOG][2.0.17-changelog] | [v2.0.17 README][2.0.17-readme] | -| 2.0.16 | 2025-09-14 | [v2.0.16 CHANGELOG][2.0.16-changelog] | [v2.0.16 README][2.0.16-readme] | -| 2.0.15 | 2025-09-08 | [v2.0.15 CHANGELOG][2.0.15-changelog] | [v2.0.15 README][2.0.15-readme] | -| 2.0.14 | 2025-08-31 | [v2.0.14 CHANGELOG][2.0.14-changelog] | [v2.0.14 README][2.0.14-readme] | -| 2.0.13 | 2025-08-30 | [v2.0.13 CHANGELOG][2.0.13-changelog] | [v2.0.13 README][2.0.13-readme] | -| 2.0.12 | 2025-05-31 | [v2.0.12 CHANGELOG][2.0.12-changelog] | [v2.0.12 README][2.0.12-readme] | -| 2.0.11 | 2025-05-23 | [v2.0.11 CHANGELOG][2.0.11-changelog] | [v2.0.11 README][2.0.11-readme] | -| 2.0.10 | 2025-05-17 | [v2.0.10 CHANGELOG][2.0.10-changelog] | [v2.0.10 README][2.0.10-readme] | -| 2.0.9 | 2022-09-16 | [v2.0.9 CHANGELOG][2.0.9-changelog] | [v2.0.9 README][2.0.9-readme] | -| 2.0.8 | 2022-09-01 | [v2.0.8 CHANGELOG][2.0.8-changelog] | [v2.0.8 README][2.0.8-readme] | -| 2.0.7 | 2022-08-22 | [v2.0.7 CHANGELOG][2.0.7-changelog] | [v2.0.7 README][2.0.7-readme] | -| 2.0.6 | 2022-07-13 | [v2.0.6 CHANGELOG][2.0.6-changelog] | [v2.0.6 README][2.0.6-readme] | -| 2.0.5 | 2022-07-07 | [v2.0.5 CHANGELOG][2.0.5-changelog] | [v2.0.5 README][2.0.5-readme] | -| 2.0.4 | 2022-07-01 | [v2.0.4 CHANGELOG][2.0.4-changelog] | [v2.0.4 README][2.0.4-readme] | -| 2.0.3 | 2022-06-28 | [v2.0.3 CHANGELOG][2.0.3-changelog] | [v2.0.3 README][2.0.3-readme] | -| 2.0.2 | 2022-06-24 | [v2.0.2 CHANGELOG][2.0.2-changelog] | [v2.0.2 README][2.0.2-readme] | -| 2.0.1 | 2022-06-22 | [v2.0.1 CHANGELOG][2.0.1-changelog] | [v2.0.1 README][2.0.1-readme] | -| 2.0.0 | 2022-06-21 | [v2.0.0 CHANGELOG][2.0.0-changelog] | [v2.0.0 README][2.0.0-readme] | - -
    - -[2.0.17-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2017---2025-09-15 -[2.0.16-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2016---2025-09-14 -[2.0.15-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2015---2025-09-08 -[2.0.14-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2014---2025-08-31 -[2.0.13-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2013---2025-08-30 -[2.0.12-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2012---2025-05-31 -[2.0.11-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2011---2025-05-23 -[2.0.10-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2010---2025-05-17 -[2.0.9-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#209---2022-09-16 -[2.0.8-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#208---2022-09-01 -[2.0.7-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#207---2022-08-22 -[2.0.6-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#206---2022-07-13 -[2.0.5-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#205---2022-07-07 -[2.0.4-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#204---2022-07-01 -[2.0.3-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#203---2022-06-28 -[2.0.2-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#202---2022-06-24 -[2.0.1-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#201---2022-06-22 -[2.0.0-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#200---2022-06-21 - -[2.0.17-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.17/README.md -[2.0.16-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.16/README.md -[2.0.15-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.15/README.md -[2.0.14-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.14/README.md -[2.0.13-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.13/README.md -[2.0.12-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.12/README.md -[2.0.11-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.11/README.md -[2.0.10-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.10/README.md -[2.0.9-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.9/README.md -[2.0.8-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.8/README.md -[2.0.7-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.7/README.md -[2.0.6-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.6/README.md -[2.0.5-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.5/README.md -[2.0.4-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.4/README.md -[2.0.3-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.3/README.md -[2.0.2-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.2/README.md -[2.0.1-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.1/README.md -[2.0.0-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.0/README.md - -### Older Releases - -
    - 1.4.x CHANGELOGs and READMEs - -| Version | Release Date | CHANGELOG | README | -|---------|--------------|---------------------------------------|---------------------------------| -| 1.4.11 | Sep 16, 2022 | [v1.4.11 CHANGELOG][1.4.11-changelog] | [v1.4.11 README][1.4.11-readme] | -| 1.4.10 | Jul 1, 2022 | [v1.4.10 CHANGELOG][1.4.10-changelog] | [v1.4.10 README][1.4.10-readme] | -| 1.4.9 | Feb 20, 2022 | [v1.4.9 CHANGELOG][1.4.9-changelog] | [v1.4.9 README][1.4.9-readme] | -| 1.4.8 | Feb 18, 2022 | [v1.4.8 CHANGELOG][1.4.8-changelog] | [v1.4.8 README][1.4.8-readme] | -| 1.4.7 | Mar 19, 2021 | [v1.4.7 CHANGELOG][1.4.7-changelog] | [v1.4.7 README][1.4.7-readme] | -| 1.4.6 | Mar 19, 2021 | [v1.4.6 CHANGELOG][1.4.6-changelog] | [v1.4.6 README][1.4.6-readme] | -| 1.4.5 | Mar 18, 2021 | [v1.4.5 CHANGELOG][1.4.5-changelog] | [v1.4.5 README][1.4.5-readme] | -| 1.4.4 | Feb 12, 2020 | [v1.4.4 CHANGELOG][1.4.4-changelog] | [v1.4.4 README][1.4.4-readme] | -| 1.4.3 | Jan 29, 2020 | [v1.4.3 CHANGELOG][1.4.3-changelog] | [v1.4.3 README][1.4.3-readme] | -| 1.4.2 | Oct 1, 2019 | [v1.4.2 CHANGELOG][1.4.2-changelog] | [v1.4.2 README][1.4.2-readme] | -| 1.4.1 | Oct 13, 2018 | [v1.4.1 CHANGELOG][1.4.1-changelog] | [v1.4.1 README][1.4.1-readme] | -| 1.4.0 | Jun 9, 2017 | [v1.4.0 CHANGELOG][1.4.0-changelog] | [v1.4.0 README][1.4.0-readme] | -
    + Need enterprise-level guarantees? -[1.4.11-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1411---2022-09-16 -[1.4.10-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1410---2022-07-01 -[1.4.9-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#149---2022-02-20 -[1.4.8-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#148---2022-02-18 -[1.4.7-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#147---2021-03-19 -[1.4.6-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#146---2021-03-19 -[1.4.5-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#145---2021-03-18 -[1.4.4-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#144---2020-02-12 -[1.4.3-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#143---2020-01-29 -[1.4.2-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#142---2019-10-01 -[1.4.1-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#141---2018-10-13 -[1.4.0-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#140---2017-06-09 - -[1.4.11-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.11/README.md -[1.4.10-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.10/README.md -[1.4.9-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.9/README.md -[1.4.8-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.8/README.md -[1.4.7-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.7/README.md -[1.4.6-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.6/README.md -[1.4.5-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.5/README.md -[1.4.4-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.4/README.md -[1.4.3-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.3/README.md -[1.4.2-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.2/README.md -[1.4.1-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.1/README.md -[1.4.0-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.0/README.md +

    The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.

    -
    - 1.3.x Readmes +

    Get help from me on Tidelift

    -| Version | Release Date | Readme | -|---------|--------------|--------------------------------------------------------------| -| 1.3.1 | Mar 3, 2017 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.3.1/README.md | -| 1.3.0 | Dec 27, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.3.0/README.md | +
      +
    • 💡Subscribe for support guarantees covering all your FLOSS dependencies
    • +
    • 💡Tidelift is part of Sonar +
    • +
    • 💡Tidelift pays maintainers to maintain the software you depend on!
      📊@Pointy Haired Boss: An enterprise support subscription is “never gonna let you down”, and supports open source maintainers
    • +
    -
    +

    Alternatively:

    -
    - ≤= 1.2.x Readmes (2016 and before) - -| Version | Release Date | Readme | -|---------|--------------|--------------------------------------------------------------| -| 1.2.0 | Jun 30, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.2.0/README.md | -| 1.1.0 | Jan 30, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.1.0/README.md | -| 1.0.0 | May 23, 2014 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.0.0/README.md | -| < 1.0.0 | Find here | https://gitlab.com/ruby-oauth/oauth2/-/tags | +
      +
    • Live Chat on Discord
    • +
    • Get help from me on Upwork
    • +
    • Get help from me on Codementor
    • +
    -## ✨ Installation +

    ✨ Installation

    -Install the gem and add to the application's Gemfile by executing: +

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

    -```console +

    console bundle add oauth2 -``` +

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

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

    -```console +

    console gem install oauth2 -``` +

    -### 🔒 Secure Installation +

    🔒 Secure Installation

    For Medium or High Security Installations -This gem is cryptographically signed and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by -[stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with -by following the instructions below. +

    This gem is cryptographically signed, and has verifiable SHA-256 and SHA-512 checksums by +stone_checksums. Be sure the gem you install hasn’t been tampered with +by following the instructions below.

    -Add my public key (if you haven’t already; will expire 2045-04-29) as a trusted certificate: +

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

    -```console +

    console 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: +

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

    -```console +

    console gem install oauth2 -P MediumSecurity -``` +

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

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

    -This is necessary because not all of `oauth2`’s dependencies are signed, so we cannot use `HighSecurity`. +

    This is necessary because not all of oauth2’s dependencies are signed, so we cannot use HighSecurity.

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

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

    -```console +

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

    -NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine. +

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

    + +

    NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.

    -## What is new for v2.0? - -- Works with Ruby versions >= 2.2 -- Drop support for the expired MAC Draft (all versions) -- Support IETF rfc7515 JSON Web Signature - JWS (since v2.0.12) - - Support JWT `kid` for key discovery and management -- Support IETF rfc7523 JWT Bearer Tokens (since v2.0.0) -- Support IETF rfc7231 Relative Location in Redirect (since v2.0.0) -- Support IETF rfc6749 Don't set oauth params when nil (since v2.0.0) -- Support IETF rfc7009 Token Revocation (since v2.0.10, updated in v2.0.13 to support revocation via URL-encoded parameters) -- Support [OIDC 1.0 Private Key JWT](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication); based on the OAuth JWT assertion specification [(RFC 7523)](https://tools.ietf.org/html/rfc7523) -- Support new formats, including from [jsonapi.org](http://jsonapi.org/format/): `application/vdn.api+json`, `application/vnd.collection+json`, `application/hal+json`, `application/problem+json` -- Adds option to `OAuth2::Client#get_token`: - - `:access_token_class` (`AccessToken`); user specified class to use for all calls to `get_token` -- Adds option to `OAuth2::AccessToken#initialize`: - - `:expires_latency` (`nil`); number of seconds by which AccessToken validity will be reduced to offset latency -- By default, keys are transformed to snake case. - - Original keys will still work as previously, in most scenarios, thanks to [snaky_hash][snaky_hash] gem. - - However, this is a _breaking_ change if you rely on `response.parsed.to_h` to retain the original case, and the original wasn't snake case, as the keys in the result will be snake case. - - As of version 2.0.4 you can turn key transformation off with the `snaky: false` option. -- By default, the `:auth_scheme` is now `:basic_auth` (instead of `:request_body`) - - Third-party strategies and gems may need to be updated if a provider was requiring client id/secret in the request body -- [... A lot more](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md#200-2022-06-21-tag) - -[snaky_hash]: https://gitlab.com/ruby-oauth/snaky_hash - -## 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. +

    What is new for v2.0?

    + +
      +
    • Works with Ruby versions >= 2.2
    • +
    • Drop support for the expired MAC Draft (all versions)
    • +
    • Support IETF rfc7515 JSON Web Signature - JWS (since v2.0.12) +
        +
      • Support JWT kid for key discovery and management
      • +
      +
    • +
    • Support IETF rfc7523 JWT Bearer Tokens (since v2.0.0)
    • +
    • Support IETF rfc7231 Relative Location in Redirect (since v2.0.0)
    • +
    • Support IETF rfc6749 Don’t set oauth params when nil (since v2.0.0)
    • +
    • Support IETF rfc7009 Token Revocation (since v2.0.10, updated in v2.0.13 to support revocation via URL-encoded parameters)
    • +
    • Support OIDC 1.0 Private Key JWT; based on the OAuth JWT assertion specification (RFC 7523) +
    • +
    • Support new formats, including from jsonapi.org: application/vdn.api+json, application/vnd.collection+json, application/hal+json, application/problem+json +
    • +
    • Adds option to OAuth2::Client#get_token: +
        +
      • +:access_token_class (AccessToken); user specified class to use for all calls to get_token +
      • +
      +
    • +
    • Adds option to OAuth2::AccessToken#initialize: +
        +
      • +:expires_latency (nil); number of seconds by which AccessToken validity will be reduced to offset latency
      • +
      +
    • +
    • By default, keys are transformed to snake case. +
        +
      • Original keys will still work as previously, in most scenarios, thanks to snaky_hash gem.
      • +
      • However, this is a breaking change if you rely on response.parsed.to_h to retain the original case, and the original wasn’t snake case, as the keys in the result will be snake case.
      • +
      • As of version 2.0.4 you can turn key transformation off with the snaky: false option.
      • +
      +
    • +
    • By default, the :auth_scheme is now :basic_auth (instead of :request_body) +
        +
      • Third-party strategies and gems may need to be updated if a provider was requiring client id/secret in the request body
      • +
      +
    • +
    • … A lot more
    • +
    + +

    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. -See `1-4-stable` branch for older rubies. +See 1-4-stable branch for older rubies.

    -
    - Ruby Engine Compatibility Policy +

    <details markdown=”1>

    +Ruby Engine Compatibility Policy -This gem is tested against MRI, JRuby, and Truffleruby. +

    This gem is tested against MRI, JRuby, and Truffleruby. Each of those has varying versions that target a specific version of MRI Ruby. This gem should work in the just-listed Ruby engines according to the targeted MRI compatibility in the table below. If you would like to add support for additional engines, -see [gemfiles/README.md](gemfiles/README.md), then submit a PR to the correct maintenance branch as according to the table below. -

    +see gemfiles/README.md, then submit a PR to the correct maintenance branch as according to the table below.

    -
    - Ruby Version Compatibility Policy +

    </details>

    + +

    <details markdown=”1>

    +Ruby Version Compatibility Policy -If something doesn't work on one of these interpreters, it's a bug. +

    If something doesn’t work on one of these interpreters, it’s a bug.

    -This library may inadvertently work (or seem to work) on other Ruby +

    This library may inadvertently work (or seem to work) on other Ruby implementations; however, support will only be provided for the versions listed -above. +above.

    -If you would like this library to support another Ruby version, you may +

    If you would like this library to support another Ruby version, you may volunteer to be a maintainer. Being a maintainer entails making sure all tests run and pass on that implementation. When something breaks on your implementation, you will be responsible for providing patches in a timely fashion. If critical issues for a particular implementation exist at the time -of a major release, support for that Ruby version may be dropped. -

    - -| | Ruby OAuth2 Version | Maintenance Branch | Targeted Support | Best Effort Support | Incidental Support | -|:----|---------------------|--------------------|----------------------|-------------------------|------------------------------| -| 1️⃣ | 2.0.x | `main` | 3.2, 3.3, 3.4 | 2.5, 2.6, 2.7, 3.0, 3.1 | 2.2, 2.3, 2.4 | -| 2️⃣ | 1.4.x | `1-4-stable` | 3.2, 3.3, 3.4 | 2.5, 2.6, 2.7, 3.0, 3.1 | 1.9, 2.0, 2.1, 2.2, 2.3, 2.4 | -| 3️⃣ | older | N/A | Best of luck to you! | Please upgrade! | | - -NOTE: The 1.4 series will only receive critical security updates. -See [SECURITY.md][🔐security] and [IRP.md][🔐irp]. - -## ⚙️ Configuration - -You can turn on additional warnings. - -```ruby +of a major release, support for that Ruby version may be dropped.

    + +

    </details>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     Ruby OAuth2 VersionMaintenance BranchTargeted SupportBest Effort SupportIncidental Support
    1️⃣2.0.xmain3.2, 3.3, 3.42.5, 2.6, 2.7, 3.0, 3.12.2, 2.3, 2.4
    2️⃣1.4.x1-4-stable3.2, 3.3, 3.42.5, 2.6, 2.7, 3.0, 3.11.9, 2.0, 2.1, 2.2, 2.3, 2.4
    3️⃣olderN/ABest of luck to you!Please upgrade! 
    + +

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

    + +

    ⚙️ Configuration

    + +

    You can turn on additional warnings.

    + +

    ruby OAuth2.configure do |config| # Turn on a warning like: # OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key @@ -577,54 +633,56 @@

    Quick Examples

    # 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. +

    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. +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 -the `token_name` 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 +the token_name option.

    -You'll likely need to do some source diving. +

    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! +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!) +

    +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" +

    ```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”

    -access = client.auth_code.get_token("authorization_code_value", redirect_uri: "http://localhost:8080/oauth2/callback", headers: => "Basic some_password") -response = access.get("/api/resource", params: => "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. +

    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 +

    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" -``` +

    -### 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: +

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

    -```ruby +

    ruby client = OAuth2::Client.new( "client_id", "client_secret", @@ -637,105 +695,109 @@

    Quick Examples

    # => "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: => "bar") +

    ruby +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) # => "additionalData"=>"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 +

    Serialization

    -As of v2.0.11, if you need to serialize the parsed result, you can! +

    As of v2.0.11, if you need to serialize the parsed result, you can!

    -There are two ways to do this, globally, or discretely. The discrete way is recommended. +

    There are two ways to do this, globally, or discretely. The discrete way is recommended.

    -##### Global Serialization Config +
    Global Serialization Config
    -Globally configure `SnakyHash::StringKeyed` to use the serializer. Put this in your code somewhere reasonable (like an initializer for Rails). +

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

    -```ruby +

    ruby SnakyHash::StringKeyed.class_eval do extend SnakyHash::Serializer end -``` +

    -##### Discrete Serialization Config +
    Discrete Serialization Config
    -Discretely configure a custom Snaky Hash class to use the serializer. +

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

    -```ruby +

    ```ruby class MySnakyHash < SnakyHash::StringKeyed - # Give this hash class `dump` and `load` abilities! + # 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") -token = client.get_token(MySnakyHash) -``` +

    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 +
    Serialization Extensions
    -These extensions work regardless of whether you used the global or discrete config above. +

    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. +

    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. -See [response_spec.rb](https://github.com/ruby-oauth/oauth2/blob/main/spec/oauth2/response_spec.rb) if you need to study the hacks for older Rubies. +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.

    + +

    <details markdown=”1>

    +See Examples -```ruby +

    ```ruby class MySnakyHash < SnakyHash::StringKeyed - # Give this hash class `dump` and `load` abilities! - extend SnakyHash::Serializer + # 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 @@ -744,35 +806,35 @@

    Quick Examples

    end end end -``` +```

    -See [response_spec.rb](https://github.com/ruby-oauth/oauth2/blob/main/spec/oauth2/response_spec.rb), or the [ruby-oauth/snaky_hash](https://gitlab.com/ruby-oauth/snaky_hash) gem for more ideas. +

    </details>

    -#### Prefer camelCase over snake_case? => snaky: false +

    Prefer camelCase over snake_case? => snaky: false

    -```ruby -response = access.get("/api/resource", params: => "bar", snaky: false) -JSON.parse(response.body) # => "additionalData"=>"additional" -response.parsed # => "additionalData"=>"additional" +

    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.parsed.class.name # => Hash (just, regular old Hash) -``` +

    Debugging & Logging -Set an environment variable as per usual (e.g. with [dotenv](https://github.com/bkeepers/dotenv)). +

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

    -```ruby +

    ruby # 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. +

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

    -```ruby +

    ruby require "oauth2" client = OAuth2::Client.new( "client_id", @@ -780,304 +842,377 @@

    Quick Examples

    site: "https://example.org", logger: Logger.new("example.log", "weekly"), ) -``` +

    +
    -### OAuth2::Response +

    OAuth2::Response

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

    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`](https://gitlab.com/ruby-oauth/snaky_hash/-/blob/main/lib/snaky_hash/string_keyed.rb) if the `Content-Type` is `application/x-www-form-urlencoded` or if +

    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. +array. Otherwise, it will return the original body string.

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

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

    -### OAuth2::AccessToken +

    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 -`application/x-www-form-urlencoded` encoded string of the values). +

    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):

    + +
      +
    • + + + + + + + +
      +expires_latency (Integernil): Seconds to subtract from expires_in when computing #expired? to offset latency.
      +
    • +
    • + + + + + + + + +
      +token_name (StringSymbolnil): When multiple token-like fields exist in responses, select the field name to use as the access token (since v2.0.10).
      +
    • +
    • + + + + + + + + +
      +mode (SymbolProcHash): Controls how the token is transmitted on requests made via this AccessToken instance.
      +
        +
      • +:header — Send as Authorization: Bearer header (default and preferred by OAuth 2.1 draft guidance). +
      • +
      • +:query — Send as access_token query parameter (discouraged in general, but required by some providers).
      • +
      • Verb-dependent (since v2.0.15): Provide either: +
          +
        • a Proc taking |verb| and returning :header or :query, or
        • +
        • a Hash with verb symbols as keys, for example {get: :query, post: :header, delete: :header}.
        • +
        +
      • +
      +
    • +
    -Options (since v2.0.x unless noted): -- `expires_latency` (Integer | nil): Seconds to subtract from expires_in when computing #expired? to offset latency. -- `token_name` (String | Symbol | nil): When multiple token-like fields exist in responses, select the field name to use as the access token (since v2.0.10). -- `mode` (Symbol | Proc | Hash): Controls how the token is transmitted on requests made via this AccessToken instance. - - `:header` — Send as Authorization: Bearer header (default and preferred by OAuth 2.1 draft guidance). - - `:query` — Send as access_token query parameter (discouraged in general, but required by some providers). - - Verb-dependent (since v2.0.15): Provide either: - - a `Proc` taking `|verb|` and returning `:header` or `:query`, or - - a `Hash` with verb symbols as keys, for example `:query, post: :header, delete: :header`. +

    Note: Verb-dependent mode supports providers like Instagram that require query mode for GET and header mode for POST/DELETE

    -Note: Verb-dependent mode supports providers like Instagram that require query mode for `GET` and header mode for `POST`/`DELETE` -- Verb-dependent mode via `Proc` was added in v2.0.15 -- Verb-dependent mode via `Hash` was added in v2.0.16 +
      +
    • Verb-dependent mode via Proc was added in v2.0.15
    • +
    • Verb-dependent mode via Hash was added in v2.0.16
    • +
    -### OAuth2::Error +

    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 -always contain the `OAuth2::Response` instance. +

    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` +

    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 - -Note on OAuth 2.1 (draft): -- PKCE is required for all OAuth clients using the authorization code flow (especially public clients). Implement PKCE in your app when required by your provider. See RFC 7636 and RFC 8252. -- Redirect URIs must be compared using exact string matching by the Authorization Server. -- The Implicit grant (response_type=token) and the Resource Owner Password Credentials grant are omitted from OAuth 2.1; they remain here for OAuth 2.0 compatibility but should be avoided for new apps. -- Bearer tokens in the query string are omitted due to security risks; prefer Authorization header usage. -- Refresh tokens for public clients must either be sender-constrained (e.g., DPoP/MTLS) or one-time use. -- The definitions of public and confidential clients are simplified to refer only to whether the client has credentials. - -References: -- OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13 -- Aaron Parecki: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1 -- FusionAuth: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1 -- Okta: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs -- Video: https://www.youtube.com/watch?v=g_aVPdwBTfw -- 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 +Response instance will contain the OAuth2::Error instance.

    + +

    Authorization Grants

    + +

    Note on OAuth 2.1 (draft):

    + +
      +
    • PKCE is required for all OAuth clients using the authorization code flow (especially public clients). Implement PKCE in your app when required by your provider. See RFC 7636 and RFC 8252.
    • +
    • Redirect URIs must be compared using exact string matching by the Authorization Server.
    • +
    • The Implicit grant (response_type=token) and the Resource Owner Password Credentials grant are omitted from OAuth 2.1; they remain here for OAuth 2.0 compatibility but should be avoided for new apps.
    • +
    • Bearer tokens in the query string are omitted due to security risks; prefer Authorization header usage.
    • +
    • Refresh tokens for public clients must either be sender-constrained (e.g., DPoP/MTLS) or one-time use.
    • +
    • The definitions of public and confidential clients are simplified to refer only to whether the client has credentials.
    • +
    + +

    References:

    + +
      +
    • OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
    • +
    • Aaron Parecki: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1
    • +
    • FusionAuth: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1
    • +
    • Okta: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs
    • +
    • Video: https://www.youtube.com/watch?v=g_aVPdwBTfw
    • +
    • 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`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/auth_code.rb), -[`#implicit`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/implicit.rb), -[`#password`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/password.rb), -[`#client_credentials`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/client_credentials.rb), and -[`#assertion`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/assertion.rb) 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.implicit.authorize_url(redirect_uri: "http://localhost:8080/oauth/callback") +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.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 -request, add a 'headers' hash under 'params': +

    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: => "Header") -``` +

    ruby +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 -requests for tokens for any Authentication grant type. +

    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) +

    Common Flows (end-to-end)

    -- Authorization Code (server-side web app): +
      +
    • Authorization Code (server-side web app):
    • +
    -```ruby -require "oauth2" +

    ```ruby +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]) + 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 +```

    -# Step 3: call APIs -profile = access.get("/api/v1/me").parsed -``` - -- Client Credentials (machine-to-machine): +
      +
    • Client Credentials (machine-to-machine):
    • +
    -```ruby +

    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") -``` +

    -- Resource Owner Password (legacy; avoid when possible): +
      +
    • Resource Owner Password (legacy; avoid when possible):
    • +
    -```ruby +

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

    -#### Examples +

    Examples

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

    ```ruby # 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 ? => 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: -- Resource Owner Password Credentials (ROPC) is deprecated in OAuth 2.1 and discouraged. Prefer Authorization Code + PKCE. -- If your deployment strictly demands the X-XSRF-TOKEN header, first fetch it from an endpoint that sets the XSRF-TOKEN cookie (often "/" or a login page) and pass it to headers. -- For Basic auth, auth_scheme: :basic_auth handles the Authorization header; you do not need to base64-encode manually. +

    Notes:

    + +
      +
    • Resource Owner Password Credentials (ROPC) is deprecated in OAuth 2.1 and discouraged. Prefer Authorization Code + PKCE.
    • +
    • If your deployment strictly demands the X-XSRF-TOKEN header, first fetch it from an endpoint that sets the XSRF-TOKEN cookie (often “/” or a login page) and pass it to headers.
    • +
    • For Basic auth, auth_scheme: :basic_auth handles the Authorization header; you do not need to base64-encode manually.
    • +
    -### Instagram API (verb‑dependent token mode) +

    Instagram API (verb‑dependent token mode)

    + +

    Providers like Instagram require the access token to be sent differently depending on the HTTP verb:

    -Providers like Instagram require the access token to be sent differently depending on the HTTP verb: -- GET requests: token must be in the query string (?access_token=...) -- POST/DELETE requests: token must be in the Authorization header (Bearer ...) +
      +
    • GET requests: token must be in the query string (?access_token=…)
    • +
    • POST/DELETE requests: token must be in the Authorization header (Bearer …)
    • +
    -Since v2.0.15, you can configure an AccessToken with a verb‑dependent mode. The gem will choose how to send the token based on the request method. +

    Since v2.0.15, you can configure an AccessToken with a verb‑dependent mode. The gem will choose how to send the token based on the request method.

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

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

    -```ruby -require "oauth2" +

    ```ruby +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: :query, post: :header, delete: :header, -) + 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: :query, post: :header, delete: :header, -) + 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: "ig_refresh_token", + “/refresh_access_token”, + params: {grant_type: “ig_refresh_token”}, ) long_lived = OAuth2::AccessToken.new( client, - refresh_resp.parsed["access_token"], - mode: :query, post: :header, delete: :header, -) + 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: "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: "https://...", caption: "hello") -``` +# long_lived.post(“/me/media”, body: {image_url: “https://…”, caption: “hello”}) +```

    -Tips: -- Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET. -- If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }. +

    Tips:

    -### Refresh Tokens +
      +
    • Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET.
    • +
    • If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }.
    • +
    -When the server issues a refresh_token, you can refresh manually or implement an auto-refresh wrapper. +

    Refresh Tokens

    -- Manual refresh: +

    When the server issues a refresh_token, you can refresh manually or implement an auto-refresh wrapper.

    -```ruby +
      +
    • Manual refresh:
    • +
    + +

    ruby if access.expired? access = access.refresh end -``` +

    -- Auto-refresh wrapper pattern: +
      +
    • Auto-refresh wrapper pattern:
    • +
    -```ruby +

    ```ruby 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 @@ -1088,180 +1223,193 @@

    Quick Examples

    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)`. +

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

    -### Token Revocation (RFC 7009) +

    Token Revocation (RFC 7009)

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

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

    -```ruby +

    ```ruby # Revoke the current access token -access.revoke(token_type_hint: :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 +

    Client Configuration Tips

    -#### Mutual TLS (mTLS) client authentication +

    Mutual TLS (mTLS) client authentication

    -Some providers require OAuth requests (including the token request and subsequent API calls) to be sender‑constrained using mutual TLS (mTLS). With this gem, you enable mTLS by providing a client certificate/private key to Faraday via connection_opts.ssl and, if your provider requires it for client authentication, selecting the tls_client_auth auth_scheme. +

    Some providers require OAuth requests (including the token request and subsequent API calls) to be sender‑constrained using mutual TLS (mTLS). With this gem, you enable mTLS by providing a client certificate/private key to Faraday via connection_opts.ssl and, if your provider requires it for client authentication, selecting the tls_client_auth auth_scheme.

    -Example using PEM files (certificate and key): +

    Example using PEM files (certificate and key):

    -```ruby -require "oauth2" -require "openssl" +

    ```ruby +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

    + +

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

    +

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

    -# 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") -``` - -Notes: -- Files must contain the appropriate PEMs. The private key may be encrypted; if so, pass a password to OpenSSL::PKey::RSA.new(File.read(path), ENV["KEY_PASSWORD"]). -- If your certificate and key are in a PKCS#12/PFX bundle, you can load them like: - - p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"]) - - client_cert = p12.certificate; client_key = p12.key -- Server trust: - - If your environment does not have system CAs, specify ca_file or ca_path inside the ssl: hash. - - Keep verify: true in production. Set verify: false only for local testing. -- Faraday adapter: Any adapter that supports Ruby’s OpenSSL should work. net_http (default) and net_http_persistent are common choices. -- Scope of mTLS: The SSL client cert is applied to any HTTPS request made by this client (token and resource requests) to the configured site base URL (and absolute URLs you call with the same client). -- OIDC tie-in: Some OPs require tls_client_auth at the token endpoint per OIDC/OAuth specifications. That is enabled via auth_scheme: :tls_client_auth as shown above. - -#### Authentication schemes for the token request - -```ruby +

    Notes:

    + +
      +
    • Files must contain the appropriate PEMs. The private key may be encrypted; if so, pass a password to OpenSSL::PKey::RSA.new(File.read(path), ENV["KEY_PASSWORD"]).
    • +
    • If your certificate and key are in a PKCS#12/PFX bundle, you can load them like: +
        +
      • p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"])
      • +
      • client_cert = p12.certificate; client_key = p12.key
      • +
      +
    • +
    • Server trust: +
        +
      • If your environment does not have system CAs, specify ca_file or ca_path inside the ssl: hash.
      • +
      • Keep verify: true in production. Set verify: false only for local testing.
      • +
      +
    • +
    • Faraday adapter: Any adapter that supports Ruby’s OpenSSL should work. net_http (default) and net_http_persistent are common choices.
    • +
    • Scope of mTLS: The SSL client cert is applied to any HTTPS request made by this client (token and resource requests) to the configured site base URL (and absolute URLs you call with the same client).
    • +
    • OIDC tie-in: Some OPs require tls_client_auth at the token endpoint per OIDC/OAuth specifications. That is enabled via auth_scheme: :tls_client_auth as shown above.
    • +
    + +

    Authentication schemes for the token request

    + +

    ruby OAuth2::Client.new( id, secret, 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: +

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

    -```ruby +

    ruby client = OAuth2::Client.new( id, secret, site: "https://provider.example.com", connection_opts: { - request: 5, timeout: 15, + request: {open_timeout: 5, timeout: 15}, proxy: ENV["HTTPS_PROXY"], - ssl: true, + ssl: {verify: true}, }, ) do |faraday| faraday.request(:url_encoded) # 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) +
    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. +

    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" +

    ```ruby +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: Faraday::FlatParamsEncoder, + request: {params_encoder: Faraday::FlatParamsEncoder}, }, ) 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: +

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

    -```ruby -conn = Faraday.new("https://api.example.com", request: Faraday::FlatParamsEncoder) -``` +

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

    -#### Redirection +

    Redirection

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

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

    -### Handling Responses and Errors +

    Handling Responses and Errors

    -- Parsing: +
      +
    • Parsing:
    • +
    -```ruby +

    ruby 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: +
      +
    • Error handling:
    • +
    -```ruby +

    ruby begin access.get("/v1/forbidden") rescue OAuth2::Error => e @@ -1269,150 +1417,155 @@

    Quick Examples

    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: +
      +
    • Disable raising on 4xx/5xx to inspect the response yourself:
    • +
    -```ruby +

    ruby 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 end -``` +

    -### Making Raw Token Requests +

    Making Raw Token Requests

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

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

    -```ruby +

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

    -### OpenID Connect (OIDC) Notes +

    OpenID Connect (OIDC) Notes

    -- If the token response includes an `id_token` (a JWT), this gem surfaces it but does not validate the signature. Use a JWT library and your provider's JWKs to verify it. -- For private_key_jwt client authentication, provide `auth_scheme: :private_key_jwt` and ensure your key configuration matches the provider requirements. -- See [OIDC.md](OIDC.md) for a more complete OIDC overview, example, and links to the relevant specifications. +
      +
    • If the token response includes an id_token (a JWT), this gem surfaces it but does not validate the signature. Use a JWT library and your provider’s JWKs to verify it.
    • +
    • For private_key_jwt client authentication, provide auth_scheme: :private_key_jwt and ensure your key configuration matches the provider requirements.
    • +
    • See OIDC.md for a more complete OIDC overview, example, and links to the relevant specifications.
    • +
    -### Debugging +

    Debugging

    -- Set environment variable `OAUTH_DEBUG=true` to enable verbose Faraday logging (uses the client-provided logger). -- To mirror a working curl request, ensure you set the same auth scheme, params, and content type. The Quick Example at the top shows a curl-to-ruby translation. +
      +
    • Set environment variable OAUTH_DEBUG=true to enable verbose Faraday logging (uses the client-provided logger).
    • +
    • To mirror a working curl request, ensure you set the same auth scheme, params, and content type. The Quick Example at the top shows a curl-to-ruby translation.
    • +
    ---- +
    -## 🦷 FLOSS Funding +

    🦷 FLOSS 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. +

    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 +

    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][🖇osc] is our preferred funding platform). +(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 -appreciate it if you suggest to your company to become a ruby-oauth sponsor.** +

    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][🖇sponsor], -[Liberapay][⛳liberapay], -[PayPal][🖇paypal], -[Open Collective][🖇osc] -and [Tidelift][🏙️entsup-tidelift]. +

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

    -| 📍 NOTE | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| If doing a sponsorship in the form of donation is problematic for your company
    from an accounting standpoint, we'd recommend the use of Tidelift,
    where you can get a support-like subscription instead. | + + + + + + + + + + + +
    📍 NOTE
    If doing a sponsorship in the form of donation is problematic for your company
    from an accounting standpoint, we’d recommend the use of Tidelift,
    where you can get a support-like subscription instead.
    -### Open Collective for Individuals +

    Open Collective for Individuals

    -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)] +

    Support us with a monthly donation and help us continue our activities. [Become a backer]

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

    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 +

    Open Collective for Organizations

    -Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/kettle-rb#sponsor)] +

    Become a sponsor and get your logo on our README on GitHub with a link to your site. [Become a sponsor]

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

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

    -No sponsors yet. Be the first! - -### Open Collective for Donors - - +

    No sponsors yet. Be the first! +

    - +

    Another way to support open-source

    -[kettle-readme-backers]: https://github.com/kettle-rb/kettle-dev/blob/main/exe/kettle-readme-backers +

    I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).

    -### Another way to support open-source +

    If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in bundle fund.

    -> How wonderful it is that nobody need wait a single moment before starting to improve the world.
    ->—Anne Frank +

    I’m developing a new library, floss_funding, designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.

    -I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions — totaling 79 hours of FLOSS coding over just the past seven days, a pretty regular week for me. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats). +

    Floss-Funding.dev: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags

    -If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`. +

    OpenCollective 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

    -I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look. +

    🔐 Security

    -**[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags** +

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

    -[![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon] +

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

    -## 🔐 Security +

    🤝 Contributing

    -To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). -Tidelift will coordinate the fix and disclosure. +

    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.

    -For more see [SECURITY.md][🔐security], [THREAT_MODEL.md][🔐threat-model], and [IRP.md][🔐irp]. +

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

    -## 🤝 Contributing +

    See CONTRIBUTING.md for more detailed instructions.

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

    🚀 Release Instructions

    -We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it. +

    See CONTRIBUTING.md.

    -See [CONTRIBUTING.md][🤝contributing] for more detailed instructions. +

    Code Coverage

    -### 🚀 Release Instructions +

    Coverage Graph

    -See [CONTRIBUTING.md][🤝contributing]. +

    Coveralls Test Coverage

    -### Code Coverage +

    QLTY Test Coverage

    -[![Coverage Graph][🏀codecov-g]][🏀codecov] +

    🪇 Code of Conduct

    -[![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] +

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

    -[![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] +

    🌈 Contributors

    -### 🪇 Code of Conduct +

    Contributors

    -Everyone interacting with this project's codebases, issue trackers, -chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct]. +

    Made with contributors-img.

    -## 🌈 Contributors - -[![Contributors][🖐contributors-img]][🖐contributors] - -Made with [contributors-img][🖐contrib-rocks]. - -Also see GitLab Contributors: [https://gitlab.com/ruby-oauth/oauth2/-/graphs/main][🚎contributors-gl] +

    Also see GitLab Contributors: https://gitlab.com/ruby-oauth/oauth2/-/graphs/main

    ⭐️ Star History @@ -1427,55 +1580,58 @@

    Quick Examples

    -## 📌 Versioning +

    📌 Versioning

    -This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver]. +

    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. +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
    ->—Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking] +
    +

    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!"), +

    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][📌pvc] with two digits of precision. +the Pessimistic Version Constraint with two digits of precision.

    -For example: +

    For example:

    -```ruby +

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

    -📌 Is "Platform Support" part of the public API? More details inside. + 📌 Is "Platform Support" part of the public API? More details inside. -SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms -is a *breaking change* to an API. -It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless. +

    SemVer should, IMO, but doesn’t explicitly, say that dropping support for specific Platforms +is a breaking change to an API, and for that reason the bike shedding is endless.

    -To get a better understanding of how SemVer is intended to work over a project's lifetime, -read this article from the creator of SemVer: +

    To get a better understanding of how SemVer is intended to work over a project’s lifetime, +read this article from the creator of SemVer:

    -- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred] +
    -See [CHANGELOG.md][📌changelog] for a list of releases. +

    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] [![License: MIT][📄license-img]][📄license-ref]. -See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer]. +

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

    -### © Copyright +
    • - Copyright (c) 2017–2025 Peter H. Boling, of + Copyright (c) 2017 – 2025 Peter H. Boling, of Galtzo.com @@ -1484,243 +1640,30 @@

      Quick Examples

      , and oauth2 contributors.
    • - Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc. + Copyright (c) 2011 - 2013 Michael Bleigh and Intridea, Inc.
    -## 🤑 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 filled with many dozens of rejections, -I'm now spending ~60+ hours a week building open source tools. -I'm hoping to be able to pay for my kids' health insurance this month, +

    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. - -[![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] - -To say "thanks!" ☝️ Join the Discord or 👇️ send money. - -[![Sponsor ruby-oauth/oauth2 on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img] - -### Please give the project a star ⭐ ♥. - -Thanks for RTFM. ☺️ - -[⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat -[⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611 -[⛳liberapay]: https://liberapay.com/pboling/donate -[🖇osc-all-img]: https://img.shields.io/opencollective/all/ruby-oauth -[🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/ruby-oauth -[🖇osc-backers-img]: https://img.shields.io/opencollective/backers/ruby-oauth -[🖇osc-backers]: https://opencollective.com/ruby-oauth#backer -[🖇osc-backers-i]: https://opencollective.com/ruby-oauth/backers/badge.svg?style=flat -[🖇osc-sponsors]: https://opencollective.com/ruby-oauth#sponsor -[🖇osc-sponsors-i]: https://opencollective.com/ruby-oauth/sponsors/badge.svg?style=flat -[🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/ruby-oauth?style=for-the-badge -[🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/ruby-oauth?style=for-the-badge -[🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/ruby-oauth?style=for-the-badge -[🖇osc]: https://opencollective.com/ruby-oauth -[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github -[🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github -[🖇sponsor]: https://github.com/sponsors/pboling -[🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat -[🖇polar]: https://polar.sh/pboling -[🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat -[🖇kofi]: https://ko-fi.com/O5O86SNP4 -[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat -[🖇patreon]: https://patreon.com/galtzo -[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat -[🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff -[🖇buyme]: https://www.buymeacoffee.com/pboling -[🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal -[🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A -[🖇paypal]: https://www.paypal.com/paypalme/peterboling -[🖇floss-funding.dev]: https://floss-funding.dev -[🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding -[✉️discord-invite]: https://discord.gg/3qme4XHNKN -[✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord -[✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white -[✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends - -[⛳gg-discussions]: https://groups.google.com/g/oauth-ruby -[⛳gg-discussions-img]: https://img.shields.io/badge/google-group-0093D0.svg?style=for-the-badge&logo=google&logoColor=orange - -[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780 -[⛳️gem-namespace]: https://github.com/ruby-oauth/oauth2 -[⛳️namespace-img]: https://img.shields.io/badge/namespace-OAuth2-3C2D2D.svg?style=square&logo=ruby&logoColor=white -[⛳️gem-name]: https://rubygems.org/gems/oauth2 -[⛳️name-img]: https://img.shields.io/badge/name-oauth2-3C2D2D.svg?style=square&logo=rubygems&logoColor=red -[⛳️tag-img]: https://img.shields.io/github/tag/ruby-oauth/oauth2.svg -[⛳️tag]: http://github.com/ruby-oauth/oauth2/releases -[🚂maint-blog]: http://www.railsbling.com/tags/oauth2 -[🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange -[🚂maint-contact]: http://www.railsbling.com/contact -[🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red -[💖🖇linkedin]: http://www.linkedin.com/in/peterboling -[💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling -[💖✌️wellfound]: https://wellfound.com/u/peter-boling -[💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound -[💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling -[💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase -[💖🐘ruby-mast]: https://ruby.social/@galtzo -[💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo -[💖🦋bluesky]: https://bsky.app/profile/galtzo.com -[💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white -[💖🌳linktree]: https://linktr.ee/galtzo -[💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree -[💖💁🏼‍♂️devto]: https://dev.to/galtzo -[💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white -[💖💁🏼‍♂️aboutme]: https://about.me/peter.boling -[💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white -[💖🧊berg]: https://codeberg.org/pboling -[💖🐙hub]: https://github.org/pboling -[💖🛖hut]: https://sr.ht/~galtzo/ -[💖🧪lab]: https://gitlab.com/pboling -[👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share -[👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white -[👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github -[👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white -[🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=readme -[🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white -[🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar -[💁🏼‍♂️peterboling]: http://www.peterboling.com -[🚂railsbling]: http://www.railsbling.com -[📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange -[📜src-gl]: https://gitlab.com/ruby-oauth/oauth2/ -[📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue -[📜src-cb]: https://codeberg.org/ruby-oauth/oauth2 -[📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green -[📜src-gh]: https://github.com/ruby-oauth/oauth2 -[📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white -[📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white -[📜gl-wiki]: https://gitlab.com/ruby-oauth/oauth2/-/wikis/home -[📜gh-wiki]: https://github.com/ruby-oauth/oauth2/wiki -[📜gl-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white -[📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white -[👽dl-rank]: https://rubygems.org/gems/oauth2 -[👽dl-ranki]: https://img.shields.io/gem/rd/oauth2.svg -[👽oss-help]: https://www.codetriage.com/ruby-oauth/oauth2 -[👽oss-helpi]: https://www.codetriage.com/ruby-oauth/oauth2/badges/users.svg -[👽version]: https://rubygems.org/gems/oauth2 -[👽versioni]: https://img.shields.io/gem/v/oauth2.svg -[🏀qlty-mnt]: https://qlty.sh/gh/ruby-oauth/projects/oauth2 -[🏀qlty-mnti]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/maintainability.svg -[🏀qlty-cov]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/metrics/code?sort=coverageRating -[🏀qlty-covi]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/coverage.svg -[🏀codecov]: https://codecov.io/gh/ruby-oauth/oauth2 -[🏀codecovi]: https://codecov.io/gh/ruby-oauth/oauth2/graph/badge.svg -[🏀coveralls]: https://coveralls.io/github/ruby-oauth/oauth2?branch=main -[🏀coveralls-img]: https://coveralls.io/repos/github/ruby-oauth/oauth2/badge.svg?branch=main -[🖐codeQL]: https://github.com/ruby-oauth/oauth2/security/code-scanning -[🖐codeQL-img]: https://github.com/ruby-oauth/oauth2/actions/workflows/codeql-analysis.yml/badge.svg -[🚎1-an-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ancient.yml -[🚎1-an-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/ancient.yml/badge.svg -[🚎2-cov-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/coverage.yml -[🚎2-cov-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/coverage.yml/badge.svg -[🚎3-hd-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/heads.yml -[🚎3-hd-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/heads.yml/badge.svg -[🚎4-lg-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/legacy.yml -[🚎4-lg-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/legacy.yml/badge.svg -[🚎5-st-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/style.yml -[🚎5-st-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/style.yml/badge.svg -[🚎6-s-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/supported.yml -[🚎6-s-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/supported.yml/badge.svg -[🚎7-us-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/unsupported.yml -[🚎7-us-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/unsupported.yml/badge.svg -[🚎8-ho-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/hoary.yml -[🚎8-ho-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/hoary.yml/badge.svg -[🚎9-t-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml -[🚎9-t-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml/badge.svg -[🚎10-j-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml -[🚎10-j-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml/badge.svg -[🚎11-c-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml -[🚎11-c-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml/badge.svg -[🚎12-crh-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/dep-heads.yml -[🚎12-crh-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/dep-heads.yml/badge.svg -[🚎13-cbs-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/caboose.yml -[🚎13-cbs-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/caboose.yml/badge.svg -[🚎13-🔒️-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/locked_deps.yml -[🚎13-🔒️-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/locked_deps.yml/badge.svg -[🚎14-🔓️-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/unlocked_deps.yml -[🚎14-🔓️-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/unlocked_deps.yml/badge.svg -[🚎15-🪪-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml -[🚎15-🪪-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml/badge.svg -[💎ruby-2.2i]: https://img.shields.io/badge/Ruby-2.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.6i]: https://img.shields.io/badge/Ruby-2.6-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.7i]: https://img.shields.io/badge/Ruby-2.7-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-3.0i]: https://img.shields.io/badge/Ruby-3.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-3.1i]: https://img.shields.io/badge/Ruby-3.1-CC342D?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green -[💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue -[💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink -[💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink -[💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink -[💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green -[💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue -[💎jruby-9.1i]: https://img.shields.io/badge/JRuby-9.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red -[💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red -[💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red -[💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red -[💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green -[💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue -[🤝gh-issues]: https://github.com/ruby-oauth/oauth2/issues -[🤝gh-pulls]: https://github.com/ruby-oauth/oauth2/pulls -[🤝gl-issues]: https://gitlab.com/ruby-oauth/oauth2/-/issues -[🤝gl-pulls]: https://gitlab.com/ruby-oauth/oauth2/-/merge_requests -[🤝cb-issues]: https://codeberg.org/ruby-oauth/oauth2/issues -[🤝cb-pulls]: https://codeberg.org/ruby-oauth/oauth2/pulls -[🤝cb-donate]: https://donate.codeberg.org/ -[🤝contributing]: CONTRIBUTING.md -[🏀codecov-g]: https://codecov.io/gh/ruby-oauth/oauth2/graphs/tree.svg -[🖐contrib-rocks]: https://contrib.rocks -[🖐contributors]: https://github.com/ruby-oauth/oauth2/graphs/contributors -[🖐contributors-img]: https://contrib.rocks/image?repo=ruby-oauth/oauth2 -[🚎contributors-gl]: https://gitlab.com/ruby-oauth/oauth2/-/graphs/main -[🪇conduct]: CODE_OF_CONDUCT.md -[🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg -[📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint -[📌semver]: https://semver.org/spec/v2.0.0.html -[📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat -[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139 -[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html -[📌changelog]: CHANGELOG.md -[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ -[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat -[📌gitmoji]:https://gitmoji.dev -[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square -[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ -[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.526-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue -[🔐security]: SECURITY.md -[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat -[🔐irp]: IRP.md -[🔐irp-img]: https://img.shields.io/badge/IRP-259D6C.svg?style=flat -[🔐threat-model]: THREAT_MODEL.md -[🔐threat-model-img]: https://img.shields.io/badge/threat-model-259D6C.svg?style=flat -[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year -[📄license]: LICENSE.txt -[📄license-ref]: https://opensource.org/licenses/MIT -[📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg -[📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0 -[📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache -[📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm -[📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat -[🚎yard-current]: http://rubydoc.info/gems/oauth2 -[🚎yard-head]: https://oauth2.galtzo.com -[💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums -[💎SHA_checksums]: https://gitlab.com/ruby-oauth/oauth2/-/tree/main/checksums -[💎rlts]: https://github.com/rubocop-lts/rubocop-lts -[💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white -[💎appraisal2]: https://github.com/appraisal-rb/appraisal2 -[💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white -[💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/ +Please consider sponsoring me or the project.

    + +

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

    + +

    Live Chat on Discord

    + +

    To say “thanks!” ☝️ Join the Discord or 👇️ send money.

    + +

    Sponsor ruby-oauth/oauth2 on Open Source Collective 💌 Sponsor me on GitHub Sponsors 💌 Sponsor me on Liberapay 💌 Donate on PayPal

    + +

    Please give the project a star ⭐ ♥.

    + +

    Thanks for RTFM. ☺️

    @@ -1731,13 +1674,12 @@

    Quick Examples

    -
    diff --git a/docs/file.RUBOCOP.html b/docs/file.RUBOCOP.html index f8f449c1..fcb0033e 100644 --- a/docs/file.RUBOCOP.html +++ b/docs/file.RUBOCOP.html @@ -69,21 +69,20 @@

    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:

    -
    bundle exec rake rubocop_gradual:check
    -
    +

    bash +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

    @@ -122,7 +121,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:
        @@ -150,7 +149,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
        • @@ -161,9 +160,9 @@

          Benefits of rubocop_gradual

    diff --git a/docs/file.SECURITY.html b/docs/file.SECURITY.html index 21f8e0dd..bc43ae18 100644 --- a/docs/file.SECURITY.html +++ b/docs/file.SECURITY.html @@ -78,24 +78,24 @@

    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

    +

    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,
    -or find other sponsorship links in the README.

    +

    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 eabbf1c6..6307deac 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 Mon

    -

    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/file_list.html b/docs/file_list.html index 883a283c..4fec7a08 100644 --- a/docs/file_list.html +++ b/docs/file_list.html @@ -102,141 +102,6 @@

    File List

    -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - -
  • - -
  • - - diff --git a/docs/index.html b/docs/index.html index aa7dfaf6..76368c46 100644 --- a/docs/index.html +++ b/docs/index.html @@ -57,13 +57,53 @@
    -

    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

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    📍 NOTE
    RubyGems (the GitHub org, not the website) suffered a hostile takeover in September 2025.
    Ultimately 4 maintainers were hard removed and a reason has been given for only 1 of those, while 2 others resigned in protest.
    It is a complicated story which is difficult to parse quickly.
    I’m adding notes like this to gems because I don’t condone theft of repositories or gems from their rightful owners.
    If a similar theft happened with my repos/gems, I’d hope some would stand up for me.
    Disenfranchised former-maintainers have started gem.coop.
    Once available I will publish there exclusively; unless RubyCentral makes amends with the community.
    The “Technology for Humans: Joel Draper” podcast episode by reinteractive is the most cogent summary I’m aware of.
    See here, here and here for more info on what comes next.
    What I’m doing: A (WIP) proposal for bundler/gem scopes, and a (WIP) proposal for a federated gem server.
    + +

    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

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

    -

    [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] [![QLTY Maintainability][🏀qlty-mnti]][🏀qlty-mnt] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]

    +

    Version GitHub tag (latest SemVer) License: MIT Downloads Rank Open Source Helpers CodeCov Test Coverage Coveralls Test Coverage QLTY Test Coverage QLTY Maintainability CI Heads CI Runtime Dependencies @ HEAD CI Current CI Truffle Ruby CI JRuby Deps Locked Deps Unlocked CI Supported CI Legacy CI Unsupported CI Ancient CI Test Coverage CI Style CodeQL Apache SkyWalking Eyes License Compatibility Check

    if ci_badges.map(&:color).detect { it != "green"} ☝️ let me know, as I may have missed the discord notification.

    @@ -71,13 +111,13 @@

    🔐 OAuth 2.0 Authorization Framewor

    if ci_badges.map(&:color).all? { it == "green"} 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.

    -

    [![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate at ko-fi.com][🖇kofi-img]][🖇kofi]

    +

    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

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

    Quick Examples

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

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

    Quick Examples

    --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. +

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

    -```ruby +

    ruby OAuth2::Client.new( "REDMOND_CLIENT_ID", # client_id "REDMOND_CLIENT_SECRET", # client_secret @@ -107,469 +147,485 @@

    Quick Examples

    ). # 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") -``` +

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

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

    -
    +

    <details markdown=”1>

    Complete E2E single file script against mock-oauth2-server -- E2E example uses [navikt/mock-oauth2-server](https://github.com/navikt/mock-oauth2-server), which was added in v2.0.11 -- E2E example does not ship with the released gem, so clone the source to play with it. +
      +
    • E2E example uses navikt/mock-oauth2-server, which was added in v2.0.11
    • +
    • E2E example does not ship with the released gem, so clone the source to play with it.
    • +
    -```console +

    console 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: +

    The output should be something like this:

    -```console +

    console ➜ ruby examples/e2e.rb Access token (truncated): eyJraWQiOiJkZWZhdWx0... userinfo status: 200 -userinfo body: => "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: +

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

    -```console +

    console docker compose -f docker-compose-ssl.yml down -``` - -Troubleshooting: validate connectivity to the mock server - -- Check container status and port mapping: - - `docker compose -f docker-compose-ssl.yml ps` -- From the host, try the discovery URL directly (this is what the example uses by default): - - `curl -v http://localhost:8080/default/.well-known/openid-configuration` - - If that fails immediately, also try: `curl -v --connect-timeout 2 http://127.0.0.1:8080/default/.well-known/openid-configuration` -- From inside the container (to distinguish container vs. host networking): - - `docker exec -it oauth2-mock-oauth2-server-1 curl -v http://127.0.0.1:8080/default/.well-known/openid-configuration` -- Simple TCP probe from the host: - - `nc -vz localhost 8080 # or: ruby -rsocket -e 'TCPSocket.new("localhost",8080).close; puts "tcp ok"'` -- Inspect which host port 8080 is bound to (should be 8080): - - `docker inspect -f '(index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }' oauth2-mock-oauth2-server-1` -- Look at server logs for readiness/errors: - - `docker logs -n 200 oauth2-mock-oauth2-server-1` -- On Linux, ensure nothing else is bound to 8080 and that firewall/SELinux aren’t blocking: - - `ss -ltnp | grep :8080` - -Notes -- Discovery URL pattern is: `http://localhost:8080//.well-known/openid-configuration`, where `` defaults to `default`. -- You can change these with env vars when running the example: - - `E2E_ISSUER_BASE` (default: http://localhost:8080) - - `E2E_REALM` (default: default) - -</details> - -If it seems like you are in the wrong place, you might try one of these: - -* [OAuth 2.0 Spec][oauth2-spec] -* [doorkeeper gem][doorkeeper-gem] for OAuth 2.0 server/provider implementation. -* [oauth sibling gem][sibling-gem] for OAuth 1.0a implementations in Ruby. - -[oauth2-spec]: https://oauth.net/2/ -[sibling-gem]: https://gitlab.com/ruby-oauth/oauth -[doorkeeper-gem]: https://github.com/doorkeeper-gem/doorkeeper - -## 💡 Info you can shake a stick at - -| Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] | -|-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i]
    [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] | -| Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i]
    [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] | -| Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] | -| Works with MRI Ruby 2 | ![Ruby 2.2 Compat][💎ruby-2.2i]
    [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] | -| Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] | -| Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] | -| Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] | -| Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Incident Response Plan][🔐irp-img]][🔐irp] [![Security Policy][🔐security-img]][🔐security] [![Threat Model][🔐threat-model-img]][🔐threat-model] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] | -| Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] | -| Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] | -| `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] | - -### Compatibility - -* Operating Systems: Linux, macOS, Windows -* MRI Ruby @ v2.3, v2.4, v2.5, v2.6, v2.7, v3.0, v3.1, v3.2, v3.3, v3.4, HEAD - * NOTE: This gem may still _install_ and _run_ on ruby v2.2, but vanilla GitHub Actions no longer supports testing against it, so YMMV. Accept patches so long as they don't break the platforms that do run in CI. -* JRuby @ v9.4, v10.0, HEAD - * NOTE: This gem may still _install_ and _run_ on JRuby v9.2 and v9.3, but they are EOL, builds are flaky, and GitHub Actions [doesn't have][GHA-continue-on-error-ui] a proper [`allow-failures` feature][GHA-allow-failure], and until they do flaky EOL-platform builds get dropped, so YMMV. Accept patches so long as they don't break the platforms that do run in CI. -* TruffleRuby @ v23.1, v24.1, HEAD - * NOTE: This gem may still _install_ and _run_ on Truffleruby v22.3 and v23.0, but they are EOL, builds are flaky, and GitHub Actions [doesn't have][GHA-continue-on-error-ui] a proper [`allow-failures` feature][GHA-allow-failure], and until they do flaky EOL-platform builds get dropped, so YMMV. Accept patches so long as they don't break the platforms that do run in CI. -* gem `faraday` @ v0, v1, v2, HEAD ⏩️ [lostisland/faraday](https://github.com/lostisland/faraday) -* gem `jwt` @ v1, v2, v3, HEAD ⏩️ [jwt/ruby-jwt](https://github.com/jwt/ruby-jwt) -* gem `logger` @ v1.2, v1.5, v1.7, HEAD ⏩️ [ruby/logger](https://github.com/ruby/logger) -* gem `multi_xml` @ v0.5, v0.6, v0.7, HEAD ⏩️ [sferik/multi_xml](https://github.com/sferik/multi_xml) -* gem `rack` @ v1.2, v1.6, v2, v3, HEAD ⏩️ [rack/rack](https://github.com/rack/rack) -* gem `snaky_hash` @ v2, HEAD ⏩️ [ruby-oauth/snaky_hash](https://gitlab.com/ruby-oauth/snaky_hash) -* gem `version_gem` @ v1, HEAD ⏩️ [ruby-oauth/version_gem](https://gitlab.com/ruby-oauth/version_gem) - -The last two were extracted from this gem. They are part of the `ruby-oauth` org, -and are developed in tight collaboration with this gem. - -Also, where reasonable, tested against the runtime dependencies of those dependencies: - -* gem `hashie` @ v0, v1, v2, v3, v4, v5, HEAD ⏩️ [hashie/hashie](https://github.com/hashie/hashie) - -[GHA-continue-on-error-ui]: https://github.com/actions/runner/issues/2347#issuecomment-2653479732 -[GHA-allow-failure]: https://github.com/orgs/community/discussions/15452 - -#### Upgrading Runtime Gem Dependencies - -This project sits underneath a large portion of the authorization systems on the internet. -According to GitHub's project tracking, which I believe only reports on public projects, -[100,000+ projects](https://github.com/ruby-oauth/oauth2/network/dependents), and -[500+ packages](https://github.com/ruby-oauth/oauth2/network/dependents?dependent_type=PACKAGE) depend on this project. - -That means it is painful for the Ruby community when this gem forces updates to its runtime dependencies. - -As a result, great care, and a lot of time, have been invested to ensure this gem is working with all the -leading versions per each minor version of Ruby of all the runtime dependencies it can install with. - -What does that mean specifically for the runtime dependencies? - -We have 100% test coverage of lines and branches, and this test suite runs across a very large matrix. -It wouldn't be possible without appraisal2. - -| 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 | -|------------------------------------------------|--------------------------------------------------------| -| 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ | - -#### You should upgrade this gem with confidence\*. - -- This gem follows a _strict & correct_ (according to the maintainer of SemVer; [more info][sv-pub-api]) interpretation of SemVer. - - Dropping support for **any** of the runtime dependency versions above will be a major version bump. - - If you aren't on one of the minor versions above, make getting there a priority. -- You should upgrade the dependencies of this gem with confidence\*. -- Please do upgrade, and then, when it goes smooth as butter [please sponsor me][🖇sponsor]. Thanks! - -[sv-pub-api]: #-versioning - -\* MIT license; The only guarantees I make are for [enterprise support](#enterprise-support). +

    -
    - Standard Library Dependencies - -The various versions of each are tested via the Ruby test matrix, along with whatever Ruby includes them. - -* base64 -* cgi -* json -* time -* logger (removed from stdlib in Ruby 3.5 so added as runtime dependency in v2.0.10) +

    Troubleshooting: validate connectivity to the mock server

    -If you use a gem version of a core Ruby library, it should work fine! - -
    +
      +
    • Check container status and port mapping: +
        +
      • docker compose -f docker-compose-ssl.yml ps
      • +
      +
    • +
    • From the host, try the discovery URL directly (this is what the example uses by default): +
        +
      • curl -v http://localhost:8080/default/.well-known/openid-configuration
      • +
      • If that fails immediately, also try: curl -v --connect-timeout 2 http://127.0.0.1:8080/default/.well-known/openid-configuration +
      • +
      +
    • +
    • From inside the container (to distinguish container vs. host networking): +
        +
      • docker exec -it oauth2-mock-oauth2-server-1 curl -v http://127.0.0.1:8080/default/.well-known/openid-configuration
      • +
      +
    • +
    • Simple TCP probe from the host: +
        +
      • nc -vz localhost 8080 # or: ruby -rsocket -e 'TCPSocket.new("localhost",8080).close; puts "tcp ok"'
      • +
      +
    • +
    • Inspect which host port 8080 is bound to (should be 8080): +
        +
      • docker inspect -f '{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' oauth2-mock-oauth2-server-1
      • +
      +
    • +
    • Look at server logs for readiness/errors: +
        +
      • docker logs -n 200 oauth2-mock-oauth2-server-1
      • +
      +
    • +
    • On Linux, ensure nothing else is bound to 8080 and that firewall/SELinux aren’t blocking: +
        +
      • ss -ltnp | grep :8080
      • +
      +
    • +
    -### Federated DVCS +

    Notes

    -
    - Find this repo on federated forges - -| Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions | -|-----------------------------------------------|-----------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------| -| 🧪 [ruby-oauth/oauth2 on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ | -| 🧊 [ruby-oauth/oauth2 on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ | -| 🐙 [ruby-oauth/oauth2 on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] | -| 🤼 [OAuth Ruby Google Group][⛳gg-discussions] | "Active" | ➖ | ➖ | ➖ | ➖ | [💚][⛳gg-discussions] | -| 🎮️ [Discord Server][✉️discord-invite] | [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] | [Let's][✉️discord-invite] | [talk][✉️discord-invite] | [about][✉️discord-invite] | [this][✉️discord-invite] | [library!][✉️discord-invite] | +
      +
    • Discovery URL pattern is: http://localhost:8080/<realm>/.well-known/openid-configuration, where <realm> defaults to default.
    • +
    • You can change these with env vars when running the example: +
        +
      • +E2E_ISSUER_BASE (default: http://localhost:8080)
      • +
      • +E2E_REALM (default: default)
      • +
      +
    • +
    -
    +

    </details>

    -[gh-discussions]: https://github.com/ruby-oauth/oauth2/discussions +

    If it seems like you are in the wrong place, you might try one of these:

    -### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/oauth2)](https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=readme) + -Available as part of the Tidelift Subscription. +

    💡 Info you can shake a stick at

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Tokens to Remember +Gem name Gem namespace +
    Works with JRuby +JRuby 9.1 Compat JRuby 9.2 Compat JRuby 9.3 Compat
    JRuby 9.4 Compat JRuby 10.0 Compat JRuby HEAD Compat +
    Works with Truffle Ruby +Truffle Ruby 22.3 Compat Truffle Ruby 23.0 Compat
    Truffle Ruby 23.1 Compat Truffle Ruby 24.1 Compat +
    Works with MRI Ruby 3 +Ruby 3.0 Compat Ruby 3.1 Compat Ruby 3.2 Compat Ruby 3.3 Compat Ruby 3.4 Compat Ruby HEAD Compat +
    Works with MRI Ruby 2 +Ruby 2.2 Compat
    Ruby 2.3 Compat Ruby 2.4 Compat Ruby 2.5 Compat Ruby 2.6 Compat Ruby 2.7 Compat +
    Support & Community +Join Me on Daily.dev's RubyFriends Live Chat on Discord Get help from me on Upwork Get help from me on Codementor +
    Source +Source on GitLab.com Source on CodeBerg.org Source on Github.com The best SHA: dQw4w9WgXcQ! +
    Documentation +Current release on RubyDoc.info YARD on Galtzo.com Maintainer Blog GitLab Wiki GitHub Wiki +
    Compliance +License: MIT Compatible with Apache Software Projects: Verified by SkyWalking Eyes 📄ilo-declaration-img Security Policy Contributor Covenant 2.1 SemVer 2.0.0 +
    Style +Enforced Code Style Linter Keep-A-Changelog 1.0.0 Gitmoji Commits Compatibility appraised by: appraisal2 +
    Maintainer 🎖️ +Follow Me on LinkedIn Follow Me on Ruby.Social Follow Me on Bluesky Contact Maintainer My technical writing +
    +... 💖 +Find Me on WellFound: Find Me on CrunchBase My LinkTree More About Me 🧊 🐙 🛖 🧪 +
    + +

    Compatibility

    + +

    Compatible with MRI Ruby 2.2.0+, and concordant releases of JRuby, and TruffleRuby.

    + + + + + + + + + + + + + + +
    🚚 Amazing test matrix was brought to you by🔎 appraisal2 🔎 and the color 💚 green 💚
    👟 Check it out!github.com/appraisal-rb/appraisal2
    + +

    Federated DVCS

    - Need enterprise-level guarantees? - -The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. - -[![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift] - -- 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies -- 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar] -- 💡Tidelift pays maintainers to maintain the software you depend on!
    📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers - -Alternatively: - -- [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] -- [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] -- [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] + Find this repo on federated forges (Coming soon!) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Federated DVCS RepositoryStatusIssuesPRsWikiCIDiscussions
    🧪 ruby-oauth/oauth2 on GitLab +The Truth💚💚💚🐭 Tiny Matrix
    🧊 ruby-oauth/oauth2 on CodeBerg +An Ethical Mirror (Donate)💚💚⭕️ No Matrix
    🐙 ruby-oauth/oauth2 on GitHub +Another Mirror💚💚💚💯 Full Matrix💚
    🎮️ Discord Server +Live Chat on DiscordLet’stalkaboutthislibrary!
    -## 🚀 Release Documentation +

    Enterprise Support Tidelift +

    -### Version 2.0.x +

    Available as part of the Tidelift Subscription.

    - 2.0.x CHANGELOG and README - -| Version | Release Date | CHANGELOG | README | -|---------|--------------|---------------------------------------|---------------------------------| -| 2.0.17 | 2025-09-15 | [v2.0.17 CHANGELOG][2.0.17-changelog] | [v2.0.17 README][2.0.17-readme] | -| 2.0.16 | 2025-09-14 | [v2.0.16 CHANGELOG][2.0.16-changelog] | [v2.0.16 README][2.0.16-readme] | -| 2.0.15 | 2025-09-08 | [v2.0.15 CHANGELOG][2.0.15-changelog] | [v2.0.15 README][2.0.15-readme] | -| 2.0.14 | 2025-08-31 | [v2.0.14 CHANGELOG][2.0.14-changelog] | [v2.0.14 README][2.0.14-readme] | -| 2.0.13 | 2025-08-30 | [v2.0.13 CHANGELOG][2.0.13-changelog] | [v2.0.13 README][2.0.13-readme] | -| 2.0.12 | 2025-05-31 | [v2.0.12 CHANGELOG][2.0.12-changelog] | [v2.0.12 README][2.0.12-readme] | -| 2.0.11 | 2025-05-23 | [v2.0.11 CHANGELOG][2.0.11-changelog] | [v2.0.11 README][2.0.11-readme] | -| 2.0.10 | 2025-05-17 | [v2.0.10 CHANGELOG][2.0.10-changelog] | [v2.0.10 README][2.0.10-readme] | -| 2.0.9 | 2022-09-16 | [v2.0.9 CHANGELOG][2.0.9-changelog] | [v2.0.9 README][2.0.9-readme] | -| 2.0.8 | 2022-09-01 | [v2.0.8 CHANGELOG][2.0.8-changelog] | [v2.0.8 README][2.0.8-readme] | -| 2.0.7 | 2022-08-22 | [v2.0.7 CHANGELOG][2.0.7-changelog] | [v2.0.7 README][2.0.7-readme] | -| 2.0.6 | 2022-07-13 | [v2.0.6 CHANGELOG][2.0.6-changelog] | [v2.0.6 README][2.0.6-readme] | -| 2.0.5 | 2022-07-07 | [v2.0.5 CHANGELOG][2.0.5-changelog] | [v2.0.5 README][2.0.5-readme] | -| 2.0.4 | 2022-07-01 | [v2.0.4 CHANGELOG][2.0.4-changelog] | [v2.0.4 README][2.0.4-readme] | -| 2.0.3 | 2022-06-28 | [v2.0.3 CHANGELOG][2.0.3-changelog] | [v2.0.3 README][2.0.3-readme] | -| 2.0.2 | 2022-06-24 | [v2.0.2 CHANGELOG][2.0.2-changelog] | [v2.0.2 README][2.0.2-readme] | -| 2.0.1 | 2022-06-22 | [v2.0.1 CHANGELOG][2.0.1-changelog] | [v2.0.1 README][2.0.1-readme] | -| 2.0.0 | 2022-06-21 | [v2.0.0 CHANGELOG][2.0.0-changelog] | [v2.0.0 README][2.0.0-readme] | - -
    - -[2.0.17-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2017---2025-09-15 -[2.0.16-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2016---2025-09-14 -[2.0.15-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2015---2025-09-08 -[2.0.14-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2014---2025-08-31 -[2.0.13-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2013---2025-08-30 -[2.0.12-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2012---2025-05-31 -[2.0.11-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2011---2025-05-23 -[2.0.10-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2010---2025-05-17 -[2.0.9-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#209---2022-09-16 -[2.0.8-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#208---2022-09-01 -[2.0.7-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#207---2022-08-22 -[2.0.6-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#206---2022-07-13 -[2.0.5-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#205---2022-07-07 -[2.0.4-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#204---2022-07-01 -[2.0.3-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#203---2022-06-28 -[2.0.2-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#202---2022-06-24 -[2.0.1-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#201---2022-06-22 -[2.0.0-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#200---2022-06-21 - -[2.0.17-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.17/README.md -[2.0.16-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.16/README.md -[2.0.15-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.15/README.md -[2.0.14-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.14/README.md -[2.0.13-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.13/README.md -[2.0.12-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.12/README.md -[2.0.11-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.11/README.md -[2.0.10-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.10/README.md -[2.0.9-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.9/README.md -[2.0.8-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.8/README.md -[2.0.7-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.7/README.md -[2.0.6-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.6/README.md -[2.0.5-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.5/README.md -[2.0.4-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.4/README.md -[2.0.3-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.3/README.md -[2.0.2-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.2/README.md -[2.0.1-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.1/README.md -[2.0.0-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.0/README.md - -### Older Releases - -
    - 1.4.x CHANGELOGs and READMEs - -| Version | Release Date | CHANGELOG | README | -|---------|--------------|---------------------------------------|---------------------------------| -| 1.4.11 | Sep 16, 2022 | [v1.4.11 CHANGELOG][1.4.11-changelog] | [v1.4.11 README][1.4.11-readme] | -| 1.4.10 | Jul 1, 2022 | [v1.4.10 CHANGELOG][1.4.10-changelog] | [v1.4.10 README][1.4.10-readme] | -| 1.4.9 | Feb 20, 2022 | [v1.4.9 CHANGELOG][1.4.9-changelog] | [v1.4.9 README][1.4.9-readme] | -| 1.4.8 | Feb 18, 2022 | [v1.4.8 CHANGELOG][1.4.8-changelog] | [v1.4.8 README][1.4.8-readme] | -| 1.4.7 | Mar 19, 2021 | [v1.4.7 CHANGELOG][1.4.7-changelog] | [v1.4.7 README][1.4.7-readme] | -| 1.4.6 | Mar 19, 2021 | [v1.4.6 CHANGELOG][1.4.6-changelog] | [v1.4.6 README][1.4.6-readme] | -| 1.4.5 | Mar 18, 2021 | [v1.4.5 CHANGELOG][1.4.5-changelog] | [v1.4.5 README][1.4.5-readme] | -| 1.4.4 | Feb 12, 2020 | [v1.4.4 CHANGELOG][1.4.4-changelog] | [v1.4.4 README][1.4.4-readme] | -| 1.4.3 | Jan 29, 2020 | [v1.4.3 CHANGELOG][1.4.3-changelog] | [v1.4.3 README][1.4.3-readme] | -| 1.4.2 | Oct 1, 2019 | [v1.4.2 CHANGELOG][1.4.2-changelog] | [v1.4.2 README][1.4.2-readme] | -| 1.4.1 | Oct 13, 2018 | [v1.4.1 CHANGELOG][1.4.1-changelog] | [v1.4.1 README][1.4.1-readme] | -| 1.4.0 | Jun 9, 2017 | [v1.4.0 CHANGELOG][1.4.0-changelog] | [v1.4.0 README][1.4.0-readme] | -
    + Need enterprise-level guarantees? -[1.4.11-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1411---2022-09-16 -[1.4.10-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1410---2022-07-01 -[1.4.9-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#149---2022-02-20 -[1.4.8-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#148---2022-02-18 -[1.4.7-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#147---2021-03-19 -[1.4.6-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#146---2021-03-19 -[1.4.5-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#145---2021-03-18 -[1.4.4-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#144---2020-02-12 -[1.4.3-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#143---2020-01-29 -[1.4.2-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#142---2019-10-01 -[1.4.1-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#141---2018-10-13 -[1.4.0-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#140---2017-06-09 - -[1.4.11-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.11/README.md -[1.4.10-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.10/README.md -[1.4.9-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.9/README.md -[1.4.8-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.8/README.md -[1.4.7-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.7/README.md -[1.4.6-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.6/README.md -[1.4.5-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.5/README.md -[1.4.4-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.4/README.md -[1.4.3-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.3/README.md -[1.4.2-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.2/README.md -[1.4.1-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.1/README.md -[1.4.0-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.0/README.md +

    The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.

    -
    - 1.3.x Readmes +

    Get help from me on Tidelift

    -| Version | Release Date | Readme | -|---------|--------------|--------------------------------------------------------------| -| 1.3.1 | Mar 3, 2017 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.3.1/README.md | -| 1.3.0 | Dec 27, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.3.0/README.md | +
      +
    • 💡Subscribe for support guarantees covering all your FLOSS dependencies
    • +
    • 💡Tidelift is part of Sonar +
    • +
    • 💡Tidelift pays maintainers to maintain the software you depend on!
      📊@Pointy Haired Boss: An enterprise support subscription is “never gonna let you down”, and supports open source maintainers
    • +
    -
    +

    Alternatively:

    -
    - ≤= 1.2.x Readmes (2016 and before) - -| Version | Release Date | Readme | -|---------|--------------|--------------------------------------------------------------| -| 1.2.0 | Jun 30, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.2.0/README.md | -| 1.1.0 | Jan 30, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.1.0/README.md | -| 1.0.0 | May 23, 2014 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.0.0/README.md | -| < 1.0.0 | Find here | https://gitlab.com/ruby-oauth/oauth2/-/tags | +
      +
    • Live Chat on Discord
    • +
    • Get help from me on Upwork
    • +
    • Get help from me on Codementor
    • +
    -## ✨ Installation +

    ✨ Installation

    -Install the gem and add to the application's Gemfile by executing: +

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

    -```console +

    console bundle add oauth2 -``` +

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

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

    -```console +

    console gem install oauth2 -``` +

    -### 🔒 Secure Installation +

    🔒 Secure Installation

    For Medium or High Security Installations -This gem is cryptographically signed and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by -[stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with -by following the instructions below. +

    This gem is cryptographically signed, and has verifiable SHA-256 and SHA-512 checksums by +stone_checksums. Be sure the gem you install hasn’t been tampered with +by following the instructions below.

    -Add my public key (if you haven’t already; will expire 2045-04-29) as a trusted certificate: +

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

    -```console +

    console 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: +

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

    -```console +

    console gem install oauth2 -P MediumSecurity -``` +

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

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

    -This is necessary because not all of `oauth2`’s dependencies are signed, so we cannot use `HighSecurity`. +

    This is necessary because not all of oauth2’s dependencies are signed, so we cannot use HighSecurity.

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

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

    -```console +

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

    -NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine. +

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

    + +

    NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.

    -## What is new for v2.0? - -- Works with Ruby versions >= 2.2 -- Drop support for the expired MAC Draft (all versions) -- Support IETF rfc7515 JSON Web Signature - JWS (since v2.0.12) - - Support JWT `kid` for key discovery and management -- Support IETF rfc7523 JWT Bearer Tokens (since v2.0.0) -- Support IETF rfc7231 Relative Location in Redirect (since v2.0.0) -- Support IETF rfc6749 Don't set oauth params when nil (since v2.0.0) -- Support IETF rfc7009 Token Revocation (since v2.0.10, updated in v2.0.13 to support revocation via URL-encoded parameters) -- Support [OIDC 1.0 Private Key JWT](https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication); based on the OAuth JWT assertion specification [(RFC 7523)](https://tools.ietf.org/html/rfc7523) -- Support new formats, including from [jsonapi.org](http://jsonapi.org/format/): `application/vdn.api+json`, `application/vnd.collection+json`, `application/hal+json`, `application/problem+json` -- Adds option to `OAuth2::Client#get_token`: - - `:access_token_class` (`AccessToken`); user specified class to use for all calls to `get_token` -- Adds option to `OAuth2::AccessToken#initialize`: - - `:expires_latency` (`nil`); number of seconds by which AccessToken validity will be reduced to offset latency -- By default, keys are transformed to snake case. - - Original keys will still work as previously, in most scenarios, thanks to [snaky_hash][snaky_hash] gem. - - However, this is a _breaking_ change if you rely on `response.parsed.to_h` to retain the original case, and the original wasn't snake case, as the keys in the result will be snake case. - - As of version 2.0.4 you can turn key transformation off with the `snaky: false` option. -- By default, the `:auth_scheme` is now `:basic_auth` (instead of `:request_body`) - - Third-party strategies and gems may need to be updated if a provider was requiring client id/secret in the request body -- [... A lot more](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md#200-2022-06-21-tag) - -[snaky_hash]: https://gitlab.com/ruby-oauth/snaky_hash - -## 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. +

    What is new for v2.0?

    + +
      +
    • Works with Ruby versions >= 2.2
    • +
    • Drop support for the expired MAC Draft (all versions)
    • +
    • Support IETF rfc7515 JSON Web Signature - JWS (since v2.0.12) +
        +
      • Support JWT kid for key discovery and management
      • +
      +
    • +
    • Support IETF rfc7523 JWT Bearer Tokens (since v2.0.0)
    • +
    • Support IETF rfc7231 Relative Location in Redirect (since v2.0.0)
    • +
    • Support IETF rfc6749 Don’t set oauth params when nil (since v2.0.0)
    • +
    • Support IETF rfc7009 Token Revocation (since v2.0.10, updated in v2.0.13 to support revocation via URL-encoded parameters)
    • +
    • Support OIDC 1.0 Private Key JWT; based on the OAuth JWT assertion specification (RFC 7523) +
    • +
    • Support new formats, including from jsonapi.org: application/vdn.api+json, application/vnd.collection+json, application/hal+json, application/problem+json +
    • +
    • Adds option to OAuth2::Client#get_token: +
        +
      • +:access_token_class (AccessToken); user specified class to use for all calls to get_token +
      • +
      +
    • +
    • Adds option to OAuth2::AccessToken#initialize: +
        +
      • +:expires_latency (nil); number of seconds by which AccessToken validity will be reduced to offset latency
      • +
      +
    • +
    • By default, keys are transformed to snake case. +
        +
      • Original keys will still work as previously, in most scenarios, thanks to snaky_hash gem.
      • +
      • However, this is a breaking change if you rely on response.parsed.to_h to retain the original case, and the original wasn’t snake case, as the keys in the result will be snake case.
      • +
      • As of version 2.0.4 you can turn key transformation off with the snaky: false option.
      • +
      +
    • +
    • By default, the :auth_scheme is now :basic_auth (instead of :request_body) +
        +
      • Third-party strategies and gems may need to be updated if a provider was requiring client id/secret in the request body
      • +
      +
    • +
    • … A lot more
    • +
    + +

    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. -See `1-4-stable` branch for older rubies. +See 1-4-stable branch for older rubies.

    -
    - Ruby Engine Compatibility Policy +

    <details markdown=”1>

    +Ruby Engine Compatibility Policy -This gem is tested against MRI, JRuby, and Truffleruby. +

    This gem is tested against MRI, JRuby, and Truffleruby. Each of those has varying versions that target a specific version of MRI Ruby. This gem should work in the just-listed Ruby engines according to the targeted MRI compatibility in the table below. If you would like to add support for additional engines, -see [gemfiles/README.md](gemfiles/README.md), then submit a PR to the correct maintenance branch as according to the table below. -

    +see gemfiles/README.md, then submit a PR to the correct maintenance branch as according to the table below.

    -
    - Ruby Version Compatibility Policy +

    </details>

    + +

    <details markdown=”1>

    +Ruby Version Compatibility Policy -If something doesn't work on one of these interpreters, it's a bug. +

    If something doesn’t work on one of these interpreters, it’s a bug.

    -This library may inadvertently work (or seem to work) on other Ruby +

    This library may inadvertently work (or seem to work) on other Ruby implementations; however, support will only be provided for the versions listed -above. +above.

    -If you would like this library to support another Ruby version, you may +

    If you would like this library to support another Ruby version, you may volunteer to be a maintainer. Being a maintainer entails making sure all tests run and pass on that implementation. When something breaks on your implementation, you will be responsible for providing patches in a timely fashion. If critical issues for a particular implementation exist at the time -of a major release, support for that Ruby version may be dropped. -

    - -| | Ruby OAuth2 Version | Maintenance Branch | Targeted Support | Best Effort Support | Incidental Support | -|:----|---------------------|--------------------|----------------------|-------------------------|------------------------------| -| 1️⃣ | 2.0.x | `main` | 3.2, 3.3, 3.4 | 2.5, 2.6, 2.7, 3.0, 3.1 | 2.2, 2.3, 2.4 | -| 2️⃣ | 1.4.x | `1-4-stable` | 3.2, 3.3, 3.4 | 2.5, 2.6, 2.7, 3.0, 3.1 | 1.9, 2.0, 2.1, 2.2, 2.3, 2.4 | -| 3️⃣ | older | N/A | Best of luck to you! | Please upgrade! | | - -NOTE: The 1.4 series will only receive critical security updates. -See [SECURITY.md][🔐security] and [IRP.md][🔐irp]. - -## ⚙️ Configuration - -You can turn on additional warnings. - -```ruby +of a major release, support for that Ruby version may be dropped.

    + +

    </details>

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
     Ruby OAuth2 VersionMaintenance BranchTargeted SupportBest Effort SupportIncidental Support
    1️⃣2.0.xmain3.2, 3.3, 3.42.5, 2.6, 2.7, 3.0, 3.12.2, 2.3, 2.4
    2️⃣1.4.x1-4-stable3.2, 3.3, 3.42.5, 2.6, 2.7, 3.0, 3.11.9, 2.0, 2.1, 2.2, 2.3, 2.4
    3️⃣olderN/ABest of luck to you!Please upgrade! 
    + +

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

    + +

    ⚙️ Configuration

    + +

    You can turn on additional warnings.

    + +

    ruby OAuth2.configure do |config| # Turn on a warning like: # OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key @@ -577,54 +633,56 @@

    Quick Examples

    # 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. +

    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. +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 -the `token_name` 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 +the token_name option.

    -You'll likely need to do some source diving. +

    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! +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!) +

    +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" +

    ```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”

    -access = client.auth_code.get_token("authorization_code_value", redirect_uri: "http://localhost:8080/oauth2/callback", headers: => "Basic some_password") -response = access.get("/api/resource", params: => "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. +

    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 +

    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" -``` +

    -### 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: +

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

    -```ruby +

    ruby client = OAuth2::Client.new( "client_id", "client_secret", @@ -637,105 +695,109 @@

    Quick Examples

    # => "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: => "bar") +

    ruby +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) # => "additionalData"=>"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 +

    Serialization

    -As of v2.0.11, if you need to serialize the parsed result, you can! +

    As of v2.0.11, if you need to serialize the parsed result, you can!

    -There are two ways to do this, globally, or discretely. The discrete way is recommended. +

    There are two ways to do this, globally, or discretely. The discrete way is recommended.

    -##### Global Serialization Config +
    Global Serialization Config
    -Globally configure `SnakyHash::StringKeyed` to use the serializer. Put this in your code somewhere reasonable (like an initializer for Rails). +

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

    -```ruby +

    ruby SnakyHash::StringKeyed.class_eval do extend SnakyHash::Serializer end -``` +

    -##### Discrete Serialization Config +
    Discrete Serialization Config
    -Discretely configure a custom Snaky Hash class to use the serializer. +

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

    -```ruby +

    ```ruby class MySnakyHash < SnakyHash::StringKeyed - # Give this hash class `dump` and `load` abilities! + # 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") -token = client.get_token(MySnakyHash) -``` +

    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 +
    Serialization Extensions
    -These extensions work regardless of whether you used the global or discrete config above. +

    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. +

    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. -See [response_spec.rb](https://github.com/ruby-oauth/oauth2/blob/main/spec/oauth2/response_spec.rb) if you need to study the hacks for older Rubies. +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.

    + +

    <details markdown=”1>

    +See Examples -```ruby +

    ```ruby class MySnakyHash < SnakyHash::StringKeyed - # Give this hash class `dump` and `load` abilities! - extend SnakyHash::Serializer + # 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 @@ -744,35 +806,35 @@

    Quick Examples

    end end end -``` +```

    -See [response_spec.rb](https://github.com/ruby-oauth/oauth2/blob/main/spec/oauth2/response_spec.rb), or the [ruby-oauth/snaky_hash](https://gitlab.com/ruby-oauth/snaky_hash) gem for more ideas. +

    </details>

    -#### Prefer camelCase over snake_case? => snaky: false +

    Prefer camelCase over snake_case? => snaky: false

    -```ruby -response = access.get("/api/resource", params: => "bar", snaky: false) -JSON.parse(response.body) # => "additionalData"=>"additional" -response.parsed # => "additionalData"=>"additional" +

    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.parsed.class.name # => Hash (just, regular old Hash) -``` +

    Debugging & Logging -Set an environment variable as per usual (e.g. with [dotenv](https://github.com/bkeepers/dotenv)). +

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

    -```ruby +

    ruby # 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. +

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

    -```ruby +

    ruby require "oauth2" client = OAuth2::Client.new( "client_id", @@ -780,304 +842,377 @@

    Quick Examples

    site: "https://example.org", logger: Logger.new("example.log", "weekly"), ) -``` +

    +
    -### OAuth2::Response +

    OAuth2::Response

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

    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`](https://gitlab.com/ruby-oauth/snaky_hash/-/blob/main/lib/snaky_hash/string_keyed.rb) if the `Content-Type` is `application/x-www-form-urlencoded` or if +

    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. +array. Otherwise, it will return the original body string.

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

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

    -### OAuth2::AccessToken +

    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 -`application/x-www-form-urlencoded` encoded string of the values). +

    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):

    + +
      +
    • + + + + + + + +
      +expires_latency (Integernil): Seconds to subtract from expires_in when computing #expired? to offset latency.
      +
    • +
    • + + + + + + + + +
      +token_name (StringSymbolnil): When multiple token-like fields exist in responses, select the field name to use as the access token (since v2.0.10).
      +
    • +
    • + + + + + + + + +
      +mode (SymbolProcHash): Controls how the token is transmitted on requests made via this AccessToken instance.
      +
        +
      • +:header — Send as Authorization: Bearer header (default and preferred by OAuth 2.1 draft guidance). +
      • +
      • +:query — Send as access_token query parameter (discouraged in general, but required by some providers).
      • +
      • Verb-dependent (since v2.0.15): Provide either: +
          +
        • a Proc taking |verb| and returning :header or :query, or
        • +
        • a Hash with verb symbols as keys, for example {get: :query, post: :header, delete: :header}.
        • +
        +
      • +
      +
    • +
    -Options (since v2.0.x unless noted): -- `expires_latency` (Integer | nil): Seconds to subtract from expires_in when computing #expired? to offset latency. -- `token_name` (String | Symbol | nil): When multiple token-like fields exist in responses, select the field name to use as the access token (since v2.0.10). -- `mode` (Symbol | Proc | Hash): Controls how the token is transmitted on requests made via this AccessToken instance. - - `:header` — Send as Authorization: Bearer header (default and preferred by OAuth 2.1 draft guidance). - - `:query` — Send as access_token query parameter (discouraged in general, but required by some providers). - - Verb-dependent (since v2.0.15): Provide either: - - a `Proc` taking `|verb|` and returning `:header` or `:query`, or - - a `Hash` with verb symbols as keys, for example `:query, post: :header, delete: :header`. +

    Note: Verb-dependent mode supports providers like Instagram that require query mode for GET and header mode for POST/DELETE

    -Note: Verb-dependent mode supports providers like Instagram that require query mode for `GET` and header mode for `POST`/`DELETE` -- Verb-dependent mode via `Proc` was added in v2.0.15 -- Verb-dependent mode via `Hash` was added in v2.0.16 +
      +
    • Verb-dependent mode via Proc was added in v2.0.15
    • +
    • Verb-dependent mode via Hash was added in v2.0.16
    • +
    -### OAuth2::Error +

    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 -always contain the `OAuth2::Response` instance. +

    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` +

    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 - -Note on OAuth 2.1 (draft): -- PKCE is required for all OAuth clients using the authorization code flow (especially public clients). Implement PKCE in your app when required by your provider. See RFC 7636 and RFC 8252. -- Redirect URIs must be compared using exact string matching by the Authorization Server. -- The Implicit grant (response_type=token) and the Resource Owner Password Credentials grant are omitted from OAuth 2.1; they remain here for OAuth 2.0 compatibility but should be avoided for new apps. -- Bearer tokens in the query string are omitted due to security risks; prefer Authorization header usage. -- Refresh tokens for public clients must either be sender-constrained (e.g., DPoP/MTLS) or one-time use. -- The definitions of public and confidential clients are simplified to refer only to whether the client has credentials. - -References: -- OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13 -- Aaron Parecki: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1 -- FusionAuth: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1 -- Okta: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs -- Video: https://www.youtube.com/watch?v=g_aVPdwBTfw -- 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 +Response instance will contain the OAuth2::Error instance.

    + +

    Authorization Grants

    + +

    Note on OAuth 2.1 (draft):

    + +
      +
    • PKCE is required for all OAuth clients using the authorization code flow (especially public clients). Implement PKCE in your app when required by your provider. See RFC 7636 and RFC 8252.
    • +
    • Redirect URIs must be compared using exact string matching by the Authorization Server.
    • +
    • The Implicit grant (response_type=token) and the Resource Owner Password Credentials grant are omitted from OAuth 2.1; they remain here for OAuth 2.0 compatibility but should be avoided for new apps.
    • +
    • Bearer tokens in the query string are omitted due to security risks; prefer Authorization header usage.
    • +
    • Refresh tokens for public clients must either be sender-constrained (e.g., DPoP/MTLS) or one-time use.
    • +
    • The definitions of public and confidential clients are simplified to refer only to whether the client has credentials.
    • +
    + +

    References:

    + +
      +
    • OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
    • +
    • Aaron Parecki: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1
    • +
    • FusionAuth: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1
    • +
    • Okta: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs
    • +
    • Video: https://www.youtube.com/watch?v=g_aVPdwBTfw
    • +
    • 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`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/auth_code.rb), -[`#implicit`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/implicit.rb), -[`#password`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/password.rb), -[`#client_credentials`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/client_credentials.rb), and -[`#assertion`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/assertion.rb) 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.implicit.authorize_url(redirect_uri: "http://localhost:8080/oauth/callback") +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.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 -request, add a 'headers' hash under 'params': +

    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: => "Header") -``` +

    ruby +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 -requests for tokens for any Authentication grant type. +

    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) +

    Common Flows (end-to-end)

    -- Authorization Code (server-side web app): +
      +
    • Authorization Code (server-side web app):
    • +
    -```ruby -require "oauth2" +

    ```ruby +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]) + 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 +```

    -# Step 3: call APIs -profile = access.get("/api/v1/me").parsed -``` - -- Client Credentials (machine-to-machine): +
      +
    • Client Credentials (machine-to-machine):
    • +
    -```ruby +

    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") -``` +

    -- Resource Owner Password (legacy; avoid when possible): +
      +
    • Resource Owner Password (legacy; avoid when possible):
    • +
    -```ruby +

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

    -#### Examples +

    Examples

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

    ```ruby # 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 ? => 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: -- Resource Owner Password Credentials (ROPC) is deprecated in OAuth 2.1 and discouraged. Prefer Authorization Code + PKCE. -- If your deployment strictly demands the X-XSRF-TOKEN header, first fetch it from an endpoint that sets the XSRF-TOKEN cookie (often "/" or a login page) and pass it to headers. -- For Basic auth, auth_scheme: :basic_auth handles the Authorization header; you do not need to base64-encode manually. +

    Notes:

    + +
      +
    • Resource Owner Password Credentials (ROPC) is deprecated in OAuth 2.1 and discouraged. Prefer Authorization Code + PKCE.
    • +
    • If your deployment strictly demands the X-XSRF-TOKEN header, first fetch it from an endpoint that sets the XSRF-TOKEN cookie (often “/” or a login page) and pass it to headers.
    • +
    • For Basic auth, auth_scheme: :basic_auth handles the Authorization header; you do not need to base64-encode manually.
    • +
    -### Instagram API (verb‑dependent token mode) +

    Instagram API (verb‑dependent token mode)

    + +

    Providers like Instagram require the access token to be sent differently depending on the HTTP verb:

    -Providers like Instagram require the access token to be sent differently depending on the HTTP verb: -- GET requests: token must be in the query string (?access_token=...) -- POST/DELETE requests: token must be in the Authorization header (Bearer ...) +
      +
    • GET requests: token must be in the query string (?access_token=…)
    • +
    • POST/DELETE requests: token must be in the Authorization header (Bearer …)
    • +
    -Since v2.0.15, you can configure an AccessToken with a verb‑dependent mode. The gem will choose how to send the token based on the request method. +

    Since v2.0.15, you can configure an AccessToken with a verb‑dependent mode. The gem will choose how to send the token based on the request method.

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

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

    -```ruby -require "oauth2" +

    ```ruby +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: :query, post: :header, delete: :header, -) + 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: :query, post: :header, delete: :header, -) + 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: "ig_refresh_token", + “/refresh_access_token”, + params: {grant_type: “ig_refresh_token”}, ) long_lived = OAuth2::AccessToken.new( client, - refresh_resp.parsed["access_token"], - mode: :query, post: :header, delete: :header, -) + 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: "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: "https://...", caption: "hello") -``` +# long_lived.post(“/me/media”, body: {image_url: “https://…”, caption: “hello”}) +```

    -Tips: -- Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET. -- If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }. +

    Tips:

    -### Refresh Tokens +
      +
    • Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET.
    • +
    • If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }.
    • +
    -When the server issues a refresh_token, you can refresh manually or implement an auto-refresh wrapper. +

    Refresh Tokens

    -- Manual refresh: +

    When the server issues a refresh_token, you can refresh manually or implement an auto-refresh wrapper.

    -```ruby +
      +
    • Manual refresh:
    • +
    + +

    ruby if access.expired? access = access.refresh end -``` +

    -- Auto-refresh wrapper pattern: +
      +
    • Auto-refresh wrapper pattern:
    • +
    -```ruby +

    ```ruby 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 @@ -1088,180 +1223,193 @@

    Quick Examples

    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)`. +

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

    -### Token Revocation (RFC 7009) +

    Token Revocation (RFC 7009)

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

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

    -```ruby +

    ```ruby # Revoke the current access token -access.revoke(token_type_hint: :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 +

    Client Configuration Tips

    -#### Mutual TLS (mTLS) client authentication +

    Mutual TLS (mTLS) client authentication

    -Some providers require OAuth requests (including the token request and subsequent API calls) to be sender‑constrained using mutual TLS (mTLS). With this gem, you enable mTLS by providing a client certificate/private key to Faraday via connection_opts.ssl and, if your provider requires it for client authentication, selecting the tls_client_auth auth_scheme. +

    Some providers require OAuth requests (including the token request and subsequent API calls) to be sender‑constrained using mutual TLS (mTLS). With this gem, you enable mTLS by providing a client certificate/private key to Faraday via connection_opts.ssl and, if your provider requires it for client authentication, selecting the tls_client_auth auth_scheme.

    -Example using PEM files (certificate and key): +

    Example using PEM files (certificate and key):

    -```ruby -require "oauth2" -require "openssl" +

    ```ruby +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

    + +

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

    +

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

    -# 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") -``` - -Notes: -- Files must contain the appropriate PEMs. The private key may be encrypted; if so, pass a password to OpenSSL::PKey::RSA.new(File.read(path), ENV["KEY_PASSWORD"]). -- If your certificate and key are in a PKCS#12/PFX bundle, you can load them like: - - p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"]) - - client_cert = p12.certificate; client_key = p12.key -- Server trust: - - If your environment does not have system CAs, specify ca_file or ca_path inside the ssl: hash. - - Keep verify: true in production. Set verify: false only for local testing. -- Faraday adapter: Any adapter that supports Ruby’s OpenSSL should work. net_http (default) and net_http_persistent are common choices. -- Scope of mTLS: The SSL client cert is applied to any HTTPS request made by this client (token and resource requests) to the configured site base URL (and absolute URLs you call with the same client). -- OIDC tie-in: Some OPs require tls_client_auth at the token endpoint per OIDC/OAuth specifications. That is enabled via auth_scheme: :tls_client_auth as shown above. - -#### Authentication schemes for the token request - -```ruby +

    Notes:

    + +
      +
    • Files must contain the appropriate PEMs. The private key may be encrypted; if so, pass a password to OpenSSL::PKey::RSA.new(File.read(path), ENV["KEY_PASSWORD"]).
    • +
    • If your certificate and key are in a PKCS#12/PFX bundle, you can load them like: +
        +
      • p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"])
      • +
      • client_cert = p12.certificate; client_key = p12.key
      • +
      +
    • +
    • Server trust: +
        +
      • If your environment does not have system CAs, specify ca_file or ca_path inside the ssl: hash.
      • +
      • Keep verify: true in production. Set verify: false only for local testing.
      • +
      +
    • +
    • Faraday adapter: Any adapter that supports Ruby’s OpenSSL should work. net_http (default) and net_http_persistent are common choices.
    • +
    • Scope of mTLS: The SSL client cert is applied to any HTTPS request made by this client (token and resource requests) to the configured site base URL (and absolute URLs you call with the same client).
    • +
    • OIDC tie-in: Some OPs require tls_client_auth at the token endpoint per OIDC/OAuth specifications. That is enabled via auth_scheme: :tls_client_auth as shown above.
    • +
    + +

    Authentication schemes for the token request

    + +

    ruby OAuth2::Client.new( id, secret, 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: +

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

    -```ruby +

    ruby client = OAuth2::Client.new( id, secret, site: "https://provider.example.com", connection_opts: { - request: 5, timeout: 15, + request: {open_timeout: 5, timeout: 15}, proxy: ENV["HTTPS_PROXY"], - ssl: true, + ssl: {verify: true}, }, ) do |faraday| faraday.request(:url_encoded) # 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) +
    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. +

    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" +

    ```ruby +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: Faraday::FlatParamsEncoder, + request: {params_encoder: Faraday::FlatParamsEncoder}, }, ) 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: +

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

    -```ruby -conn = Faraday.new("https://api.example.com", request: Faraday::FlatParamsEncoder) -``` +

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

    -#### Redirection +

    Redirection

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

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

    -### Handling Responses and Errors +

    Handling Responses and Errors

    -- Parsing: +
      +
    • Parsing:
    • +
    -```ruby +

    ruby 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: +
      +
    • Error handling:
    • +
    -```ruby +

    ruby begin access.get("/v1/forbidden") rescue OAuth2::Error => e @@ -1269,150 +1417,155 @@

    Quick Examples

    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: +
      +
    • Disable raising on 4xx/5xx to inspect the response yourself:
    • +
    -```ruby +

    ruby 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 end -``` +

    -### Making Raw Token Requests +

    Making Raw Token Requests

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

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

    -```ruby +

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

    -### OpenID Connect (OIDC) Notes +

    OpenID Connect (OIDC) Notes

    -- If the token response includes an `id_token` (a JWT), this gem surfaces it but does not validate the signature. Use a JWT library and your provider's JWKs to verify it. -- For private_key_jwt client authentication, provide `auth_scheme: :private_key_jwt` and ensure your key configuration matches the provider requirements. -- See [OIDC.md](OIDC.md) for a more complete OIDC overview, example, and links to the relevant specifications. +
      +
    • If the token response includes an id_token (a JWT), this gem surfaces it but does not validate the signature. Use a JWT library and your provider’s JWKs to verify it.
    • +
    • For private_key_jwt client authentication, provide auth_scheme: :private_key_jwt and ensure your key configuration matches the provider requirements.
    • +
    • See OIDC.md for a more complete OIDC overview, example, and links to the relevant specifications.
    • +
    -### Debugging +

    Debugging

    -- Set environment variable `OAUTH_DEBUG=true` to enable verbose Faraday logging (uses the client-provided logger). -- To mirror a working curl request, ensure you set the same auth scheme, params, and content type. The Quick Example at the top shows a curl-to-ruby translation. +
      +
    • Set environment variable OAUTH_DEBUG=true to enable verbose Faraday logging (uses the client-provided logger).
    • +
    • To mirror a working curl request, ensure you set the same auth scheme, params, and content type. The Quick Example at the top shows a curl-to-ruby translation.
    • +
    ---- +
    -## 🦷 FLOSS Funding +

    🦷 FLOSS 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. +

    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 +

    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][🖇osc] is our preferred funding platform). +(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 -appreciate it if you suggest to your company to become a ruby-oauth sponsor.** +

    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][🖇sponsor], -[Liberapay][⛳liberapay], -[PayPal][🖇paypal], -[Open Collective][🖇osc] -and [Tidelift][🏙️entsup-tidelift]. +

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

    -| 📍 NOTE | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| If doing a sponsorship in the form of donation is problematic for your company
    from an accounting standpoint, we'd recommend the use of Tidelift,
    where you can get a support-like subscription instead. | + + + + + + + + + + + +
    📍 NOTE
    If doing a sponsorship in the form of donation is problematic for your company
    from an accounting standpoint, we’d recommend the use of Tidelift,
    where you can get a support-like subscription instead.
    -### Open Collective for Individuals +

    Open Collective for Individuals

    -Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/kettle-rb#backer)] +

    Support us with a monthly donation and help us continue our activities. [Become a backer]

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

    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 +

    Open Collective for Organizations

    -Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor](https://opencollective.com/kettle-rb#sponsor)] +

    Become a sponsor and get your logo on our README on GitHub with a link to your site. [Become a sponsor]

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

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

    -No sponsors yet. Be the first! - -### Open Collective for Donors - - +

    No sponsors yet. Be the first! +

    - +

    Another way to support open-source

    -[kettle-readme-backers]: https://github.com/kettle-rb/kettle-dev/blob/main/exe/kettle-readme-backers +

    I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).

    -### Another way to support open-source +

    If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in bundle fund.

    -> How wonderful it is that nobody need wait a single moment before starting to improve the world.
    ->—Anne Frank +

    I’m developing a new library, floss_funding, designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.

    -I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions — totaling 79 hours of FLOSS coding over just the past seven days, a pretty regular week for me. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats). +

    Floss-Funding.dev: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags

    -If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`. +

    OpenCollective 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

    -I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look. +

    🔐 Security

    -**[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags** +

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

    -[![OpenCollective Backers][🖇osc-backers-i]][🖇osc-backers] [![OpenCollective Sponsors][🖇osc-sponsors-i]][🖇osc-sponsors] [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon] +

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

    -## 🔐 Security +

    🤝 Contributing

    -To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). -Tidelift will coordinate the fix and disclosure. +

    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.

    -For more see [SECURITY.md][🔐security], [THREAT_MODEL.md][🔐threat-model], and [IRP.md][🔐irp]. +

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

    -## 🤝 Contributing +

    See CONTRIBUTING.md for more detailed instructions.

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

    🚀 Release Instructions

    -We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it. +

    See CONTRIBUTING.md.

    -See [CONTRIBUTING.md][🤝contributing] for more detailed instructions. +

    Code Coverage

    -### 🚀 Release Instructions +

    Coverage Graph

    -See [CONTRIBUTING.md][🤝contributing]. +

    Coveralls Test Coverage

    -### Code Coverage +

    QLTY Test Coverage

    -[![Coverage Graph][🏀codecov-g]][🏀codecov] +

    🪇 Code of Conduct

    -[![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] +

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

    -[![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov] +

    🌈 Contributors

    -### 🪇 Code of Conduct +

    Contributors

    -Everyone interacting with this project's codebases, issue trackers, -chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct]. +

    Made with contributors-img.

    -## 🌈 Contributors - -[![Contributors][🖐contributors-img]][🖐contributors] - -Made with [contributors-img][🖐contrib-rocks]. - -Also see GitLab Contributors: [https://gitlab.com/ruby-oauth/oauth2/-/graphs/main][🚎contributors-gl] +

    Also see GitLab Contributors: https://gitlab.com/ruby-oauth/oauth2/-/graphs/main

    ⭐️ Star History @@ -1427,55 +1580,58 @@

    Quick Examples

    -## 📌 Versioning +

    📌 Versioning

    -This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver]. +

    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. +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
    ->—Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking] +
    +

    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!"), +

    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][📌pvc] with two digits of precision. +the Pessimistic Version Constraint with two digits of precision.

    -For example: +

    For example:

    -```ruby +

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

    -📌 Is "Platform Support" part of the public API? More details inside. + 📌 Is "Platform Support" part of the public API? More details inside. -SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms -is a *breaking change* to an API. -It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless. +

    SemVer should, IMO, but doesn’t explicitly, say that dropping support for specific Platforms +is a breaking change to an API, and for that reason the bike shedding is endless.

    -To get a better understanding of how SemVer is intended to work over a project's lifetime, -read this article from the creator of SemVer: +

    To get a better understanding of how SemVer is intended to work over a project’s lifetime, +read this article from the creator of SemVer:

    -- ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred] +
    -See [CHANGELOG.md][📌changelog] for a list of releases. +

    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] [![License: MIT][📄license-img]][📄license-ref]. -See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer]. +

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

    -### © Copyright +
    • - Copyright (c) 2017–2025 Peter H. Boling, of + Copyright (c) 2017 – 2025 Peter H. Boling, of Galtzo.com @@ -1484,243 +1640,30 @@

      Quick Examples

      , and oauth2 contributors.
    • - Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc. + Copyright (c) 2011 - 2013 Michael Bleigh and Intridea, Inc.
    -## 🤑 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 filled with many dozens of rejections, -I'm now spending ~60+ hours a week building open source tools. -I'm hoping to be able to pay for my kids' health insurance this month, +

    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. - -[![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] - -To say "thanks!" ☝️ Join the Discord or 👇️ send money. - -[![Sponsor ruby-oauth/oauth2 on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img] - -### Please give the project a star ⭐ ♥. - -Thanks for RTFM. ☺️ - -[⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat -[⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611 -[⛳liberapay]: https://liberapay.com/pboling/donate -[🖇osc-all-img]: https://img.shields.io/opencollective/all/ruby-oauth -[🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/ruby-oauth -[🖇osc-backers-img]: https://img.shields.io/opencollective/backers/ruby-oauth -[🖇osc-backers]: https://opencollective.com/ruby-oauth#backer -[🖇osc-backers-i]: https://opencollective.com/ruby-oauth/backers/badge.svg?style=flat -[🖇osc-sponsors]: https://opencollective.com/ruby-oauth#sponsor -[🖇osc-sponsors-i]: https://opencollective.com/ruby-oauth/sponsors/badge.svg?style=flat -[🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/ruby-oauth?style=for-the-badge -[🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/ruby-oauth?style=for-the-badge -[🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/ruby-oauth?style=for-the-badge -[🖇osc]: https://opencollective.com/ruby-oauth -[🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github -[🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github -[🖇sponsor]: https://github.com/sponsors/pboling -[🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat -[🖇polar]: https://polar.sh/pboling -[🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat -[🖇kofi]: https://ko-fi.com/O5O86SNP4 -[🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat -[🖇patreon]: https://patreon.com/galtzo -[🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat -[🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff -[🖇buyme]: https://www.buymeacoffee.com/pboling -[🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal -[🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A -[🖇paypal]: https://www.paypal.com/paypalme/peterboling -[🖇floss-funding.dev]: https://floss-funding.dev -[🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding -[✉️discord-invite]: https://discord.gg/3qme4XHNKN -[✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord -[✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white -[✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends - -[⛳gg-discussions]: https://groups.google.com/g/oauth-ruby -[⛳gg-discussions-img]: https://img.shields.io/badge/google-group-0093D0.svg?style=for-the-badge&logo=google&logoColor=orange - -[✇bundle-group-pattern]: https://gist.github.com/pboling/4564780 -[⛳️gem-namespace]: https://github.com/ruby-oauth/oauth2 -[⛳️namespace-img]: https://img.shields.io/badge/namespace-OAuth2-3C2D2D.svg?style=square&logo=ruby&logoColor=white -[⛳️gem-name]: https://rubygems.org/gems/oauth2 -[⛳️name-img]: https://img.shields.io/badge/name-oauth2-3C2D2D.svg?style=square&logo=rubygems&logoColor=red -[⛳️tag-img]: https://img.shields.io/github/tag/ruby-oauth/oauth2.svg -[⛳️tag]: http://github.com/ruby-oauth/oauth2/releases -[🚂maint-blog]: http://www.railsbling.com/tags/oauth2 -[🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange -[🚂maint-contact]: http://www.railsbling.com/contact -[🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red -[💖🖇linkedin]: http://www.linkedin.com/in/peterboling -[💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling -[💖✌️wellfound]: https://wellfound.com/u/peter-boling -[💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound -[💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling -[💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase -[💖🐘ruby-mast]: https://ruby.social/@galtzo -[💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo -[💖🦋bluesky]: https://bsky.app/profile/galtzo.com -[💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white -[💖🌳linktree]: https://linktr.ee/galtzo -[💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree -[💖💁🏼‍♂️devto]: https://dev.to/galtzo -[💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white -[💖💁🏼‍♂️aboutme]: https://about.me/peter.boling -[💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white -[💖🧊berg]: https://codeberg.org/pboling -[💖🐙hub]: https://github.org/pboling -[💖🛖hut]: https://sr.ht/~galtzo/ -[💖🧪lab]: https://gitlab.com/pboling -[👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share -[👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white -[👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github -[👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white -[🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=readme -[🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white -[🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar -[💁🏼‍♂️peterboling]: http://www.peterboling.com -[🚂railsbling]: http://www.railsbling.com -[📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange -[📜src-gl]: https://gitlab.com/ruby-oauth/oauth2/ -[📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue -[📜src-cb]: https://codeberg.org/ruby-oauth/oauth2 -[📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green -[📜src-gh]: https://github.com/ruby-oauth/oauth2 -[📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white -[📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white -[📜gl-wiki]: https://gitlab.com/ruby-oauth/oauth2/-/wikis/home -[📜gh-wiki]: https://github.com/ruby-oauth/oauth2/wiki -[📜gl-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white -[📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white -[👽dl-rank]: https://rubygems.org/gems/oauth2 -[👽dl-ranki]: https://img.shields.io/gem/rd/oauth2.svg -[👽oss-help]: https://www.codetriage.com/ruby-oauth/oauth2 -[👽oss-helpi]: https://www.codetriage.com/ruby-oauth/oauth2/badges/users.svg -[👽version]: https://rubygems.org/gems/oauth2 -[👽versioni]: https://img.shields.io/gem/v/oauth2.svg -[🏀qlty-mnt]: https://qlty.sh/gh/ruby-oauth/projects/oauth2 -[🏀qlty-mnti]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/maintainability.svg -[🏀qlty-cov]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/metrics/code?sort=coverageRating -[🏀qlty-covi]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/coverage.svg -[🏀codecov]: https://codecov.io/gh/ruby-oauth/oauth2 -[🏀codecovi]: https://codecov.io/gh/ruby-oauth/oauth2/graph/badge.svg -[🏀coveralls]: https://coveralls.io/github/ruby-oauth/oauth2?branch=main -[🏀coveralls-img]: https://coveralls.io/repos/github/ruby-oauth/oauth2/badge.svg?branch=main -[🖐codeQL]: https://github.com/ruby-oauth/oauth2/security/code-scanning -[🖐codeQL-img]: https://github.com/ruby-oauth/oauth2/actions/workflows/codeql-analysis.yml/badge.svg -[🚎1-an-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ancient.yml -[🚎1-an-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/ancient.yml/badge.svg -[🚎2-cov-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/coverage.yml -[🚎2-cov-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/coverage.yml/badge.svg -[🚎3-hd-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/heads.yml -[🚎3-hd-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/heads.yml/badge.svg -[🚎4-lg-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/legacy.yml -[🚎4-lg-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/legacy.yml/badge.svg -[🚎5-st-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/style.yml -[🚎5-st-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/style.yml/badge.svg -[🚎6-s-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/supported.yml -[🚎6-s-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/supported.yml/badge.svg -[🚎7-us-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/unsupported.yml -[🚎7-us-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/unsupported.yml/badge.svg -[🚎8-ho-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/hoary.yml -[🚎8-ho-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/hoary.yml/badge.svg -[🚎9-t-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml -[🚎9-t-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml/badge.svg -[🚎10-j-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml -[🚎10-j-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml/badge.svg -[🚎11-c-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml -[🚎11-c-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml/badge.svg -[🚎12-crh-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/dep-heads.yml -[🚎12-crh-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/dep-heads.yml/badge.svg -[🚎13-cbs-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/caboose.yml -[🚎13-cbs-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/caboose.yml/badge.svg -[🚎13-🔒️-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/locked_deps.yml -[🚎13-🔒️-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/locked_deps.yml/badge.svg -[🚎14-🔓️-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/unlocked_deps.yml -[🚎14-🔓️-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/unlocked_deps.yml/badge.svg -[🚎15-🪪-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml -[🚎15-🪪-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml/badge.svg -[💎ruby-2.2i]: https://img.shields.io/badge/Ruby-2.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.6i]: https://img.shields.io/badge/Ruby-2.6-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-2.7i]: https://img.shields.io/badge/Ruby-2.7-DF00CA?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-3.0i]: https://img.shields.io/badge/Ruby-3.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-3.1i]: https://img.shields.io/badge/Ruby-3.1-CC342D?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white -[💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green -[💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue -[💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink -[💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink -[💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink -[💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green -[💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue -[💎jruby-9.1i]: https://img.shields.io/badge/JRuby-9.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red -[💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red -[💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red -[💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red -[💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green -[💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue -[🤝gh-issues]: https://github.com/ruby-oauth/oauth2/issues -[🤝gh-pulls]: https://github.com/ruby-oauth/oauth2/pulls -[🤝gl-issues]: https://gitlab.com/ruby-oauth/oauth2/-/issues -[🤝gl-pulls]: https://gitlab.com/ruby-oauth/oauth2/-/merge_requests -[🤝cb-issues]: https://codeberg.org/ruby-oauth/oauth2/issues -[🤝cb-pulls]: https://codeberg.org/ruby-oauth/oauth2/pulls -[🤝cb-donate]: https://donate.codeberg.org/ -[🤝contributing]: CONTRIBUTING.md -[🏀codecov-g]: https://codecov.io/gh/ruby-oauth/oauth2/graphs/tree.svg -[🖐contrib-rocks]: https://contrib.rocks -[🖐contributors]: https://github.com/ruby-oauth/oauth2/graphs/contributors -[🖐contributors-img]: https://contrib.rocks/image?repo=ruby-oauth/oauth2 -[🚎contributors-gl]: https://gitlab.com/ruby-oauth/oauth2/-/graphs/main -[🪇conduct]: CODE_OF_CONDUCT.md -[🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg -[📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint -[📌semver]: https://semver.org/spec/v2.0.0.html -[📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat -[📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139 -[📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html -[📌changelog]: CHANGELOG.md -[📗keep-changelog]: https://keepachangelog.com/en/1.0.0/ -[📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat -[📌gitmoji]:https://gitmoji.dev -[📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square -[🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ -[🧮kloc-img]: https://img.shields.io/badge/KLOC-0.526-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue -[🔐security]: SECURITY.md -[🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat -[🔐irp]: IRP.md -[🔐irp-img]: https://img.shields.io/badge/IRP-259D6C.svg?style=flat -[🔐threat-model]: THREAT_MODEL.md -[🔐threat-model-img]: https://img.shields.io/badge/threat-model-259D6C.svg?style=flat -[📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year -[📄license]: LICENSE.txt -[📄license-ref]: https://opensource.org/licenses/MIT -[📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg -[📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0 -[📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache -[📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm -[📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat -[🚎yard-current]: http://rubydoc.info/gems/oauth2 -[🚎yard-head]: https://oauth2.galtzo.com -[💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums -[💎SHA_checksums]: https://gitlab.com/ruby-oauth/oauth2/-/tree/main/checksums -[💎rlts]: https://github.com/rubocop-lts/rubocop-lts -[💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white -[💎appraisal2]: https://github.com/appraisal-rb/appraisal2 -[💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white -[💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/ +Please consider sponsoring me or the project.

    + +

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

    + +

    Live Chat on Discord

    + +

    To say “thanks!” ☝️ Join the Discord or 👇️ send money.

    + +

    Sponsor ruby-oauth/oauth2 on Open Source Collective 💌 Sponsor me on GitHub Sponsors 💌 Sponsor me on Liberapay 💌 Donate on PayPal

    + +

    Please give the project a star ⭐ ♥.

    + +

    Thanks for RTFM. ☺️

    @@ -1731,13 +1674,12 @@

    Quick Examples

    -
    diff --git a/docs/top-level-namespace.html b/docs/top-level-namespace.html index 0040754f..9097f9bf 100644 --- a/docs/top-level-namespace.html +++ b/docs/top-level-namespace.html @@ -100,9 +100,9 @@

    Defined Under Namespace

    From 206fea19687cd7ad8eb5153617791860870a8eb5 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Fri, 7 Nov 2025 19:25:18 -0700 Subject: [PATCH 11/14] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20site?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .rubocop_gradual.lock | 2 +- docs/OAuth2.html | 2 +- docs/OAuth2/AccessToken.html | 2 +- docs/OAuth2/Authenticator.html | 2 +- docs/OAuth2/Client.html | 2 +- docs/OAuth2/Error.html | 2 +- docs/OAuth2/FilteredAttributes.html | 2 +- .../FilteredAttributes/ClassMethods.html | 2 +- docs/OAuth2/Response.html | 2 +- docs/OAuth2/Strategy.html | 2 +- docs/OAuth2/Strategy/Assertion.html | 2 +- docs/OAuth2/Strategy/AuthCode.html | 2 +- docs/OAuth2/Strategy/Base.html | 2 +- docs/OAuth2/Strategy/ClientCredentials.html | 2 +- docs/OAuth2/Strategy/Implicit.html | 2 +- docs/OAuth2/Strategy/Password.html | 2 +- docs/OAuth2/Version.html | 2 +- docs/_index.html | 2 +- docs/file.CHANGELOG.html | 2 +- docs/file.CITATION.html | 92 ------------- docs/file.CODE_OF_CONDUCT.html | 2 +- docs/file.CONTRIBUTING.html | 2 +- docs/file.FUNDING.html | 2 +- docs/file.IRP.html | 2 +- docs/file.LICENSE.html | 2 +- docs/file.OIDC.html | 2 +- docs/file.README.html | 2 +- docs/file.REEK.html | 71 ---------- docs/file.RUBOCOP.html | 2 +- docs/file.SECURITY.html | 2 +- docs/file.THREAT_MODEL.html | 2 +- docs/file.access_token.html | 94 -------------- docs/file.authenticator.html | 91 ------------- docs/file.client.html | 121 ------------------ docs/file.error.html | 78 ----------- docs/file.filtered_attributes.html | 76 ----------- docs/file.oauth2-2.0.10.gem.html | 71 ---------- docs/file.oauth2-2.0.11.gem.html | 71 ---------- docs/file.oauth2-2.0.12.gem.html | 71 ---------- docs/file.oauth2-2.0.13.gem.html | 71 ---------- docs/file.oauth2-2.0.14.gem.html | 71 ---------- docs/file.oauth2-2.0.15.gem.html | 71 ---------- docs/file.oauth2-2.0.16.gem.html | 71 ---------- docs/file.oauth2-2.0.17.gem.html | 71 ---------- docs/file.oauth2.html | 79 ------------ docs/file.response.html | 87 ------------- docs/file.strategy.html | 103 --------------- docs/file.version.html | 75 ----------- docs/index.html | 2 +- docs/top-level-namespace.html | 2 +- 50 files changed, 31 insertions(+), 1566 deletions(-) delete mode 100644 docs/file.CITATION.html delete mode 100644 docs/file.REEK.html delete mode 100644 docs/file.access_token.html delete mode 100644 docs/file.authenticator.html delete mode 100644 docs/file.client.html delete mode 100644 docs/file.error.html delete mode 100644 docs/file.filtered_attributes.html delete mode 100644 docs/file.oauth2-2.0.10.gem.html delete mode 100644 docs/file.oauth2-2.0.11.gem.html delete mode 100644 docs/file.oauth2-2.0.12.gem.html delete mode 100644 docs/file.oauth2-2.0.13.gem.html delete mode 100644 docs/file.oauth2-2.0.14.gem.html delete mode 100644 docs/file.oauth2-2.0.15.gem.html delete mode 100644 docs/file.oauth2-2.0.16.gem.html delete mode 100644 docs/file.oauth2-2.0.17.gem.html delete mode 100644 docs/file.oauth2.html delete mode 100644 docs/file.response.html delete mode 100644 docs/file.strategy.html delete mode 100644 docs/file.version.html diff --git a/.rubocop_gradual.lock b/.rubocop_gradual.lock index 9a28e666..ae390c31 100644 --- a/.rubocop_gradual.lock +++ b/.rubocop_gradual.lock @@ -6,7 +6,7 @@ "lib/oauth2.rb:2435263975": [ [73, 11, 7, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 651502127] ], - "lib/oauth2/access_token.rb:707681139": [ + "lib/oauth2/access_token.rb:1962777363": [ [64, 13, 5, "Style/IdenticalConditionalBranches: Move `t_key` out of the conditional.", 183811513], [70, 13, 5, "Style/IdenticalConditionalBranches: Move `t_key` out of the conditional.", 183811513] ], diff --git a/docs/OAuth2.html b/docs/OAuth2.html index 11eebbb6..20892da0 100644 --- a/docs/OAuth2.html +++ b/docs/OAuth2.html @@ -415,7 +415,7 @@

    diff --git a/docs/OAuth2/AccessToken.html b/docs/OAuth2/AccessToken.html index 4eb602c5..dc0bf8cc 100644 --- a/docs/OAuth2/AccessToken.html +++ b/docs/OAuth2/AccessToken.html @@ -3083,7 +3083,7 @@

    diff --git a/docs/OAuth2/Authenticator.html b/docs/OAuth2/Authenticator.html index 7e023866..a5f21aec 100644 --- a/docs/OAuth2/Authenticator.html +++ b/docs/OAuth2/Authenticator.html @@ -883,7 +883,7 @@

    diff --git a/docs/OAuth2/Client.html b/docs/OAuth2/Client.html index beff8225..4658bb2f 100644 --- a/docs/OAuth2/Client.html +++ b/docs/OAuth2/Client.html @@ -2654,7 +2654,7 @@

    diff --git a/docs/OAuth2/Error.html b/docs/OAuth2/Error.html index 78c78b30..2f174eed 100644 --- a/docs/OAuth2/Error.html +++ b/docs/OAuth2/Error.html @@ -772,7 +772,7 @@

    diff --git a/docs/OAuth2/FilteredAttributes.html b/docs/OAuth2/FilteredAttributes.html index 55a0ab89..c0197942 100644 --- a/docs/OAuth2/FilteredAttributes.html +++ b/docs/OAuth2/FilteredAttributes.html @@ -335,7 +335,7 @@

    diff --git a/docs/OAuth2/FilteredAttributes/ClassMethods.html b/docs/OAuth2/FilteredAttributes/ClassMethods.html index 94807c82..e0290629 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 0a0d9c6e..8ef77038 100644 --- a/docs/OAuth2/Response.html +++ b/docs/OAuth2/Response.html @@ -1619,7 +1619,7 @@

    diff --git a/docs/OAuth2/Strategy.html b/docs/OAuth2/Strategy.html index 33004cea..e1ccd313 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 9b50b18f..7a06b005 100644 --- a/docs/OAuth2/Strategy/Assertion.html +++ b/docs/OAuth2/Strategy/Assertion.html @@ -481,7 +481,7 @@

    diff --git a/docs/OAuth2/Strategy/AuthCode.html b/docs/OAuth2/Strategy/AuthCode.html index 66f488e8..5242b1c8 100644 --- a/docs/OAuth2/Strategy/AuthCode.html +++ b/docs/OAuth2/Strategy/AuthCode.html @@ -479,7 +479,7 @@

    diff --git a/docs/OAuth2/Strategy/Base.html b/docs/OAuth2/Strategy/Base.html index 25d0e02c..f27dc542 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 0ae17856..6a8cfba9 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 1c4f1a99..1887c973 100644 --- a/docs/OAuth2/Strategy/Implicit.html +++ b/docs/OAuth2/Strategy/Implicit.html @@ -418,7 +418,7 @@

    diff --git a/docs/OAuth2/Strategy/Password.html b/docs/OAuth2/Strategy/Password.html index 040c2902..bc0021ae 100644 --- a/docs/OAuth2/Strategy/Password.html +++ b/docs/OAuth2/Strategy/Password.html @@ -372,7 +372,7 @@

    diff --git a/docs/OAuth2/Version.html b/docs/OAuth2/Version.html index cd15b2c4..2d7730b3 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 1f2eec5b..f5410716 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 e953df4b..8e99bef0 100644 --- a/docs/file.CHANGELOG.html +++ b/docs/file.CHANGELOG.html @@ -1290,7 +1290,7 @@

    diff --git a/docs/file.CITATION.html b/docs/file.CITATION.html deleted file mode 100644 index 6667e4a7..00000000 --- a/docs/file.CITATION.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - File: CITATION - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    cff-version: 1.2.0
    -title: oauth2
    -message: >-
    - If you use this work and you want to cite it,
    - then you can use the metadata from this file.
    -type: software
    -authors:

    -
      -
    • given-names: Peter Hurn
      -family-names: Boling
      -email: peter@railsbling.com
      -affiliation: railsbling.com
      -orcid: ‘https://orcid.org/0009-0008-8519-441X’
      -identifiers:
    • -
    • type: url
      -value: ‘https://github.com/ruby-oauth/oauth2’
      -description: oauth2
      -repository-code: ‘https://github.com/ruby-oauth/oauth2’
      -abstract: >-
      - oauth2
      -license: See license file
    • -
    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.CODE_OF_CONDUCT.html b/docs/file.CODE_OF_CONDUCT.html index dbbd6d87..cf36e06e 100644 --- a/docs/file.CODE_OF_CONDUCT.html +++ b/docs/file.CODE_OF_CONDUCT.html @@ -191,7 +191,7 @@

    Attribution

    diff --git a/docs/file.CONTRIBUTING.html b/docs/file.CONTRIBUTING.html index 0a22870a..dd5ef561 100644 --- a/docs/file.CONTRIBUTING.html +++ b/docs/file.CONTRIBUTING.html @@ -296,7 +296,7 @@

    Manual process

    diff --git a/docs/file.FUNDING.html b/docs/file.FUNDING.html index 3a6b386d..9c4d8142 100644 --- a/docs/file.FUNDING.html +++ b/docs/file.FUNDING.html @@ -99,7 +99,7 @@

    Another Way to Support Open diff --git a/docs/file.IRP.html b/docs/file.IRP.html index 38d6f80d..84142c56 100644 --- a/docs/file.IRP.html +++ b/docs/file.IRP.html @@ -203,7 +203,7 @@

    Appendix: Example checklist diff --git a/docs/file.LICENSE.html b/docs/file.LICENSE.html index 8befb1fa..ee6cf1a9 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 2f34a113..46fdcb1b 100644 --- a/docs/file.OIDC.html +++ b/docs/file.OIDC.html @@ -224,7 +224,7 @@

    Optionally: call UserInfo

    diff --git a/docs/file.README.html b/docs/file.README.html index 2dc94f80..3d63387d 100644 --- a/docs/file.README.html +++ b/docs/file.README.html @@ -1677,7 +1677,7 @@

    Please give the project a star ⭐ ♥ diff --git a/docs/file.REEK.html b/docs/file.REEK.html deleted file mode 100644 index bc4f2767..00000000 --- a/docs/file.REEK.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: REEK - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -
    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.RUBOCOP.html b/docs/file.RUBOCOP.html index fcb0033e..d616dada 100644 --- a/docs/file.RUBOCOP.html +++ b/docs/file.RUBOCOP.html @@ -160,7 +160,7 @@

    Benefits of rubocop_gradual

    diff --git a/docs/file.SECURITY.html b/docs/file.SECURITY.html index bc43ae18..d2d62611 100644 --- a/docs/file.SECURITY.html +++ b/docs/file.SECURITY.html @@ -93,7 +93,7 @@

    Additional Support

    diff --git a/docs/file.THREAT_MODEL.html b/docs/file.THREAT_MODEL.html index 6307deac..e6487b53 100644 --- a/docs/file.THREAT_MODEL.html +++ b/docs/file.THREAT_MODEL.html @@ -206,7 +206,7 @@

    8. References

    diff --git a/docs/file.access_token.html b/docs/file.access_token.html deleted file mode 100644 index e6fc7b16..00000000 --- a/docs/file.access_token.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - File: access_token - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - class AccessToken
    - def self.from_hash: (OAuth2::Client, Hash[untyped, untyped]) -> OAuth2::AccessToken
    - def self.from_kvform: (OAuth2::Client, String) -> OAuth2::AccessToken

    - -
    def initialize: (OAuth2::Client, String, ?Hash[Symbol, untyped]) -> void
    -def []: (String | Symbol) -> untyped
    -def expires?: () -> bool
    -def expired?: () -> bool
    -def refresh: (?Hash[untyped, untyped], ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::AccessToken
    -def revoke: (?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -def to_hash: () -> Hash[Symbol, untyped]
    -def request: (Symbol, String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -def get: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -def post: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -def put: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -def patch: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -def delete: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -def headers: () -> Hash[String, String]
    -def configure_authentication!: (Hash[Symbol, untyped], Symbol) -> void
    -def convert_expires_at: (untyped) -> (Time | Integer | nil)
    -
    -attr_accessor response: OAuth2::Response   end end
    -
    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.authenticator.html b/docs/file.authenticator.html deleted file mode 100644 index 24436d9e..00000000 --- a/docs/file.authenticator.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - File: authenticator - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - class Authenticator
    - include OAuth2::FilteredAttributes

    - -
    attr_reader mode: (Symbol | String)
    -attr_reader id: String?
    -attr_reader secret: String?
    -
    -def initialize: (String? id, String? secret, (Symbol | String) mode) -> void
    -
    -def apply: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
    -
    -def self.encode_basic_auth: (String, String) -> String
    -
    -private
    -
    -def apply_params_auth: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
    -def apply_client_id: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
    -def apply_basic_auth: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
    -def basic_auth_header: () -> Hash[String, String]   end end
    -
    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.client.html b/docs/file.client.html deleted file mode 100644 index c9691758..00000000 --- a/docs/file.client.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - File: client - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - class Client
    - RESERVED_REQ_KEYS: Array[String]
    - RESERVED_PARAM_KEYS: Array[String]

    - -
    include OAuth2::FilteredAttributes
    -
    -attr_reader id: String
    -attr_reader secret: String
    -attr_reader site: String?
    -attr_accessor options: Hash[Symbol, untyped]
    -attr_writer connection: untyped
    -
    -def initialize: (String client_id, String client_secret, ?Hash[Symbol, untyped]) { (untyped) -> void } -> void
    -
    -def site=: (String) -> String
    -
    -def connection: () -> untyped
    -
    -def authorize_url: (?Hash[untyped, untyped]) -> String
    -def token_url: (?Hash[untyped, untyped]) -> String
    -def revoke_url: (?Hash[untyped, untyped]) -> String
    -
    -def request: (Symbol verb, String url, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -
    -def get_token: (Hash[untyped, untyped] params, ?Hash[Symbol, untyped] access_token_opts, ?Proc) { (Hash[Symbol, untyped]) -> void } -> (OAuth2::AccessToken | nil)
    -
    -def revoke_token: (String token, ?String token_type_hint, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
    -
    -def http_method: () -> Symbol
    -
    -def auth_code: () -> OAuth2::Strategy::AuthCode
    -def implicit: () -> OAuth2::Strategy::Implicit
    -def password: () -> OAuth2::Strategy::Password
    -def client_credentials: () -> OAuth2::Strategy::ClientCredentials
    -def assertion: () -> OAuth2::Strategy::Assertion
    -
    -def redirection_params: () -> Hash[String, String]
    -
    -private
    -
    -def params_to_req_opts: (Hash[untyped, untyped]) -> Hash[Symbol, untyped]
    -def parse_snaky_params_headers: (Hash[untyped, untyped]) -> [Symbol, bool, untyped, (Symbol | nil), Hash[untyped, untyped], Hash[String, String]]
    -def execute_request: (Symbol verb, String url, ?Hash[Symbol, untyped]) { (Faraday::Request) -> void } -> OAuth2::Response
    -def authenticator: () -> OAuth2::Authenticator
    -def parse_response_legacy: (OAuth2::Response, Hash[Symbol, untyped], Proc) -> (OAuth2::AccessToken | nil)
    -def parse_response: (OAuth2::Response, Hash[Symbol, untyped]) -> (OAuth2::AccessToken | nil)
    -def build_access_token: (OAuth2::Response, Hash[Symbol, untyped], untyped) -> OAuth2::AccessToken
    -def build_access_token_legacy: (OAuth2::Response, Hash[Symbol, untyped], Proc) -> (OAuth2::AccessToken | nil)
    -def oauth_debug_logging: (untyped) -> void   end end
    -
    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.error.html b/docs/file.error.html deleted file mode 100644 index 0934c7fd..00000000 --- a/docs/file.error.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - File: error - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - class Error < StandardError
    - def initialize: (OAuth2::Response) -> void
    - def code: () -> (String | Integer | nil)
    - def description: () -> (String | nil)
    - def response: () -> OAuth2::Response
    - end
    -end

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.filtered_attributes.html b/docs/file.filtered_attributes.html deleted file mode 100644 index 3b36819a..00000000 --- a/docs/file.filtered_attributes.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - File: filtered_attributes - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - module FilteredAttributes
    - def self.included: (untyped) -> untyped
    - def filtered_attributes: (*String) -> void
    - end
    -end

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2-2.0.10.gem.html b/docs/file.oauth2-2.0.10.gem.html deleted file mode 100644 index b00e56e9..00000000 --- a/docs/file.oauth2-2.0.10.gem.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: oauth2-2.0.10.gem - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    e692f68ab79677ee7fa9300bbd5e0c41de08642d51659a49ca7fd742230445601ad3c2d271ee110718d58a27383aba0c25ddbdbef5b13f7c18585cdfda74850b

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2-2.0.11.gem.html b/docs/file.oauth2-2.0.11.gem.html deleted file mode 100644 index 11369457..00000000 --- a/docs/file.oauth2-2.0.11.gem.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: oauth2-2.0.11.gem - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    048743f9efd89460231738885c9c0de7b36433055eefc66331b91eee343885cd9145bbac239c6121d13b716633fb8385fa886ce854bf14142f9894e6c8f19ba2

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2-2.0.12.gem.html b/docs/file.oauth2-2.0.12.gem.html deleted file mode 100644 index c691479c..00000000 --- a/docs/file.oauth2-2.0.12.gem.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: oauth2-2.0.12.gem - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    a209c7a0c4b9d46ccb00e750af8899c01d52648ca77a0d40b934593de53edc4f2774440fc50733c0e5098672c6c5a4a20f8709046be427fcf032f45922dff2d2

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2-2.0.13.gem.html b/docs/file.oauth2-2.0.13.gem.html deleted file mode 100644 index 6119b737..00000000 --- a/docs/file.oauth2-2.0.13.gem.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: oauth2-2.0.13.gem - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    3bfe481d98f859f37f3b90ced2b8856a843eef0f2e0263163cccc14430047bc3cd03d28597f48daa3d623b52d692c3b3e7c2dc26df5eb588dd82d28608fba639

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2-2.0.14.gem.html b/docs/file.oauth2-2.0.14.gem.html deleted file mode 100644 index 39830f8f..00000000 --- a/docs/file.oauth2-2.0.14.gem.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: oauth2-2.0.14.gem - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    5ce561a6b103a123d9b96e1e4725c07094bd6e58c135cc775ae9d5a055c031169ca6d6de379c2569daf1dd8ab2727079db3c80aa8568d6947e94a0c06b4c6d2b

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2-2.0.15.gem.html b/docs/file.oauth2-2.0.15.gem.html deleted file mode 100644 index af525d3c..00000000 --- a/docs/file.oauth2-2.0.15.gem.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: oauth2-2.0.15.gem - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    287a5d2cff87b4f37dde7b97f0fc31ee4c79edcc451b33694d1ba6f13d218cd04848780a857b94b93b656d6d81de4f4fcb4e8345f432cee17a6d96bd3f313df2

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2-2.0.16.gem.html b/docs/file.oauth2-2.0.16.gem.html deleted file mode 100644 index 70c1903d..00000000 --- a/docs/file.oauth2-2.0.16.gem.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: oauth2-2.0.16.gem - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    49788bf25c3afcc08171f92c3c8a21b4bcd322aae0834f69ae77c08963f54be6c9155588ca66f82022af897ddd0bf28b0c5ee254bc9fe533d1a37b1d52f409be

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2-2.0.17.gem.html b/docs/file.oauth2-2.0.17.gem.html deleted file mode 100644 index 555bded1..00000000 --- a/docs/file.oauth2-2.0.17.gem.html +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - File: oauth2-2.0.17.gem - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    6385dfb2d4cb0309745de2d442d99c6148744abaca5599bd1e4f6038e99734d9cf90d1de83d1833e416e2682f0e3d6ae83e10a5a55d6e884b9cdc54e6070fb8b

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.oauth2.html b/docs/file.oauth2.html deleted file mode 100644 index ca8fc1ff..00000000 --- a/docs/file.oauth2.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - File: oauth2 - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - OAUTH_DEBUG: bool

    - -

    DEFAULT_CONFIG: untyped
    - @config: untyped

    - -

    def self.config: () -> untyped
    - def self.configure: () { (untyped) -> void } -> void
    -end

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.response.html b/docs/file.response.html deleted file mode 100644 index 2966fd2c..00000000 --- a/docs/file.response.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - File: response - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - class Response
    - DEFAULT_OPTIONS: Hash[Symbol, untyped]

    - -
    def self.register_parser: (Symbol key, (Array[String] | String) mime_types) { (String) -> untyped } -> void
    -
    -def initialize: (untyped response, parse: Symbol?, snaky: bool?, snaky_hash_klass: untyped?, options: Hash[Symbol, untyped]?) -> void
    -def headers: () -> Hash[untyped, untyped]
    -def status: () -> Integer
    -def body: () -> String
    -def parsed: () -> untyped
    -def content_type: () -> (String | nil)
    -def parser: () -> (untyped | nil)
    -
    -attr_reader response: untyped
    -attr_accessor options: Hash[Symbol, untyped]   end end
    -
    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.strategy.html b/docs/file.strategy.html deleted file mode 100644 index 94941ce1..00000000 --- a/docs/file.strategy.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - File: strategy - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - module Strategy
    - class Base
    - def initialize: (OAuth2::Client) -> void
    - end

    - -
    class AuthCode < Base
    -  def authorize_params: (?Hash[untyped, untyped]) -> Hash[untyped, untyped]
    -  def authorize_url: (?Hash[untyped, untyped]) -> String
    -  def get_token: (String, ?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
    -end
    -
    -class Implicit < Base
    -  def authorize_params: (?Hash[untyped, untyped]) -> Hash[untyped, untyped]
    -  def authorize_url: (?Hash[untyped, untyped]) -> String
    -  def get_token: (*untyped) -> void
    -end
    -
    -class Password < Base
    -  def authorize_url: () -> void
    -  def get_token: (String, String, ?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
    -end
    -
    -class ClientCredentials < Base
    -  def authorize_url: () -> void
    -  def get_token: (?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
    -end
    -
    -class Assertion < Base
    -  def authorize_url: () -> void
    -  def get_token: (Hash[untyped, untyped], Hash[Symbol, untyped], ?Hash[Symbol, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
    -end   end end
    -
    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/file.version.html b/docs/file.version.html deleted file mode 100644 index 3a21b7b8..00000000 --- a/docs/file.version.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - File: version - - — Documentation by YARD 0.9.37 - - - - - - - - - - - - - - - - - - - -
    - - -

    module OAuth2
    - module Version
    - VERSION: String
    - end
    -end

    -
    - - - -
    - - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 76368c46..b4641ae9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1677,7 +1677,7 @@

    Please give the project a star ⭐ ♥ diff --git a/docs/top-level-namespace.html b/docs/top-level-namespace.html index 9097f9bf..a4e375cf 100644 --- a/docs/top-level-namespace.html +++ b/docs/top-level-namespace.html @@ -100,7 +100,7 @@

    Defined Under Namespace

    From dd10b3295215a3b25d84568d65c4efc06aa6d0f6 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Fri, 7 Nov 2025 19:36:22 -0700 Subject: [PATCH 12/14] =?UTF-8?q?=F0=9F=91=B7=20json=20having=20trouble=20?= =?UTF-8?q?installing=20on=20TruffleRuby=20v23.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/truffle.yml | 99 ----------------------------------- README.md | 8 ++- 2 files changed, 3 insertions(+), 104 deletions(-) delete mode 100644 .github/workflows/truffle.yml diff --git a/.github/workflows/truffle.yml b/.github/workflows/truffle.yml deleted file mode 100644 index 67807231..00000000 --- a/.github/workflows/truffle.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Truffle - -permissions: - contents: read - -env: - K_SOUP_COV_DO: false - -on: - push: - branches: - - 'main' - - '*-stable' - tags: - - '!*' # Do not execute on tags - pull_request: - branches: - - '*' - # Allow manually triggering the workflow. - workflow_dispatch: - -# Cancels all previous workflow runs for the same branch that have not yet completed. -concurrency: - # The concurrency group contains the workflow name and the branch name. - group: "${{ github.workflow }}-${{ github.ref }}" - cancel-in-progress: true - -jobs: - test: - if: "!contains(github.event.commits[0].message, '[ci skip]') && !contains(github.event.commits[0].message, '[skip ci]')" - name: Specs ${{ matrix.ruby }} ${{ matrix.appraisal }}${{ matrix.name_extra || '' }} - runs-on: ubuntu-22.04 - continue-on-error: ${{ matrix.experimental || endsWith(matrix.ruby, 'head') }} - env: # $BUNDLE_GEMFILE must be set at job level, so it is set for all steps - BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}.gemfile - strategy: - matrix: - include: - # NOTE: truffleruby does not support upgrading rubygems. - # truffleruby-23.1 (targets Ruby 3.2 compatibility) - - ruby: "truffleruby-23.1" - appraisal: "ruby-3-2" - exec_cmd: "rake test" - gemfile: "Appraisal.root" - rubygems: default - bundler: default - - steps: - - name: Checkout - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} - uses: actions/checkout@v5 - - - name: Setup Ruby & RubyGems - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - rubygems: ${{ matrix.rubygems }} - bundler: ${{ matrix.bundler }} - bundler-cache: false - - # Raw `bundle` will use the BUNDLE_GEMFILE set to matrix.gemfile (i.e. Appraisal.root) - # We need to do this first to get appraisal installed. - # NOTE: This does not use the primary Gemfile at all. - - name: Install Root Appraisal - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} - run: bundle - - - name: "[Attempt 1] Install Root Appraisal" - id: bundleAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} - run: bundle - # Continue to the next step on failure - continue-on-error: true - - # Effectively an automatic retry of the previous step. - - name: "[Attempt 2] Install Root Appraisal" - id: bundleAttempt2 - # If bundleAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} - run: bundle - - - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" - id: bundleAppraisalAttempt1 - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle - # Continue to the next step on failure - continue-on-error: true - - # Effectively an automatic retry of the previous step. - - name: "[Attempt 2] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}" - id: bundleAppraisalAttempt2 - # If bundleAppraisalAttempt1 failed, try again here; Otherwise skip. - if: ${{ steps.bundleAppraisalAttempt1.outcome == 'failure' && (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle - - - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }} - if: ${{ (env.ACT && !(startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) || (!env.ACT && (startsWith(matrix.ruby, 'jruby')) || startsWith(matrix.ruby, 'truffleruby')) }} - run: bundle exec appraisal ${{ matrix.appraisal }} bundle exec ${{ matrix.exec_cmd }} diff --git a/README.md b/README.md index a5ec5975..5517fd43 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,9 @@ If it seems like you are in the wrong place, you might try one of these: | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] | |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i]
    [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] | -| Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i]
    [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] | +| Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i] ![Truffle Ruby 23.1 Compat][💎truby-23.1i]
    [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] | | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] | -| Works with MRI Ruby 2 | ![Ruby 2.2 Compat][💎ruby-2.2i]
    [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] | +| Works with MRI Ruby 2 | ![Ruby 2.2 Compat][💎ruby-2.2i]
    [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] | | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] | | Source | [![Source on GitLab.com][📜src-gl-img]][📜src-gl] [![Source on CodeBerg.org][📜src-cb-img]][📜src-cb] [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] | | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] | @@ -1414,8 +1414,6 @@ Thanks for RTFM. ☺️ [🚎7-us-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/unsupported.yml/badge.svg [🚎8-ho-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/hoary.yml [🚎8-ho-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/hoary.yml/badge.svg -[🚎9-t-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml -[🚎9-t-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml/badge.svg [🚎10-j-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml [🚎10-j-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml/badge.svg [🚎11-c-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml @@ -1444,7 +1442,7 @@ Thanks for RTFM. ☺️ [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue [💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink [💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink -[💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink +[💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue [💎jruby-9.1i]: https://img.shields.io/badge/JRuby-9.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red From 710c3419b7f0a8f483f73009b7039b5c366a3395 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Fri, 7 Nov 2025 20:39:54 -0700 Subject: [PATCH 13/14] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20yard-fence=20v0.5.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +- Gemfile.lock | 2 +- OIDC.md | 11 +- README.md | 8 +- docs/OAuth2.html | 2 +- docs/OAuth2/AccessToken.html | 2 +- docs/OAuth2/Authenticator.html | 2 +- docs/OAuth2/Client.html | 2 +- docs/OAuth2/Error.html | 2 +- docs/OAuth2/FilteredAttributes.html | 2 +- .../FilteredAttributes/ClassMethods.html | 2 +- docs/OAuth2/Response.html | 2 +- docs/OAuth2/Strategy.html | 2 +- docs/OAuth2/Strategy/Assertion.html | 2 +- docs/OAuth2/Strategy/AuthCode.html | 2 +- docs/OAuth2/Strategy/Base.html | 2 +- docs/OAuth2/Strategy/ClientCredentials.html | 2 +- docs/OAuth2/Strategy/Implicit.html | 2 +- docs/OAuth2/Strategy/Password.html | 2 +- docs/OAuth2/Version.html | 2 +- docs/_index.html | 2 +- docs/file.CHANGELOG.html | 15 +- docs/file.CITATION.html | 90 ++++++++++ docs/file.CODE_OF_CONDUCT.html | 2 +- docs/file.CONTRIBUTING.html | 2 +- docs/file.FUNDING.html | 2 +- docs/file.IRP.html | 2 +- docs/file.LICENSE.html | 2 +- docs/file.OIDC.html | 165 +++++++++++------- docs/file.README.html | 14 +- docs/file.REEK.html | 71 ++++++++ docs/file.RUBOCOP.html | 2 +- docs/file.SECURITY.html | 2 +- docs/file.THREAT_MODEL.html | 2 +- docs/file.access_token.html | 94 ++++++++++ docs/file.authenticator.html | 91 ++++++++++ docs/file.client.html | 121 +++++++++++++ docs/file.error.html | 78 +++++++++ docs/file.filtered_attributes.html | 76 ++++++++ docs/file.oauth2-2.0.10.gem.html | 71 ++++++++ docs/file.oauth2-2.0.11.gem.html | 71 ++++++++ docs/file.oauth2-2.0.12.gem.html | 71 ++++++++ docs/file.oauth2-2.0.13.gem.html | 71 ++++++++ docs/file.oauth2-2.0.14.gem.html | 71 ++++++++ docs/file.oauth2-2.0.15.gem.html | 71 ++++++++ docs/file.oauth2-2.0.16.gem.html | 71 ++++++++ docs/file.oauth2-2.0.17.gem.html | 71 ++++++++ docs/file.oauth2.html | 79 +++++++++ docs/file.response.html | 87 +++++++++ docs/file.strategy.html | 103 +++++++++++ docs/file.version.html | 75 ++++++++ docs/index.html | 14 +- docs/top-level-namespace.html | 2 +- 53 files changed, 1708 insertions(+), 117 deletions(-) create mode 100644 docs/file.CITATION.html create mode 100644 docs/file.REEK.html create mode 100644 docs/file.access_token.html create mode 100644 docs/file.authenticator.html create mode 100644 docs/file.client.html create mode 100644 docs/file.error.html create mode 100644 docs/file.filtered_attributes.html create mode 100644 docs/file.oauth2-2.0.10.gem.html create mode 100644 docs/file.oauth2-2.0.11.gem.html create mode 100644 docs/file.oauth2-2.0.12.gem.html create mode 100644 docs/file.oauth2-2.0.13.gem.html create mode 100644 docs/file.oauth2-2.0.14.gem.html create mode 100644 docs/file.oauth2-2.0.15.gem.html create mode 100644 docs/file.oauth2-2.0.16.gem.html create mode 100644 docs/file.oauth2-2.0.17.gem.html create mode 100644 docs/file.oauth2.html create mode 100644 docs/file.response.html create mode 100644 docs/file.strategy.html create mode 100644 docs/file.version.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 607363f7..17cc8fb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,11 +21,13 @@ Please file a bug if you notice a violation of semantic versioning. ### Added - [gh!683][gh!683], [gh!684][gh!684] - Improve documentation by @pboling +- [gh!686][gh!686]- Add Incident Response Plan by @pboling +- [gh!687][gh!687]- Add Threat Model by @pboling ### Changed -- [gh!685][gh!685] - upgrade kettle-dev v1.1.24 by pboling -- upgrade kettle-dev v1.1.26 by pboling +- [gh!685][gh!685] - upgrade kettle-dev v1.1.24 by @pboling +- upgrade kettle-dev v1.1.51 by @pboling - Add open collective donors to README ### Deprecated @@ -34,11 +36,16 @@ Please file a bug if you notice a violation of semantic versioning. ### Fixed +- [gh!690][gh!690] - Add yard-fence to handle braces within code fences in markdown properly by @pboling + ### Security [gh!683]: https://github.com/ruby-oauth/oauth2/pull/683 [gh!684]: https://github.com/ruby-oauth/oauth2/pull/684 [gh!685]: https://github.com/ruby-oauth/oauth2/pull/685 +[gh!686]: https://github.com/ruby-oauth/oauth2/pull/686 +[gh!687]: https://github.com/ruby-oauth/oauth2/pull/687 +[gh!690]: https://github.com/ruby-oauth/oauth2/pull/690 ## [2.0.17] - 2025-09-15 diff --git a/Gemfile.lock b/Gemfile.lock index dbb04aaf..8c4b1f4b 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.4.0) + yard-fence (0.5.0) rdoc (~> 6.11) version_gem (~> 1.1, >= 1.1.9) yard (~> 0.9, >= 0.9.37) diff --git a/OIDC.md b/OIDC.md index 2bd7c708..22e31d11 100644 --- a/OIDC.md +++ b/OIDC.md @@ -16,11 +16,13 @@ If any other libraries would like to be added to this list, please open an issue This document complements the inline documentation by focusing on OpenID Connect (OIDC) 1.0 usage patterns when using this gem as an OAuth 2.0 client library. Scope of this document + - Audience: Developers building an OAuth 2.0/OIDC Relying Party (RP, aka client) in Ruby. - Non-goals: This gem does not implement an OIDC Provider (OP, aka Authorization Server); for OP/server see other projects (e.g., doorkeeper + oidc extensions). - Status: Informational documentation with links to normative specs. The gem intentionally remains protocol-agnostic beyond OAuth 2.0; OIDC specifics (like ID Token validation) must be handled by your application. Key concepts refresher + - OAuth 2.0 delegates authorization; it does not define authentication of the end-user. - OIDC layers an identity layer on top of OAuth 2.0, introducing: - ID Token: a JWT carrying claims about the authenticated end-user and the authentication event. @@ -29,6 +31,7 @@ Key concepts refresher - Discovery and Dynamic Client Registration (optional for providers/clients that support them). What this gem provides for OIDC + - All OAuth 2.0 client capabilities required for OIDC flows: building authorization requests, exchanging authorization codes, refreshing tokens, and making authenticated resource requests. - Transport and parsing conveniences (snaky hash, Faraday integration, error handling, etc.). - Optional client authentication schemes useful with OIDC deployments: @@ -38,6 +41,7 @@ What this gem provides for OIDC - private_key_jwt (OIDC-compliant when configured per OP requirements) 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) @@ -124,10 +128,12 @@ userinfo = token.get("/userinfo").parsed ``` Notes on discovery and registration -- Discovery: Most OPs publish configuration at {issuer}/.well-known/openid-configuration (OIDC Discovery 1.0). From there, resolve authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, etc. + +- Discovery: Most OPs publish configuration at `{issuer}/.well-known/openid-configuration` (OIDC Discovery 1.0). From there, resolve authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, etc. - Dynamic Client Registration: Some OPs allow registering clients programmatically (OIDC Dynamic Client Registration 1.0). This gem does not implement registration; use a plain HTTP client or Faraday and store credentials securely. Common pitfalls and tips + - Always request the openid scope when you expect an ID Token. Without it, the OP may behave as vanilla OAuth 2.0. - Validate ID Token signature and claims before trusting any identity data. Do not rely solely on the presence of an id_token field. - Prefer Authorization Code + PKCE. Avoid Implicit; it is discouraged in modern guidance and may be disabled by providers. @@ -136,6 +142,7 @@ Common pitfalls and tips - When using private_key_jwt, ensure the "aud" (or token_url) and "iss/sub" claims are set per the OP’s rules, and include kid in the JWT header when required so the OP can select the right key. Relevant specifications and references + - OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html - OIDC Core (final): https://openid.net/specs/openid-connect-core-1_0-final.html - How OIDC works: https://openid.net/developers/how-connect-works/ @@ -150,9 +157,11 @@ Relevant specifications and references - Spring Authorization Server’s list of OAuth2/OIDC specs: https://github.com/spring-projects/spring-authorization-server/wiki/OAuth2-and-OIDC-Specifications See also + - README sections on OAuth 2.1 notes and OIDC notes - Strategy classes under lib/oauth2/strategy for flow helpers - Specs under spec/oauth2 for concrete usage patterns Contributions welcome + - If you discover provider-specific nuances, consider contributing examples or clarifications (without embedding provider-specific hacks into the library). diff --git a/README.md b/README.md index 5517fd43..13ab7ba0 100644 --- a/README.md +++ b/README.md @@ -843,8 +843,8 @@ me = long_lived.get("/me", params: {fields: "id,username"}).parsed Tips: -- Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET. -- If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }. +- Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for `GET` requests. +- If you need a custom rule, you can pass a `Proc` for `mode`, e.g. `mode: ->(verb) { verb == :get ? :query : :header }`. ### Refresh Tokens @@ -991,9 +991,9 @@ client = OAuth2::Client.new( end ``` -##### Using flat query params (Faraday::FlatParamsEncoder) +##### 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. +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" diff --git a/docs/OAuth2.html b/docs/OAuth2.html index 20892da0..1ffcdd17 100644 --- a/docs/OAuth2.html +++ b/docs/OAuth2.html @@ -415,7 +415,7 @@

    diff --git a/docs/OAuth2/AccessToken.html b/docs/OAuth2/AccessToken.html index dc0bf8cc..2d0ed1df 100644 --- a/docs/OAuth2/AccessToken.html +++ b/docs/OAuth2/AccessToken.html @@ -3083,7 +3083,7 @@

    diff --git a/docs/OAuth2/Authenticator.html b/docs/OAuth2/Authenticator.html index a5f21aec..53736878 100644 --- a/docs/OAuth2/Authenticator.html +++ b/docs/OAuth2/Authenticator.html @@ -883,7 +883,7 @@

    diff --git a/docs/OAuth2/Client.html b/docs/OAuth2/Client.html index 4658bb2f..9ff6fc48 100644 --- a/docs/OAuth2/Client.html +++ b/docs/OAuth2/Client.html @@ -2654,7 +2654,7 @@

    diff --git a/docs/OAuth2/Error.html b/docs/OAuth2/Error.html index 2f174eed..1401cd95 100644 --- a/docs/OAuth2/Error.html +++ b/docs/OAuth2/Error.html @@ -772,7 +772,7 @@

    diff --git a/docs/OAuth2/FilteredAttributes.html b/docs/OAuth2/FilteredAttributes.html index c0197942..7c738657 100644 --- a/docs/OAuth2/FilteredAttributes.html +++ b/docs/OAuth2/FilteredAttributes.html @@ -335,7 +335,7 @@

    diff --git a/docs/OAuth2/FilteredAttributes/ClassMethods.html b/docs/OAuth2/FilteredAttributes/ClassMethods.html index e0290629..75bb5568 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 8ef77038..dccdc25b 100644 --- a/docs/OAuth2/Response.html +++ b/docs/OAuth2/Response.html @@ -1619,7 +1619,7 @@

    diff --git a/docs/OAuth2/Strategy.html b/docs/OAuth2/Strategy.html index e1ccd313..c629c668 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 7a06b005..6153d6fa 100644 --- a/docs/OAuth2/Strategy/Assertion.html +++ b/docs/OAuth2/Strategy/Assertion.html @@ -481,7 +481,7 @@

    diff --git a/docs/OAuth2/Strategy/AuthCode.html b/docs/OAuth2/Strategy/AuthCode.html index 5242b1c8..d5081e25 100644 --- a/docs/OAuth2/Strategy/AuthCode.html +++ b/docs/OAuth2/Strategy/AuthCode.html @@ -479,7 +479,7 @@

    diff --git a/docs/OAuth2/Strategy/Base.html b/docs/OAuth2/Strategy/Base.html index f27dc542..dd410d38 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 6a8cfba9..efa4fdb8 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 1887c973..32f63999 100644 --- a/docs/OAuth2/Strategy/Implicit.html +++ b/docs/OAuth2/Strategy/Implicit.html @@ -418,7 +418,7 @@

    diff --git a/docs/OAuth2/Strategy/Password.html b/docs/OAuth2/Strategy/Password.html index bc0021ae..91d2fe7c 100644 --- a/docs/OAuth2/Strategy/Password.html +++ b/docs/OAuth2/Strategy/Password.html @@ -372,7 +372,7 @@

    diff --git a/docs/OAuth2/Version.html b/docs/OAuth2/Version.html index 2d7730b3..35c06cb4 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 f5410716..f023b32d 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 8e99bef0..b22f44a2 100644 --- a/docs/file.CHANGELOG.html +++ b/docs/file.CHANGELOG.html @@ -75,14 +75,18 @@

    Added

    • gh!683, gh!684 - Improve documentation by @pboling
    • +
    • +gh!686- Add Incident Response Plan by @pboling
    • +
    • +gh!687- Add Threat Model by @pboling

    Changed

    • -gh!685 - upgrade kettle-dev v1.1.24 by pboling
    • -
    • upgrade kettle-dev v1.1.26 by pboling +gh!685 - upgrade kettle-dev v1.1.24 by @pboling
    • +
    • upgrade kettle-dev v1.1.51 by @pboling
      • Add open collective donors to README
      @@ -95,6 +99,11 @@

      Removed

      Fixed

      +
        +
      • +gh!690 - Add yard-fence to handle braces within code fences in markdown properly by @pboling
      • +
      +

      Security

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

      diff --git a/docs/file.CITATION.html b/docs/file.CITATION.html new file mode 100644 index 00000000..ec687c30 --- /dev/null +++ b/docs/file.CITATION.html @@ -0,0 +1,90 @@ + + + + + + + File: CITATION + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      cff-version: 1.2.0 +title: oauth2 +message: >- + If you use this work and you want to cite it, + then you can use the metadata from this file. +type: software +authors: + - given-names: Peter Hurn + family-names: Boling + email: peter@railsbling.com + affiliation: railsbling.com + orcid: ‘https://orcid.org/0009-0008-8519-441X’ +identifiers: + - type: url + value: ‘https://github.com/ruby-oauth/oauth2’ + description: oauth2 +repository-code: ‘https://github.com/ruby-oauth/oauth2’ +abstract: >- + oauth2 +license: See license file

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.CODE_OF_CONDUCT.html b/docs/file.CODE_OF_CONDUCT.html index cf36e06e..818acbd7 100644 --- a/docs/file.CODE_OF_CONDUCT.html +++ b/docs/file.CODE_OF_CONDUCT.html @@ -191,7 +191,7 @@

      Attribution

      diff --git a/docs/file.CONTRIBUTING.html b/docs/file.CONTRIBUTING.html index dd5ef561..08f3438c 100644 --- a/docs/file.CONTRIBUTING.html +++ b/docs/file.CONTRIBUTING.html @@ -296,7 +296,7 @@

      Manual process

      diff --git a/docs/file.FUNDING.html b/docs/file.FUNDING.html index 9c4d8142..59b0b074 100644 --- a/docs/file.FUNDING.html +++ b/docs/file.FUNDING.html @@ -99,7 +99,7 @@

      Another Way to Support Open diff --git a/docs/file.IRP.html b/docs/file.IRP.html index 84142c56..6664d497 100644 --- a/docs/file.IRP.html +++ b/docs/file.IRP.html @@ -203,7 +203,7 @@

      Appendix: Example checklist diff --git a/docs/file.LICENSE.html b/docs/file.LICENSE.html index ee6cf1a9..428dfecc 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 46fdcb1b..e73faa29 100644 --- a/docs/file.OIDC.html +++ b/docs/file.OIDC.html @@ -80,38 +80,56 @@

      Raw OIDC with ruby-oauth/oauth2

      This document complements the inline documentation by focusing on OpenID Connect (OIDC) 1.0 usage patterns when using this gem as an OAuth 2.0 client library.

      -

      Scope of this document -- Audience: Developers building an OAuth 2.0/OIDC Relying Party (RP, aka client) in Ruby. -- Non-goals: This gem does not implement an OIDC Provider (OP, aka Authorization Server); for OP/server see other projects (e.g., doorkeeper + oidc extensions). -- Status: Informational documentation with links to normative specs. The gem intentionally remains protocol-agnostic beyond OAuth 2.0; OIDC specifics (like ID Token validation) must be handled by your application.

      - -

      Key concepts refresher -- OAuth 2.0 delegates authorization; it does not define authentication of the end-user. -- OIDC layers an identity layer on top of OAuth 2.0, introducing: - - ID Token: a JWT carrying claims about the authenticated end-user and the authentication event. - - Standardized scopes: openid (mandatory), profile, email, address, phone, offline_access, and others. - - UserInfo endpoint: a protected resource for retrieving user profile claims. - - Discovery and Dynamic Client Registration (optional for providers/clients that support them).

      - -

      What this gem provides for OIDC -- All OAuth 2.0 client capabilities required for OIDC flows: building authorization requests, exchanging authorization codes, refreshing tokens, and making authenticated resource requests. -- Transport and parsing conveniences (snaky hash, Faraday integration, error handling, etc.). -- Optional client authentication schemes useful with OIDC deployments: - - basic_auth (default) - - request_body (legacy) - - tls_client_auth (MTLS) - - private_key_jwt (OIDC-compliant when configured per OP requirements)

      - -

      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) - 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. -- Session/state management: continue to validate state to mitigate CSRF; use exact redirect_uri matching.

      +

      Scope of this document

      + +
        +
      • Audience: Developers building an OAuth 2.0/OIDC Relying Party (RP, aka client) in Ruby.
      • +
      • Non-goals: This gem does not implement an OIDC Provider (OP, aka Authorization Server); for OP/server see other projects (e.g., doorkeeper + oidc extensions).
      • +
      • Status: Informational documentation with links to normative specs. The gem intentionally remains protocol-agnostic beyond OAuth 2.0; OIDC specifics (like ID Token validation) must be handled by your application.
      • +
      + +

      Key concepts refresher

      + +
        +
      • OAuth 2.0 delegates authorization; it does not define authentication of the end-user.
      • +
      • OIDC layers an identity layer on top of OAuth 2.0, introducing: +
          +
        • ID Token: a JWT carrying claims about the authenticated end-user and the authentication event.
        • +
        • Standardized scopes: openid (mandatory), profile, email, address, phone, offline_access, and others.
        • +
        • UserInfo endpoint: a protected resource for retrieving user profile claims.
        • +
        • Discovery and Dynamic Client Registration (optional for providers/clients that support them).
        • +
        +
      • +
      + +

      What this gem provides for OIDC

      + +
        +
      • All OAuth 2.0 client capabilities required for OIDC flows: building authorization requests, exchanging authorization codes, refreshing tokens, and making authenticated resource requests.
      • +
      • Transport and parsing conveniences (snaky hash, Faraday integration, error handling, etc.).
      • +
      • Optional client authentication schemes useful with OIDC deployments: +
          +
        • basic_auth (default)
        • +
        • request_body (legacy)
        • +
        • tls_client_auth (MTLS)
        • +
        • private_key_jwt (OIDC-compliant when configured per OP requirements)
        • +
        +
      • +
      + +

      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) +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.
      • +
      • Session/state management: continue to validate state to mitigate CSRF; use exact redirect_uri matching.
      • +

      Minimal OIDC Authorization Code example

      @@ -188,43 +206,58 @@

      Optionally: call UserInfo

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

      -

      Notes on discovery and registration -- Discovery: Most OPs publish configuration at {issuer}/.well-known/openid-configuration (OIDC Discovery 1.0). From there, resolve authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, etc. -- Dynamic Client Registration: Some OPs allow registering clients programmatically (OIDC Dynamic Client Registration 1.0). This gem does not implement registration; use a plain HTTP client or Faraday and store credentials securely.

      - -

      Common pitfalls and tips -- Always request the openid scope when you expect an ID Token. Without it, the OP may behave as vanilla OAuth 2.0. -- Validate ID Token signature and claims before trusting any identity data. Do not rely solely on the presence of an id_token field. -- Prefer Authorization Code + PKCE. Avoid Implicit; it is discouraged in modern guidance and may be disabled by providers. -- Use exact redirect_uri matching, and keep your allow-list short. -- For public clients that use refresh tokens, prefer sender-constrained tokens (DPoP/MTLS) or rotation with one-time-use refresh tokens, per modern best practices. -- When using private_key_jwt, ensure the “aud” (or token_url) and “iss/sub” claims are set per the OP’s rules, and include kid in the JWT header when required so the OP can select the right key.

      - -

      Relevant specifications and references -- OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html -- OIDC Core (final): https://openid.net/specs/openid-connect-core-1_0-final.html -- How OIDC works: https://openid.net/developers/how-connect-works/ -- OpenID Connect home: https://openid.net/connect/ -- OIDC Discovery 1.0: https://openid.net/specs/openid-connect-discovery-1_0.html -- OIDC Dynamic Client Registration 1.0: https://openid.net/specs/openid-connect-registration-1_0.html -- OIDC Session Management 1.0: https://openid.net/specs/openid-connect-session-1_0.html -- OIDC RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html -- OIDC Back-Channel Logout 1.0: https://openid.net/specs/openid-connect-backchannel-1_0.html -- OIDC Front-Channel Logout 1.0: https://openid.net/specs/openid-connect-frontchannel-1_0.html -- Auth0 OIDC overview: https://auth0.com/docs/authenticate/protocols/openid-connect-protocol -- Spring Authorization Server’s list of OAuth2/OIDC specs: https://github.com/spring-projects/spring-authorization-server/wiki/OAuth2-and-OIDC-Specifications

      - -

      See also -- README sections on OAuth 2.1 notes and OIDC notes -- Strategy classes under lib/oauth2/strategy for flow helpers -- Specs under spec/oauth2 for concrete usage patterns

      - -

      Contributions welcome -- If you discover provider-specific nuances, consider contributing examples or clarifications (without embedding provider-specific hacks into the library).

      +

      Notes on discovery and registration

      + +
        +
      • Discovery: Most OPs publish configuration at {issuer}/.well-known/openid-configuration (OIDC Discovery 1.0). From there, resolve authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, etc.
      • +
      • Dynamic Client Registration: Some OPs allow registering clients programmatically (OIDC Dynamic Client Registration 1.0). This gem does not implement registration; use a plain HTTP client or Faraday and store credentials securely.
      • +
      + +

      Common pitfalls and tips

      + +
        +
      • Always request the openid scope when you expect an ID Token. Without it, the OP may behave as vanilla OAuth 2.0.
      • +
      • Validate ID Token signature and claims before trusting any identity data. Do not rely solely on the presence of an id_token field.
      • +
      • Prefer Authorization Code + PKCE. Avoid Implicit; it is discouraged in modern guidance and may be disabled by providers.
      • +
      • Use exact redirect_uri matching, and keep your allow-list short.
      • +
      • For public clients that use refresh tokens, prefer sender-constrained tokens (DPoP/MTLS) or rotation with one-time-use refresh tokens, per modern best practices.
      • +
      • When using private_key_jwt, ensure the “aud” (or token_url) and “iss/sub” claims are set per the OP’s rules, and include kid in the JWT header when required so the OP can select the right key.
      • +
      + +

      Relevant specifications and references

      + +
        +
      • OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html
      • +
      • OIDC Core (final): https://openid.net/specs/openid-connect-core-1_0-final.html
      • +
      • How OIDC works: https://openid.net/developers/how-connect-works/
      • +
      • OpenID Connect home: https://openid.net/connect/
      • +
      • OIDC Discovery 1.0: https://openid.net/specs/openid-connect-discovery-1_0.html
      • +
      • OIDC Dynamic Client Registration 1.0: https://openid.net/specs/openid-connect-registration-1_0.html
      • +
      • OIDC Session Management 1.0: https://openid.net/specs/openid-connect-session-1_0.html
      • +
      • OIDC RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
      • +
      • OIDC Back-Channel Logout 1.0: https://openid.net/specs/openid-connect-backchannel-1_0.html
      • +
      • OIDC Front-Channel Logout 1.0: https://openid.net/specs/openid-connect-frontchannel-1_0.html
      • +
      • Auth0 OIDC overview: https://auth0.com/docs/authenticate/protocols/openid-connect-protocol
      • +
      • Spring Authorization Server’s list of OAuth2/OIDC specs: https://github.com/spring-projects/spring-authorization-server/wiki/OAuth2-and-OIDC-Specifications
      • +
      + +

      See also

      + +
        +
      • README sections on OAuth 2.1 notes and OIDC notes
      • +
      • Strategy classes under lib/oauth2/strategy for flow helpers
      • +
      • Specs under spec/oauth2 for concrete usage patterns
      • +
      + +

      Contributions welcome

      + +
        +
      • If you discover provider-specific nuances, consider contributing examples or clarifications (without embedding provider-specific hacks into the library).
      • +
      diff --git a/docs/file.README.html b/docs/file.README.html index 3d63387d..f103d44c 100644 --- a/docs/file.README.html +++ b/docs/file.README.html @@ -103,7 +103,7 @@

      🔐 OAuth 2.0 Authorization Framework

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

      -

      Version GitHub tag (latest SemVer) License: MIT Downloads Rank Open Source Helpers CodeCov Test Coverage Coveralls Test Coverage QLTY Test Coverage QLTY Maintainability CI Heads CI Runtime Dependencies @ HEAD CI Current CI Truffle Ruby CI JRuby Deps Locked Deps Unlocked CI Supported CI Legacy CI Unsupported CI Ancient CI Test Coverage CI Style CodeQL Apache SkyWalking Eyes License Compatibility Check

      +

      Version GitHub tag (latest SemVer) License: MIT Downloads Rank Open Source Helpers CodeCov Test Coverage Coveralls Test Coverage QLTY Test Coverage QLTY Maintainability CI Heads CI Runtime Dependencies @ HEAD CI Current [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] CI JRuby Deps Locked Deps Unlocked CI Supported CI Legacy CI Unsupported CI Ancient CI Test Coverage CI Style CodeQL Apache SkyWalking Eyes License Compatibility Check

      if ci_badges.map(&:color).detect { it != "green"} ☝️ let me know, as I may have missed the discord notification.

      @@ -274,7 +274,7 @@

      💡 Info you can shake a stick at

      Works with Truffle Ruby -Truffle Ruby 22.3 Compat Truffle Ruby 23.0 Compat
      Truffle Ruby 23.1 Compat Truffle Ruby 24.1 Compat +Truffle Ruby 22.3 Compat Truffle Ruby 23.0 Compat Truffle Ruby 23.1 Compat
      Truffle Ruby 24.1 Compat @@ -1183,8 +1183,8 @@

      4) Example POST

      Tips:

        -
      • Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET.
      • -
      • If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }.
      • +
      • Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET requests.
      • +
      • If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }.

      Refresh Tokens

      @@ -1344,9 +1344,9 @@

      Faraday conn end

      -

      Using flat query params (Faraday::FlatParamsEncoder)
      +
      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.

      +

      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”

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

      Please give the project a star ⭐ ♥ diff --git a/docs/file.REEK.html b/docs/file.REEK.html new file mode 100644 index 00000000..fa6fc2fc --- /dev/null +++ b/docs/file.REEK.html @@ -0,0 +1,71 @@ + + + + + + + File: REEK + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +
      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.RUBOCOP.html b/docs/file.RUBOCOP.html index d616dada..99b4a4b1 100644 --- a/docs/file.RUBOCOP.html +++ b/docs/file.RUBOCOP.html @@ -160,7 +160,7 @@

      Benefits of rubocop_gradual

      diff --git a/docs/file.SECURITY.html b/docs/file.SECURITY.html index d2d62611..b1a7d6ea 100644 --- a/docs/file.SECURITY.html +++ b/docs/file.SECURITY.html @@ -93,7 +93,7 @@

      Additional Support

      diff --git a/docs/file.THREAT_MODEL.html b/docs/file.THREAT_MODEL.html index e6487b53..d9568287 100644 --- a/docs/file.THREAT_MODEL.html +++ b/docs/file.THREAT_MODEL.html @@ -206,7 +206,7 @@

      8. References

      diff --git a/docs/file.access_token.html b/docs/file.access_token.html new file mode 100644 index 00000000..1300c2b1 --- /dev/null +++ b/docs/file.access_token.html @@ -0,0 +1,94 @@ + + + + + + + File: access_token + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + class AccessToken + def self.from_hash: (OAuth2::Client, Hash[untyped, untyped]) -> OAuth2::AccessToken + def self.from_kvform: (OAuth2::Client, String) -> OAuth2::AccessToken

      + +
      def initialize: (OAuth2::Client, String, ?Hash[Symbol, untyped]) -> void
      +def []: (String | Symbol) -> untyped
      +def expires?: () -> bool
      +def expired?: () -> bool
      +def refresh: (?Hash[untyped, untyped], ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::AccessToken
      +def revoke: (?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +def to_hash: () -> Hash[Symbol, untyped]
      +def request: (Symbol, String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +def get: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +def post: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +def put: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +def patch: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +def delete: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +def headers: () -> Hash[String, String]
      +def configure_authentication!: (Hash[Symbol, untyped], Symbol) -> void
      +def convert_expires_at: (untyped) -> (Time | Integer | nil)
      +
      +attr_accessor response: OAuth2::Response   end end
      +
      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.authenticator.html b/docs/file.authenticator.html new file mode 100644 index 00000000..630200ca --- /dev/null +++ b/docs/file.authenticator.html @@ -0,0 +1,91 @@ + + + + + + + File: authenticator + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + class Authenticator + include OAuth2::FilteredAttributes

      + +
      attr_reader mode: (Symbol | String)
      +attr_reader id: String?
      +attr_reader secret: String?
      +
      +def initialize: (String? id, String? secret, (Symbol | String) mode) -> void
      +
      +def apply: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
      +
      +def self.encode_basic_auth: (String, String) -> String
      +
      +private
      +
      +def apply_params_auth: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
      +def apply_client_id: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
      +def apply_basic_auth: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
      +def basic_auth_header: () -> Hash[String, String]   end end
      +
      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.client.html b/docs/file.client.html new file mode 100644 index 00000000..a21933aa --- /dev/null +++ b/docs/file.client.html @@ -0,0 +1,121 @@ + + + + + + + File: client + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + class Client + RESERVED_REQ_KEYS: Array[String] + RESERVED_PARAM_KEYS: Array[String]

      + +
      include OAuth2::FilteredAttributes
      +
      +attr_reader id: String
      +attr_reader secret: String
      +attr_reader site: String?
      +attr_accessor options: Hash[Symbol, untyped]
      +attr_writer connection: untyped
      +
      +def initialize: (String client_id, String client_secret, ?Hash[Symbol, untyped]) { (untyped) -> void } -> void
      +
      +def site=: (String) -> String
      +
      +def connection: () -> untyped
      +
      +def authorize_url: (?Hash[untyped, untyped]) -> String
      +def token_url: (?Hash[untyped, untyped]) -> String
      +def revoke_url: (?Hash[untyped, untyped]) -> String
      +
      +def request: (Symbol verb, String url, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +
      +def get_token: (Hash[untyped, untyped] params, ?Hash[Symbol, untyped] access_token_opts, ?Proc) { (Hash[Symbol, untyped]) -> void } -> (OAuth2::AccessToken | nil)
      +
      +def revoke_token: (String token, ?String token_type_hint, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
      +
      +def http_method: () -> Symbol
      +
      +def auth_code: () -> OAuth2::Strategy::AuthCode
      +def implicit: () -> OAuth2::Strategy::Implicit
      +def password: () -> OAuth2::Strategy::Password
      +def client_credentials: () -> OAuth2::Strategy::ClientCredentials
      +def assertion: () -> OAuth2::Strategy::Assertion
      +
      +def redirection_params: () -> Hash[String, String]
      +
      +private
      +
      +def params_to_req_opts: (Hash[untyped, untyped]) -> Hash[Symbol, untyped]
      +def parse_snaky_params_headers: (Hash[untyped, untyped]) -> [Symbol, bool, untyped, (Symbol | nil), Hash[untyped, untyped], Hash[String, String]]
      +def execute_request: (Symbol verb, String url, ?Hash[Symbol, untyped]) { (Faraday::Request) -> void } -> OAuth2::Response
      +def authenticator: () -> OAuth2::Authenticator
      +def parse_response_legacy: (OAuth2::Response, Hash[Symbol, untyped], Proc) -> (OAuth2::AccessToken | nil)
      +def parse_response: (OAuth2::Response, Hash[Symbol, untyped]) -> (OAuth2::AccessToken | nil)
      +def build_access_token: (OAuth2::Response, Hash[Symbol, untyped], untyped) -> OAuth2::AccessToken
      +def build_access_token_legacy: (OAuth2::Response, Hash[Symbol, untyped], Proc) -> (OAuth2::AccessToken | nil)
      +def oauth_debug_logging: (untyped) -> void   end end
      +
      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.error.html b/docs/file.error.html new file mode 100644 index 00000000..d6e43057 --- /dev/null +++ b/docs/file.error.html @@ -0,0 +1,78 @@ + + + + + + + File: error + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + class Error < StandardError + def initialize: (OAuth2::Response) -> void + def code: () -> (String | Integer | nil) + def description: () -> (String | nil) + def response: () -> OAuth2::Response + end +end

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.filtered_attributes.html b/docs/file.filtered_attributes.html new file mode 100644 index 00000000..95f557bb --- /dev/null +++ b/docs/file.filtered_attributes.html @@ -0,0 +1,76 @@ + + + + + + + File: filtered_attributes + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + module FilteredAttributes + def self.included: (untyped) -> untyped + def filtered_attributes: (*String) -> void + end +end

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2-2.0.10.gem.html b/docs/file.oauth2-2.0.10.gem.html new file mode 100644 index 00000000..aaedb848 --- /dev/null +++ b/docs/file.oauth2-2.0.10.gem.html @@ -0,0 +1,71 @@ + + + + + + + File: oauth2-2.0.10.gem + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      e692f68ab79677ee7fa9300bbd5e0c41de08642d51659a49ca7fd742230445601ad3c2d271ee110718d58a27383aba0c25ddbdbef5b13f7c18585cdfda74850b

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2-2.0.11.gem.html b/docs/file.oauth2-2.0.11.gem.html new file mode 100644 index 00000000..299ada70 --- /dev/null +++ b/docs/file.oauth2-2.0.11.gem.html @@ -0,0 +1,71 @@ + + + + + + + File: oauth2-2.0.11.gem + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      048743f9efd89460231738885c9c0de7b36433055eefc66331b91eee343885cd9145bbac239c6121d13b716633fb8385fa886ce854bf14142f9894e6c8f19ba2

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2-2.0.12.gem.html b/docs/file.oauth2-2.0.12.gem.html new file mode 100644 index 00000000..f5efd44b --- /dev/null +++ b/docs/file.oauth2-2.0.12.gem.html @@ -0,0 +1,71 @@ + + + + + + + File: oauth2-2.0.12.gem + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      a209c7a0c4b9d46ccb00e750af8899c01d52648ca77a0d40b934593de53edc4f2774440fc50733c0e5098672c6c5a4a20f8709046be427fcf032f45922dff2d2

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2-2.0.13.gem.html b/docs/file.oauth2-2.0.13.gem.html new file mode 100644 index 00000000..72e8abb0 --- /dev/null +++ b/docs/file.oauth2-2.0.13.gem.html @@ -0,0 +1,71 @@ + + + + + + + File: oauth2-2.0.13.gem + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      3bfe481d98f859f37f3b90ced2b8856a843eef0f2e0263163cccc14430047bc3cd03d28597f48daa3d623b52d692c3b3e7c2dc26df5eb588dd82d28608fba639

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2-2.0.14.gem.html b/docs/file.oauth2-2.0.14.gem.html new file mode 100644 index 00000000..ee25b93f --- /dev/null +++ b/docs/file.oauth2-2.0.14.gem.html @@ -0,0 +1,71 @@ + + + + + + + File: oauth2-2.0.14.gem + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      5ce561a6b103a123d9b96e1e4725c07094bd6e58c135cc775ae9d5a055c031169ca6d6de379c2569daf1dd8ab2727079db3c80aa8568d6947e94a0c06b4c6d2b

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2-2.0.15.gem.html b/docs/file.oauth2-2.0.15.gem.html new file mode 100644 index 00000000..c4d115e1 --- /dev/null +++ b/docs/file.oauth2-2.0.15.gem.html @@ -0,0 +1,71 @@ + + + + + + + File: oauth2-2.0.15.gem + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      287a5d2cff87b4f37dde7b97f0fc31ee4c79edcc451b33694d1ba6f13d218cd04848780a857b94b93b656d6d81de4f4fcb4e8345f432cee17a6d96bd3f313df2

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2-2.0.16.gem.html b/docs/file.oauth2-2.0.16.gem.html new file mode 100644 index 00000000..7abd2400 --- /dev/null +++ b/docs/file.oauth2-2.0.16.gem.html @@ -0,0 +1,71 @@ + + + + + + + File: oauth2-2.0.16.gem + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      49788bf25c3afcc08171f92c3c8a21b4bcd322aae0834f69ae77c08963f54be6c9155588ca66f82022af897ddd0bf28b0c5ee254bc9fe533d1a37b1d52f409be

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2-2.0.17.gem.html b/docs/file.oauth2-2.0.17.gem.html new file mode 100644 index 00000000..668d7dbd --- /dev/null +++ b/docs/file.oauth2-2.0.17.gem.html @@ -0,0 +1,71 @@ + + + + + + + File: oauth2-2.0.17.gem + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      6385dfb2d4cb0309745de2d442d99c6148744abaca5599bd1e4f6038e99734d9cf90d1de83d1833e416e2682f0e3d6ae83e10a5a55d6e884b9cdc54e6070fb8b

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.oauth2.html b/docs/file.oauth2.html new file mode 100644 index 00000000..af26ebe4 --- /dev/null +++ b/docs/file.oauth2.html @@ -0,0 +1,79 @@ + + + + + + + File: oauth2 + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + OAUTH_DEBUG: bool

      + +

      DEFAULT_CONFIG: untyped + @config: untyped

      + +

      def self.config: () -> untyped + def self.configure: () { (untyped) -> void } -> void +end

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.response.html b/docs/file.response.html new file mode 100644 index 00000000..f9edaafd --- /dev/null +++ b/docs/file.response.html @@ -0,0 +1,87 @@ + + + + + + + File: response + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + class Response + DEFAULT_OPTIONS: Hash[Symbol, untyped]

      + +
      def self.register_parser: (Symbol key, (Array[String] | String) mime_types) { (String) -> untyped } -> void
      +
      +def initialize: (untyped response, parse: Symbol?, snaky: bool?, snaky_hash_klass: untyped?, options: Hash[Symbol, untyped]?) -> void
      +def headers: () -> Hash[untyped, untyped]
      +def status: () -> Integer
      +def body: () -> String
      +def parsed: () -> untyped
      +def content_type: () -> (String | nil)
      +def parser: () -> (untyped | nil)
      +
      +attr_reader response: untyped
      +attr_accessor options: Hash[Symbol, untyped]   end end
      +
      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.strategy.html b/docs/file.strategy.html new file mode 100644 index 00000000..e1f679f3 --- /dev/null +++ b/docs/file.strategy.html @@ -0,0 +1,103 @@ + + + + + + + File: strategy + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + module Strategy + class Base + def initialize: (OAuth2::Client) -> void + end

      + +
      class AuthCode < Base
      +  def authorize_params: (?Hash[untyped, untyped]) -> Hash[untyped, untyped]
      +  def authorize_url: (?Hash[untyped, untyped]) -> String
      +  def get_token: (String, ?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
      +end
      +
      +class Implicit < Base
      +  def authorize_params: (?Hash[untyped, untyped]) -> Hash[untyped, untyped]
      +  def authorize_url: (?Hash[untyped, untyped]) -> String
      +  def get_token: (*untyped) -> void
      +end
      +
      +class Password < Base
      +  def authorize_url: () -> void
      +  def get_token: (String, String, ?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
      +end
      +
      +class ClientCredentials < Base
      +  def authorize_url: () -> void
      +  def get_token: (?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
      +end
      +
      +class Assertion < Base
      +  def authorize_url: () -> void
      +  def get_token: (Hash[untyped, untyped], Hash[Symbol, untyped], ?Hash[Symbol, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
      +end   end end
      +
      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/file.version.html b/docs/file.version.html new file mode 100644 index 00000000..db7a1599 --- /dev/null +++ b/docs/file.version.html @@ -0,0 +1,75 @@ + + + + + + + File: version + + — Documentation by YARD 0.9.37 + + + + + + + + + + + + + + + + + + + +
      + + +

      module OAuth2 + module Version + VERSION: String + end +end

      +
      + + + +
      + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index b4641ae9..95578274 100644 --- a/docs/index.html +++ b/docs/index.html @@ -103,7 +103,7 @@

      🔐 OAuth 2.0 Authorization Framework

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

      -

      Version GitHub tag (latest SemVer) License: MIT Downloads Rank Open Source Helpers CodeCov Test Coverage Coveralls Test Coverage QLTY Test Coverage QLTY Maintainability CI Heads CI Runtime Dependencies @ HEAD CI Current CI Truffle Ruby CI JRuby Deps Locked Deps Unlocked CI Supported CI Legacy CI Unsupported CI Ancient CI Test Coverage CI Style CodeQL Apache SkyWalking Eyes License Compatibility Check

      +

      Version GitHub tag (latest SemVer) License: MIT Downloads Rank Open Source Helpers CodeCov Test Coverage Coveralls Test Coverage QLTY Test Coverage QLTY Maintainability CI Heads CI Runtime Dependencies @ HEAD CI Current [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] CI JRuby Deps Locked Deps Unlocked CI Supported CI Legacy CI Unsupported CI Ancient CI Test Coverage CI Style CodeQL Apache SkyWalking Eyes License Compatibility Check

      if ci_badges.map(&:color).detect { it != "green"} ☝️ let me know, as I may have missed the discord notification.

      @@ -274,7 +274,7 @@

      💡 Info you can shake a stick at

      Works with Truffle Ruby -Truffle Ruby 22.3 Compat Truffle Ruby 23.0 Compat
      Truffle Ruby 23.1 Compat Truffle Ruby 24.1 Compat +Truffle Ruby 22.3 Compat Truffle Ruby 23.0 Compat Truffle Ruby 23.1 Compat
      Truffle Ruby 24.1 Compat @@ -1183,8 +1183,8 @@

      4) Example POST

      Tips:

        -
      • Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET.
      • -
      • If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }.
      • +
      • Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET requests.
      • +
      • If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }.

      Refresh Tokens

      @@ -1344,9 +1344,9 @@

      Faraday conn end

      -

      Using flat query params (Faraday::FlatParamsEncoder)
      +
      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.

      +

      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”

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

      Please give the project a star ⭐ ♥ diff --git a/docs/top-level-namespace.html b/docs/top-level-namespace.html index a4e375cf..ba975a96 100644 --- a/docs/top-level-namespace.html +++ b/docs/top-level-namespace.html @@ -100,7 +100,7 @@

      Defined Under Namespace

      From 6037dad9282feba4869c5a89ba77dc41597cab10 Mon Sep 17 00:00:00 2001 From: "Peter H. Boling" Date: Fri, 7 Nov 2025 20:55:20 -0700 Subject: [PATCH 14/14] =?UTF-8?q?=F0=9F=91=B7=20Update=20dep=20resolution?= =?UTF-8?q?=20for=20appraisals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gemfiles/modular/x_std_libs/r2.4/libs.gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gemfiles/modular/x_std_libs/r2.4/libs.gemfile b/gemfiles/modular/x_std_libs/r2.4/libs.gemfile index c1bcbd8f..5a3c5b6c 100644 --- a/gemfiles/modular/x_std_libs/r2.4/libs.gemfile +++ b/gemfiles/modular/x_std_libs/r2.4/libs.gemfile @@ -1,3 +1,3 @@ -eval_gemfile "../../erb/r2.6/v2.2.gemfile" +eval_gemfile "../../erb/r2.4/v2.2.gemfile" eval_gemfile "../../mutex_m/r2.4/v0.1.gemfile" eval_gemfile "../../stringio/r2.4/v0.0.2.gemfile"