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
deleted file mode 100644
index db651885..00000000
--- a/.github/workflows/truffle.yml
+++ /dev/null
@@ -1,93 +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
- uses: actions/checkout@v5
-
- - name: Setup Ruby & RubyGems
- 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
- run: bundle
-
- - name: "[Attempt 1] Install Root Appraisal"
- id: bundleAttempt1
- 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'
- run: bundle
-
- - name: "[Attempt 1] Appraisal for ${{ matrix.ruby }}@${{ matrix.appraisal }}"
- id: bundleAppraisalAttempt1
- 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 bundleAttempt1 failed, try again here; Otherwise skip.
- if: steps.bundleAppraisalAttempt1.outcome == 'failure'
- run: bundle exec appraisal ${{ matrix.appraisal }} bundle
-
- - name: Tests for ${{ matrix.ruby }} via ${{ matrix.exec_cmd }}
- 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/.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/.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
deleted file mode 100644
index 4f2f1403..00000000
--- a/.yard_gfm_support.rb
+++ /dev/null
@@ -1,22 +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
-
-# 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"},
-)
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..5cacc191 100644
--- a/Appraisals
+++ b/Appraisals
@@ -47,66 +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"
eval_gemfile "modular/hashie_v1.gemfile"
@@ -217,3 +157,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/CHANGELOG.md b/CHANGELOG.md
index 044437fe..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
@@ -49,7 +56,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/CONTRIBUTING.md b/CONTRIBUTING.md
index f70f8c81..fbc87e94 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,31 +24,22 @@ 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
+- 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**
-```ruby
-group :development do
- gem "oauth2", require: false
-end
-```
-
-**Rakefile**
-```ruby
-# Rakefile
-require "oauth2"
+```shell
+bin/rake -T
```
## Environment Variables for Local Development
@@ -77,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
@@ -118,10 +111,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 +135,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 +158,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,9 +167,10 @@ 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`.
+4. Stay awake and monitor the release process for any errors, and answer any prompts.
#### Manual process
@@ -205,7 +197,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 +208,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 5f2a158a..8c4b1f4b 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)
@@ -33,18 +33,15 @@ 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)
+ 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)
- bigdecimal
- rexml
- date (3.4.1)
+ date (3.5.0)
debug (1.11.0)
irb (~> 1.10)
reline (>= 0.3.8)
@@ -81,29 +78,28 @@ GEM
dry-inflector (~> 1.0)
dry-logic (~> 1.4)
zeitwerk (~> 2.6)
- erb (5.0.2)
- faraday (2.13.4)
+ 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)
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.2)
+ irb (1.15.3)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
- json (2.13.2)
+ json (2.16.0)
jwt (3.1.2)
base64
- kettle-dev (1.1.31)
+ kettle-dev (1.1.51)
kettle-soup-cover (1.0.10)
simplecov (~> 0.22)
simplecov-cobertura (~> 3.0)
@@ -113,15 +109,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 +130,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 +170,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)
@@ -204,7 +203,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)
@@ -245,7 +244,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 +293,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,28 +304,27 @@ 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)
- vcr (6.3.1)
- base64
+ uri (1.1.1)
version_gem (1.1.9)
- webmock (3.25.1)
- addressable (>= 2.8.0)
- crack (>= 0.3.2)
- hashdiff (>= 0.4.0, < 2.0.0)
yard (0.9.37)
+ yard-fence (0.5.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)
- zlib (3.2.1)
+ zlib (3.2.2)
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)
@@ -338,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)
@@ -349,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)
@@ -359,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/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 190f2420..13ab7ba0 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,8 +842,9 @@ 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
@@ -1107,16 +949,17 @@ 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
@@ -1148,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"
@@ -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
@@ -1580,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
@@ -1610,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
@@ -1642,8 +1474,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..631e8b13 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.51 - 2025-11-07
# Ruby 2.3 (Safe Navigation) or higher required
#
# MIT License (see License.txt)
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/docs/OAuth2.html b/docs/OAuth2.html
index 3ca5c041..1ffcdd17 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..2d0ed1df 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..53736878 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..9ff6fc48 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..1401cd95 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..7c738657 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..75bb5568 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..dccdc25b 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
+
Note:
-
If #parser is a Proc, it will be called with no arguments, just
+
@@ -1619,9 +1619,9 @@
diff --git a/docs/OAuth2/Strategy.html b/docs/OAuth2/Strategy.html
index a75d5e8d..c629c668 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..6153d6fa 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..d5081e25 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..dd410d38 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..efa4fdb8 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..32f63999 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..91d2fe7c 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..35c06cb4 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..f023b32d 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..b22f44a2 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.
@@ -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,9 +99,14 @@ Removed
Fixed
+
+ -
+gh!690 - Add yard-fence to handle braces within code fences in markdown properly by @pboling
+
+
Security
-
+
2.0.17 - 2025-09-15
@@ -112,10 +121,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 +166,7 @@ Changed
gh!681 - Upgrade to kettle-dev v1.1.19
-
+
2.0.15 - 2025-09-08
@@ -202,7 +211,7 @@ Fixed
- point badge to the correct workflow for Ruby 2.3 (caboose.yml)
-
+
2.0.14 - 2025-08-31
@@ -246,7 +255,7 @@ Added
gh!664 - README: Add example for JHipster UAA (Spring Cloud) password grant, converted from Postman/Net::HTTP by @pboling
-
+
2.0.13 - 2025-08-30
@@ -291,7 +300,7 @@ Fixed
Security
-
+
2.0.12 - 2025-05-31
@@ -332,7 +341,7 @@ Fixed
- Documentation Typos by @pboling
-
+
2.0.11 - 2025-05-23
@@ -393,7 +402,7 @@ Fixed
- Incorrect documentation related to silencing warnings (@pboling)
-
+
2.0.10 - 2025-05-17
@@ -500,7 +509,7 @@ Fixed
gh!646 - Change require to require_relative (improve performance) (@Aboling0)
-
+
2.0.9 - 2022-09-16
@@ -521,7 +530,7 @@ Changed
- Complete migration to Gitlab, updating all links, and references in VCS-managed files (@pboling)
-
+
2.0.8 - 2022-09-01
@@ -544,7 +553,7 @@ Added
-
+
2.0.7 - 2022-08-22
@@ -572,7 +581,7 @@ Fixed
!625 - Fixes the printed version in the post install message (@hasghari)
-
+
2.0.6 - 2022-07-13
@@ -587,7 +596,7 @@ Fixed
!624 - Fixes a regression in v2.0.5, where an error would be raised in refresh_token flows due to (legitimate) lack of access_token (@pboling)
-
+
2.0.5 - 2022-07-07
@@ -617,7 +626,7 @@ Fixed
-
+
2.0.4 - 2022-07-01
@@ -632,7 +641,7 @@ Fixed
!618 - In some scenarios the snaky option default value was not applied (@pboling)
-
+
2.0.3 - 2022-06-28
@@ -658,7 +667,7 @@ Fixed
!615 - Fix support for requests with blocks, see Faraday::Connection#run_request (@pboling)
-
+
2.0.2 - 2022-06-24
@@ -677,7 +686,7 @@ Fixed
!607 - CHANGELOG correction, reference to OAuth2::ConnectionError (@zavan)
-
+
2.0.1 - 2022-06-22
@@ -692,7 +701,7 @@ Added
- Increased test coverage to 99% (@pboling)
-
+
2.0.0 - 2022-06-21
@@ -850,7 +859,7 @@ Removed
!590 - Dependency: Removed multi_json (@stanhu)
-
+
1.4.11 - 2022-09-16
@@ -860,7 +869,7 @@
- Complete migration to Gitlab, updating all links, and references in VCS-managed files (@pboling)
-
+
1.4.10 - 2022-07-01
@@ -869,7 +878,7 @@
- FIPS Compatibility !587 (@akostadinov)
-
+
1.4.9 - 2022-02-20
@@ -887,7 +896,7 @@
- Add Windows and MacOS to test matrix
-
+
1.4.8 - 2022-02-18
@@ -904,7 +913,7 @@
!543 - Support for more modern Open SSL libraries (@pboling)
-
+
1.4.7 - 2021-03-19
@@ -914,7 +923,7 @@
!541 - Backport fix to expires_at handling !533 to 1-4-stable branch. (@dobon)
-
+
1.4.6 - 2021-03-19
@@ -928,7 +937,7 @@
!538 - Remove reliance on globally included OAuth2 in tests, analogous to !539 on main branch (@anderscarling)
-
+
1.4.5 - 2021-03-18
@@ -944,7 +953,7 @@
!500 - Fix YARD documentation formatting (@olleolleolle)
-
+
1.4.4 - 2020-02-12
@@ -954,7 +963,7 @@
!408 - Fixed expires_at for formatted time (@Lomey)
-
+
1.4.3 - 2020-01-29
@@ -972,7 +981,7 @@
!433 - allow field names with square brackets and numbers in params (@asm256)
-
+
1.4.2 - 2019-10-01
-
+
1.4.1 - 2018-10-13
-
+
1.4.0 - 2017-06-09
@@ -1044,7 +1053,7 @@
Dependency: Upgrade Faraday to 0.12 (@sferik)
-
+
1.3.1 - 2017-03-03
@@ -1055,7 +1064,7 @@
Dependency: Upgrade Faraday to Faraday 0.11 (@mcfiredrill, @rhymes, @pschambacher)
-
+
1.3.0 - 2016-12-28
@@ -1071,7 +1080,7 @@
- Add support for Faraday 0.10 (@rhymes)
-
+
1.2.0 - 2016-07-01
@@ -1082,7 +1091,7 @@
- Use
raise rather than fail to throw exceptions (@sferik)
-
+
1.1.0 - 2016-01-30
@@ -1092,7 +1101,7 @@
- Add support for Rack 2, and bump various other dependencies (@sferik)
-
+
1.0.0 - 2014-07-09
@@ -1112,7 +1121,7 @@ Fixed
- Fix Base64.strict_encode64 incompatibility with Ruby 1.8.7.
-
+
0.5.0 - 2011-07-29
@@ -1135,7 +1144,7 @@ Changed
breaking web_server renamed to auth_code.
-
+
0.4.1 - 2011-04-20
-
+
0.4.0 - 2011-04-20
-
+
0.3.0 - 2011-04-08
-
+
0.2.0 - 2011-04-01
-
+
0.1.1 - 2011-01-12
-
+
0.1.0 - 2010-10-13
-
+
0.0.13 - 2010-08-17
-
+
0.0.12 - 2010-08-17
-
+
0.0.11 - 2010-08-17
-
+
0.0.10 - 2010-06-19
-
+
0.0.9 - 2010-06-18
-
+
0.0.8 - 2010-04-27
-
+
0.0.7 - 2010-04-27
-
+
0.0.6 - 2010-04-25
-
+
0.0.5 - 2010-04-23
-
+
0.0.4 - 2010-04-22
-
+
0.0.3 - 2010-04-22
-
+
0.0.2 - 2010-04-22
-
+
0.0.1 - 2010-04-22
diff --git a/docs/file.CITATION.html b/docs/file.CITATION.html
index 6667e4a7..ec687c30 100644
--- a/docs/file.CITATION.html
+++ b/docs/file.CITATION.html
@@ -57,34 +57,32 @@
-
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
-
+
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
diff --git a/docs/file.CODE_OF_CONDUCT.html b/docs/file.CODE_OF_CONDUCT.html
index 4e34bb2e..818acbd7 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
-
.
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+
.
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..08f3438c 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
- - Update version.rb to contian the correct version-to-be-released.
+ - Update version.rb to contain the correct version-to-be-released.
- Run
bundle exec kettle-changelog.
- Run
bundle exec kettle-release.
+ - 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..59b0b074 100644
--- a/docs/file.FUNDING.html
+++ b/docs/file.FUNDING.html
@@ -65,35 +65,30 @@

-

+

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

-
To say “thanks for maintaining such a great tool” ☝️ Join the Discord or 👇️ send money.
+
To say “thanks!” ☝️ Join the Discord or 👇️ send money.
-
💌
💌
💌 
+
💌
💌
💌 
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..6664d497 100644
--- a/docs/file.IRP.html
+++ b/docs/file.IRP.html
@@ -180,13 +180,11 @@
Legal & regulatory
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
-
diff --git a/docs/file.LICENSE.html b/docs/file.LICENSE.html
index db020c50..428dfecc 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..e73faa29 100644
--- a/docs/file.OIDC.html
+++ b/docs/file.OIDC.html
@@ -81,6 +81,7 @@ 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).
@@ -88,6 +89,7 @@ Raw OIDC with ruby-oauth/oauth2
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:
@@ -101,6 +103,7 @@
Raw OIDC with ruby-oauth/oauth2
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.).
@@ -115,12 +118,13 @@ Raw OIDC with ruby-oauth/oauth2
What you must add in your app for OIDC
+
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"),
-)
+ aud: ENV.fetch(“OIDC_CLIENT_ID”),
+)
-# Verify nonce
-raise "nonce mismatch" unless decoded["nonce"] == nonce
+Verify nonce
+raise “nonce mismatch” unless decoded[“nonce”] == nonce
-# Optionally: call UserInfo
-userinfo = token.get("/userinfo").parsed
-
+Optionally: call UserInfo
+userinfo = token.get(“/userinfo”).parsed
+```
Notes on discovery and registration
+
- - 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.
@@ -218,6 +225,7 @@ Raw OIDC with ruby-oauth/oauth2
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
@@ -234,6 +242,7 @@ Raw OIDC with ruby-oauth/oauth2
See also
+
- README sections on OAuth 2.1 notes and OIDC notes
- Strategy classes under lib/oauth2/strategy for flow helpers
@@ -241,15 +250,16 @@ Raw OIDC with ruby-oauth/oauth2
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..f103d44c 100644
--- a/docs/file.README.html
+++ b/docs/file.README.html
@@ -57,13 +57,53 @@
-

-
-
🔐 OAuth 2.0 Authorization Framework
+
+
+

+
+
🔐 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]
+
[![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf]

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

-
🌻 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 [](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 |
+
+
+ |
+
+
+
+
+ | Works with JRuby |
+
+
+ |
+
+
+ | Works with Truffle Ruby |
+
+
+ |
+
+
+ | Works with MRI Ruby 3 |
+
+
+ |
+
+
+ | Works with MRI Ruby 2 |
+
+
+ |
+
+
+ | Support & Community |
+
+
+ |
+
+
+ | Source |
+
+
+ |
+
+
+ | Documentation |
+
+
+ |
+
+
+ | Compliance |
+
+
+ |
+
+
+ | Style |
+
+
+ |
+
+
+ | Maintainer 🎖️ |
+
+
+ |
+
+
+
+... 💖 |
+
+ 🧊 🐙 🛖 🧪
+ |
+
+
+
+
+Compatibility
+
+Compatible with MRI Ruby 2.2.0+, and concordant releases of JRuby, and TruffleRuby.
+
+
+
+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!)
+
+
-## 🚀 Release Documentation
+Enterprise Support
+
-### 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
+ 
-| 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 |
+
-## ✨ 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 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 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 (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 {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,
+
+ # 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,
+
+# 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 requests.
+ - 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
+
+
+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:
+
-```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:
+
-```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`.
+

-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
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
+
-See [CONTRIBUTING.md][🤝contributing].
+
-### Code 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
.
-[![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
+🌈 Contributors
-### 🪇 Code of Conduct
+
-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
.
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
.
+See LICENSE.txt for the official Copyright Notice.
-### © Copyright
+© 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.
+
+
+
+To say “thanks!” ☝️ Join the Discord or 👇️ send money.
+
+
💌
💌
💌 
+
+Please give the project a star ⭐ ♥.
+
+Thanks for RTFM. ☺️
@@ -1731,13 +1674,12 @@ Quick Examples
-
diff --git a/docs/file.REEK.html b/docs/file.REEK.html
index bc4f2767..fa6fc2fc 100644
--- a/docs/file.REEK.html
+++ b/docs/file.REEK.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.RUBOCOP.html b/docs/file.RUBOCOP.html
index f8f449c1..99b4a4b1 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
- - Before submitting a PR, run
bundle exec rake rubocop_gradual:autocorrect
+ - 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.
- 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..b1a7d6ea 100644
--- a/docs/file.SECURITY.html
+++ b/docs/file.SECURITY.html
@@ -78,24 +78,24 @@ Supported Versions
-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..d9568287 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
-
+
-
Threat: Injection attacks via untrusted input
@@ -170,7 +170,7 @@
-
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.access_token.html b/docs/file.access_token.html
index e6fc7b16..1300c2b1 100644
--- a/docs/file.access_token.html
+++ b/docs/file.access_token.html
@@ -57,9 +57,9 @@
- module OAuth2
- class AccessToken
- def self.from_hash: (OAuth2::Client, Hash[untyped, untyped]) -> OAuth2::AccessToken
+
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
@@ -84,9 +84,9 @@
diff --git a/docs/file.authenticator.html b/docs/file.authenticator.html
index 24436d9e..630200ca 100644
--- a/docs/file.authenticator.html
+++ b/docs/file.authenticator.html
@@ -57,8 +57,8 @@
- module OAuth2
- class Authenticator
+
module OAuth2
+ class Authenticator
include OAuth2::FilteredAttributes
attr_reader mode: (Symbol | String)
@@ -81,9 +81,9 @@
diff --git a/docs/file.client.html b/docs/file.client.html
index c9691758..a21933aa 100644
--- a/docs/file.client.html
+++ b/docs/file.client.html
@@ -57,9 +57,9 @@
- module OAuth2
- class Client
- RESERVED_REQ_KEYS: Array[String]
+
module OAuth2
+ class Client
+ RESERVED_REQ_KEYS: Array[String]
RESERVED_PARAM_KEYS: Array[String]
include OAuth2::FilteredAttributes
@@ -111,9 +111,9 @@
diff --git a/docs/file.error.html b/docs/file.error.html
index 0934c7fd..d6e43057 100644
--- a/docs/file.error.html
+++ b/docs/file.error.html
@@ -57,20 +57,20 @@
- module OAuth2
- class Error < StandardError
- def initialize: (OAuth2::Response) -> void
- def code: () -> (String | Integer | nil)
- def description: () -> (String | nil)
- def response: () -> OAuth2::Response
- end
+
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
diff --git a/docs/file.filtered_attributes.html b/docs/file.filtered_attributes.html
index 3b36819a..95f557bb 100644
--- a/docs/file.filtered_attributes.html
+++ b/docs/file.filtered_attributes.html
@@ -57,18 +57,18 @@
- module OAuth2
- module FilteredAttributes
- def self.included: (untyped) -> untyped
- def filtered_attributes: (*String) -> void
- end
+
module OAuth2
+ module FilteredAttributes
+ def self.included: (untyped) -> untyped
+ def filtered_attributes: (*String) -> void
+ end
end
diff --git a/docs/file.oauth2-2.0.10.gem.html b/docs/file.oauth2-2.0.10.gem.html
index b00e56e9..aaedb848 100644
--- a/docs/file.oauth2-2.0.10.gem.html
+++ b/docs/file.oauth2-2.0.10.gem.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.oauth2-2.0.11.gem.html b/docs/file.oauth2-2.0.11.gem.html
index 11369457..299ada70 100644
--- a/docs/file.oauth2-2.0.11.gem.html
+++ b/docs/file.oauth2-2.0.11.gem.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.oauth2-2.0.12.gem.html b/docs/file.oauth2-2.0.12.gem.html
index c691479c..f5efd44b 100644
--- a/docs/file.oauth2-2.0.12.gem.html
+++ b/docs/file.oauth2-2.0.12.gem.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.oauth2-2.0.13.gem.html b/docs/file.oauth2-2.0.13.gem.html
index 6119b737..72e8abb0 100644
--- a/docs/file.oauth2-2.0.13.gem.html
+++ b/docs/file.oauth2-2.0.13.gem.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.oauth2-2.0.14.gem.html b/docs/file.oauth2-2.0.14.gem.html
index 39830f8f..ee25b93f 100644
--- a/docs/file.oauth2-2.0.14.gem.html
+++ b/docs/file.oauth2-2.0.14.gem.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.oauth2-2.0.15.gem.html b/docs/file.oauth2-2.0.15.gem.html
index af525d3c..c4d115e1 100644
--- a/docs/file.oauth2-2.0.15.gem.html
+++ b/docs/file.oauth2-2.0.15.gem.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.oauth2-2.0.16.gem.html b/docs/file.oauth2-2.0.16.gem.html
index 70c1903d..7abd2400 100644
--- a/docs/file.oauth2-2.0.16.gem.html
+++ b/docs/file.oauth2-2.0.16.gem.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.oauth2-2.0.17.gem.html b/docs/file.oauth2-2.0.17.gem.html
index 555bded1..668d7dbd 100644
--- a/docs/file.oauth2-2.0.17.gem.html
+++ b/docs/file.oauth2-2.0.17.gem.html
@@ -61,9 +61,9 @@
diff --git a/docs/file.oauth2.html b/docs/file.oauth2.html
index ca8fc1ff..af26ebe4 100644
--- a/docs/file.oauth2.html
+++ b/docs/file.oauth2.html
@@ -57,21 +57,21 @@
- module OAuth2
+
module OAuth2
OAUTH_DEBUG: bool
-
DEFAULT_CONFIG: untyped
+
DEFAULT_CONFIG: untyped
@config: untyped
-
def self.config: () -> untyped
- def self.configure: () { (untyped) -> void } -> void
+
def self.config: () -> untyped
+ def self.configure: () { (untyped) -> void } -> void
end
diff --git a/docs/file.response.html b/docs/file.response.html
index 2966fd2c..f9edaafd 100644
--- a/docs/file.response.html
+++ b/docs/file.response.html
@@ -57,8 +57,8 @@
- module OAuth2
- class Response
+
module OAuth2
+ class Response
DEFAULT_OPTIONS: Hash[Symbol, untyped]
def self.register_parser: (Symbol key, (Array[String] | String) mime_types) { (String) -> untyped } -> void
@@ -77,9 +77,9 @@
diff --git a/docs/file.strategy.html b/docs/file.strategy.html
index 94941ce1..e1f679f3 100644
--- a/docs/file.strategy.html
+++ b/docs/file.strategy.html
@@ -57,10 +57,10 @@
- module OAuth2
- module Strategy
- class Base
- def initialize: (OAuth2::Client) -> void
+
module OAuth2
+ module Strategy
+ class Base
+ def initialize: (OAuth2::Client) -> void
end
class AuthCode < Base
@@ -93,9 +93,9 @@
diff --git a/docs/file.version.html b/docs/file.version.html
index 3a21b7b8..db7a1599 100644
--- a/docs/file.version.html
+++ b/docs/file.version.html
@@ -57,17 +57,17 @@
- module OAuth2
- module Version
- VERSION: String
- end
+
module OAuth2
+ module Version
+ VERSION: String
+ end
end
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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/index.html b/docs/index.html
index aa7dfaf6..95578274 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -57,13 +57,53 @@
-

-
-
🔐 OAuth 2.0 Authorization Framework
+
+
+

+
+
🔐 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]
+
[![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf]

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

-
🌻 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 [](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 |
+
+
+ |
+
+
+
+
+ | Works with JRuby |
+
+
+ |
+
+
+ | Works with Truffle Ruby |
+
+
+ |
+
+
+ | Works with MRI Ruby 3 |
+
+
+ |
+
+
+ | Works with MRI Ruby 2 |
+
+
+ |
+
+
+ | Support & Community |
+
+
+ |
+
+
+ | Source |
+
+
+ |
+
+
+ | Documentation |
+
+
+ |
+
+
+ | Compliance |
+
+
+ |
+
+
+ | Style |
+
+
+ |
+
+
+ | Maintainer 🎖️ |
+
+
+ |
+
+
+
+... 💖 |
+
+ 🧊 🐙 🛖 🧪
+ |
+
+
+
+
+Compatibility
+
+Compatible with MRI Ruby 2.2.0+, and concordant releases of JRuby, and TruffleRuby.
+
+
+
+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!)
+
+
-## 🚀 Release Documentation
+Enterprise Support
+
-### 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
+ 
-| 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 |
+
-## ✨ 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 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 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 (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 {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,
+
+ # 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,
+
+# 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 requests.
+ - 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
+
+
+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:
+
-```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:
+
-```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`.
+

-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
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
+
-See [CONTRIBUTING.md][🤝contributing].
+
-### Code 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
.
-[![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
+🌈 Contributors
-### 🪇 Code of Conduct
+
-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
.
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
.
+See LICENSE.txt for the official Copyright Notice.
-### © Copyright
+© 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.
+
+
+
+To say “thanks!” ☝️ Join the Discord or 👇️ send money.
+
+
💌
💌
💌 
+
+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..ba975a96 100644
--- a/docs/top-level-namespace.html
+++ b/docs/top-level-namespace.html
@@ -100,9 +100,9 @@
Defined Under Namespace
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/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
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