oauth2 2.0.10 → 2.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,60 +1,180 @@
1
- <p align="center">
2
- <a href="http://oauth.net/2/" target="_blank" rel="noopener">
3
- <img src="https://github.com/oauth-xx/oauth2/raw/main/docs/images/logo/oauth2-logo-124px.png?raw=true" alt="OAuth 2.0 Logo by Chris Messina, CC BY-SA 3.0">
4
- </a>
5
- <a href="https://www.ruby-lang.org/" target="_blank" rel="noopener">
6
- <img width="124px" src="https://github.com/oauth-xx/oauth2/raw/main/docs/images/logo/ruby-logo-198px.svg?raw=true" alt="Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5">
7
- </a>
8
- </p>
9
-
10
- ## 🔐 OAuth2
11
-
12
- [![Version][👽versioni]][👽version]
13
- [![License: MIT][📄license-img]][📄license-ref]
14
- [![Downloads Rank][👽dl-ranki]][👽dl-rank]
15
- [![Open Source Helpers][👽oss-helpi]][👽oss-help]
16
- [![Depfu][🔑depfui♻️]][🔑depfu]
17
- [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls]
18
- [![QLTY Test Coverage][🔑cc-covi♻️]][🔑cc-cov]
19
- [![Maintainability][🔑cc-mnti♻️]][🔑cc-mnt]
20
- [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf]
21
- [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf]
22
- [![CI Current][🚎11-c-wfi]][🚎11-c-wf]
23
- [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf]
24
- [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf]
25
- [![CI Supported][🚎6-s-wfi]][🚎6-s-wf]
26
- [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf]
27
- [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf]
28
- [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf]
29
- [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf]
30
- [![CI Style][🚎5-st-wfi]][🚎5-st-wf]
1
+ [![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]
2
+
3
+ [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg
4
+ [🖼️galtzo-discord]: https://discord.gg/3qme4XHNKN
5
+ [🖼️ruby-lang-i]: https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg
6
+ [🖼️ruby-lang]: https://www.ruby-lang.org/
7
+ [🖼️oauth2-i]: https://logos.galtzo.com/assets/images/oauth/oauth2/avatar-192px.svg
8
+ [🖼️oauth2]: https://github.com/ruby-oauth/oauth2
9
+
10
+ # 🔐 OAuth 2.0 Authorization Framework
11
+
12
+ ⭐️ including OAuth 2.1 draft spec & OpenID Connect (OIDC)
13
+
14
+ [![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]
15
+
16
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
31
17
 
32
18
  ---
33
19
 
34
- [![Liberapay Patrons][⛳liberapay-img]][⛳liberapay]
35
- [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor]
36
- [![Buy me a coffee][🖇buyme-small-img]][🖇buyme]
37
- [![Donate on Polar][🖇polar-img]][🖇polar]
38
- [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi]
39
- [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
20
+ `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.
21
+
22
+ [![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]
23
+
24
+ ## 🌻 Synopsis
40
25
 
41
26
  OAuth 2.0 is the industry-standard protocol for authorization.
42
27
  OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications,
43
28
  desktop applications, mobile phones, and living room devices.
44
29
  This is a RubyGem for implementing OAuth 2.0 clients (not servers) in Ruby applications.
45
30
 
46
- | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
47
- |-----------------------------------------------|----------------|--------|-----|------|----------------|-------------|
48
- | 🧪 [oauth-xx/oauth2 on GitLab][📜src-gl] | The Truth | 💚 | 💚 | 💚 | 🏀 Tiny Matrix | ➖ |
49
- | 🐙 [oauth-xx/oauth2 on GitHub][📜src-gh] | A Dirty Mirror | 💚 | 💚 | ➖ | 💯 Full Matrix | ➖ |
50
- | 🤼 [OAuth Ruby Google Group][⛳gg-discussions] | "Active" | ➖ | ➖ | ➖ | ➖ | 💚 |
31
+ ### Quick Examples
32
+
33
+ <details>
34
+ <summary>Convert the following `curl` command into a token request using this gem...</summary>
35
+
36
+ ```shell
37
+ curl --request POST \
38
+ --url 'https://login.microsoftonline.com/REDMOND_REDACTED/oauth2/token' \
39
+ --header 'content-type: application/x-www-form-urlencoded' \
40
+ --data grant_type=client_credentials \
41
+ --data client_id=REDMOND_CLIENT_ID \
42
+ --data client_secret=REDMOND_CLIENT_SECRET \
43
+ --data resource=REDMOND_RESOURCE_UUID
44
+ ```
45
+
46
+ NOTE: In the ruby version below, certain params are passed to the `get_token` call, instead of the client creation.
47
+
48
+ ```ruby
49
+ OAuth2::Client.new(
50
+ "REDMOND_CLIENT_ID", # client_id
51
+ "REDMOND_CLIENT_SECRET", # client_secret
52
+ auth_scheme: :request_body, # Other modes are supported: :basic_auth, :tls_client_auth, :private_key_jwt
53
+ token_url: "oauth2/token", # relative path, except with leading `/`, then absolute path
54
+ site: "https://login.microsoftonline.com/REDMOND_REDACTED",
55
+ ). # The base path for token_url when it is relative
56
+ client_credentials. # There are many other types to choose from!
57
+ get_token(resource: "REDMOND_RESOURCE_UUID")
58
+ ```
59
+
60
+ NOTE: `header` - The content type specified in the `curl` is already the default!
61
+
62
+ </details>
63
+
64
+ <details>
65
+ <summary>Complete E2E single file script against [navikt/mock-oauth2-server](https://github.com/navikt/mock-oauth2-server)</summary>
66
+
67
+ - E2E example using the mock test server added in v2.0.11
68
+
69
+ ```console
70
+ docker compose -f docker-compose-ssl.yml up -d --wait
71
+ ruby examples/e2e.rb
72
+ # If your machine is slow or Docker pulls are cold, increase the wait:
73
+ E2E_WAIT_TIMEOUT=120 ruby examples/e2e.rb
74
+ # The mock server serves HTTP on 8080; the example points to http://localhost:8080 by default.
75
+ ```
76
+
77
+ The output should be something like this:
78
+
79
+ ```console
80
+ ➜ ruby examples/e2e.rb
81
+ Access token (truncated): eyJraWQiOiJkZWZhdWx0...
82
+ userinfo status: 200
83
+ 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"}
84
+ E2E complete
85
+ ```
86
+
87
+ Make sure to shut down the mock server when you are done:
88
+
89
+ ```console
90
+ docker compose -f docker-compose-ssl.yml down
91
+ ```
92
+
93
+ Troubleshooting: validate connectivity to the mock server
94
+
95
+ - Check container status and port mapping:
96
+ - docker compose -f docker-compose-ssl.yml ps
97
+ - From the host, try the discovery URL directly (this is what the example uses by default):
98
+ - curl -v http://localhost:8080/default/.well-known/openid-configuration
99
+ - If that fails immediately, also try: curl -v --connect-timeout 2 http://127.0.0.1:8080/default/.well-known/openid-configuration
100
+ - From inside the container (to distinguish container vs host networking):
101
+ - docker exec -it oauth2-mock-oauth2-server-1 curl -v http://127.0.0.1:8080/default/.well-known/openid-configuration
102
+ - Simple TCP probe from the host:
103
+ - nc -vz localhost 8080 # or: ruby -rsocket -e 'TCPSocket.new("localhost",8080).close; puts "tcp ok"'
104
+ - Inspect which host port 8080 is bound to (should be 8080):
105
+ - docker inspect -f '{{ (index (index .NetworkSettings.Ports "8080/tcp") 0).HostPort }}' oauth2-mock-oauth2-server-1
106
+ - Look at server logs for readiness/errors:
107
+ - docker logs -n 200 oauth2-mock-oauth2-server-1
108
+ - On Linux, ensure nothing else is bound to 8080 and that firewall/SELinux aren’t blocking:
109
+ - ss -ltnp | grep :8080
110
+
111
+ Notes
112
+ - Discovery URL pattern is: http://localhost:8080/<realm>/.well-known/openid-configuration, where <realm> defaults to "default".
113
+ - You can change these with env vars when running the example:
114
+ - E2E_ISSUER_BASE (default: http://localhost:8080)
115
+ - E2E_REALM (default: default)
116
+
117
+ </details>
118
+
119
+ If it seems like you are in the wrong place, you might try one of these:
120
+
121
+ * [OAuth 2.0 Spec][oauth2-spec]
122
+ * [doorkeeper gem][doorkeeper-gem] for OAuth 2.0 server/provider implementation.
123
+ * [oauth sibling gem][sibling-gem] for OAuth 1.0a implementations in Ruby.
51
124
 
52
- ### Upgrading Runtime Gem Dependencies
125
+ [oauth2-spec]: https://oauth.net/2/
126
+ [sibling-gem]: https://gitlab.com/ruby-oauth/oauth
127
+ [doorkeeper-gem]: https://github.com/doorkeeper-gem/doorkeeper
128
+
129
+ ## 💡 Info you can shake a stick at
130
+
131
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
132
+ |-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
133
+ | Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i] <br/> [![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] |
134
+ | Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i] <br/> [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
135
+ | 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] |
136
+ | Works with MRI Ruby 2 | ![Ruby 2.2 Compat][💎ruby-2.2i] <br/> [![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] |
137
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Discussion][⛳gg-discussions-img]][⛳gg-discussions] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
138
+ | 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] |
139
+ | 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] [![Wiki][📜wiki-img]][📜wiki] |
140
+ | 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] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
141
+ | 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] |
142
+ | 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] |
143
+ | `...` 💖 | [![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] |
144
+
145
+ ### Compatibility
146
+
147
+ * Operating Systems: Linux, MacOS, Windows
148
+ * MRI Ruby @ v2.3, v2.4, v2.5, v2.6, v2.7, v3.0, v3.1, v3.2, v3.3, v3.4, HEAD
149
+ * 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.
150
+ * JRuby @ v9.4, v10.0, HEAD
151
+ * 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.
152
+ * TruffleRuby @ v23.1, v24.1, HEAD
153
+ * 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.
154
+ * gem `faraday` @ v0, v1, v2, HEAD ⏩️ [lostisland/faraday](https://github.com/lostisland/faraday)
155
+ * gem `jwt` @ v1, v2, v3, HEAD ⏩️ [jwt/ruby-jwt](https://github.com/jwt/ruby-jwt)
156
+ * gem `logger` @ v1.2, v1.5, v1.7, HEAD ⏩️ [ruby/logger](https://github.com/ruby/logger)
157
+ * gem `multi_xml` @ v0.5, v0.6, v0.7, HEAD ⏩️ [sferik/multi_xml](https://github.com/sferik/multi_xml)
158
+ * gem `rack` @ v1.2, v1.6, v2, v3, HEAD ⏩️ [rack/rack](https://github.com/rack/rack)
159
+ * gem `snaky_hash` @ v2, HEAD ⏩️ [ruby-oauth/snaky_hash](https://gitlab.com/ruby-oauth/snaky_hash)
160
+ * gem `version_gem` @ v1, HEAD ⏩️ [ruby-oauth/version_gem](https://gitlab.com/ruby-oauth/version_gem)
53
161
 
54
- This project sits underneath a large portion of the authentication systems on the internet.
162
+ The last two were extracted from this gem. They are part of the `ruby-oauth` org,
163
+ and are developed in tight collaboration with this gem.
164
+
165
+ Also, where reasonable, tested against the runtime dependencies of those dependencies:
166
+
167
+ * gem `hashie` @ v0, v1, v2, v3, v4, v5, HEAD ⏩️ [hashie/hashie](https://github.com/hashie/hashie)
168
+
169
+ [GHA-continue-on-error-ui]: https://github.com/actions/runner/issues/2347#issuecomment-2653479732
170
+ [GHA-allow-failure]: https://github.com/orgs/community/discussions/15452
171
+
172
+ #### Upgrading Runtime Gem Dependencies
173
+
174
+ This project sits underneath a large portion of the authorization systems on the internet.
55
175
  According to GitHub's project tracking, which I believe only reports on public projects,
56
- [100,000+ projects](https://github.com/oauth-xx/oauth2/network/dependents), and
57
- [500+ packages](https://github.com/oauth-xx/oauth2/network/dependents?dependent_type=PACKAGE) depend on this project.
176
+ [100,000+ projects](https://github.com/ruby-oauth/oauth2/network/dependents), and
177
+ [500+ packages](https://github.com/ruby-oauth/oauth2/network/dependents?dependent_type=PACKAGE) depend on this project.
58
178
 
59
179
  That means it is painful for the Ruby community when this gem forces updates to its runtime dependencies.
60
180
 
@@ -63,42 +183,24 @@ leading versions per each minor version of Ruby of all the runtime dependencies
63
183
 
64
184
  What does that mean specifically for the runtime dependencies?
65
185
 
66
- We have 100% test coverage of lines and branches, and this test suite runs across a large matrix
67
- covering the latest patch for each of the following minor versions:
186
+ We have 100% test coverage of lines and branches, and this test suite runs across a very large matrix.
187
+ It wouldn't be possible without appraisal2.
68
188
 
69
- * MRI Ruby @ v2.3, v2.4, v2.5, v2.6, v2.7, v3.0, v3.1, v3.2, v3.3, v3.4, HEAD
70
- * NOTE: This gem will still install on ruby v2.2, but vanilla GitHub Actions no longer supports testing against it, so YMMV.
71
- * JRuby @ v9.2, v9.3, v9.4, v10.0, HEAD
72
- * TruffleRuby @ v23.1, v23.2, HEAD
73
- * gem `faraday` @ v0, v1, v2, HEAD
74
- * gem `jwt` @ v1, v2, v3, HEAD
75
- * gem `logger` @ v1.2, v1.5, v1.7, HEAD
76
- * gem `multi_xml` @ v0.5, v0.6, v0.7, HEAD
77
- * gem `rack` @ v1.2, v1.6, v2, v3, HEAD
189
+ | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
190
+ |------------------------------------------------|--------------------------------------------------------|
191
+ | 👟 Check it out! | [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
192
+
193
+ #### You should upgrade this gem with confidence\*.
78
194
 
79
195
  - This gem follows a _strict & correct_ (according to the maintainer of SemVer; [more info][sv-pub-api]) interpretation of SemVer.
80
- - Dropping support for **any** of the runtime dependency versions above will be a major version bump.
81
- - If you aren't on one of the minor versions above, make getting there a priority.
82
- - You should upgrade this gem with confidence\*.
196
+ - Dropping support for **any** of the runtime dependency versions above will be a major version bump.
197
+ - If you aren't on one of the minor versions above, make getting there a priority.
83
198
  - You should upgrade the dependencies of this gem with confidence\*.
84
199
  - Please do upgrade, and then, when it goes smooth as butter [please sponsor me][🖇sponsor]. Thanks!
85
200
 
86
- If you are thinking, "that list is missing two runtime dependencies", you are correct!
87
- Both of them were extracted from this gem. They are part of the `oauth-xx` org,
88
- and are developed in tight collaboration with this gem, so not much more needs to be said about them.
89
-
90
- * gem `snaky_hash` - https://gitlab.com/oauth-xx/snaky_hash
91
- * gem `version_gem` - https://gitlab.com/oauth-xx/version_gem
92
-
93
- [sv-pub-api]: #-is-platform-support-part-of-the-public-api
201
+ [sv-pub-api]: #-versioning
94
202
 
95
- \* MIT license; I am unable to make guarantees.
96
-
97
- | 🚚 Test matrix brought to you by | 🔎 appraisal++ |
98
- |----------------------------------|-------------------------------------------------------------------------|
99
- | Adds back support for old Rubies | ✨ [appraisal PR #250](https://github.com/thoughtbot/appraisal/pull/250) |
100
- | Adds support for `eval_gemfile` | ✨ [appraisal PR #248](https://github.com/thoughtbot/appraisal/pull/248) |
101
- | Please review | my PRs! |
203
+ \* MIT license; The only guarantees I make are for [enterprise support](#enterprise-support).
102
204
 
103
205
  <details>
104
206
  <summary>Standard Library Dependencies</summary>
@@ -111,77 +213,67 @@ The various versions of each are tested via the Ruby test matrix, along with wha
111
213
  * time
112
214
  * logger (removed from stdlib in Ruby 3.5 so added as runtime dependency in v2.0.10)
113
215
 
114
- If you use a gem version it should work fine!
216
+ If you use a gem version of a core Ruby library it should work fine!
115
217
 
116
218
  </details>
117
219
 
118
- ### Quick Usage Example for AI and Copy / Pasting
220
+ ### Federated DVCS
119
221
 
120
- Convert the following `curl` command into a token request using this gem...
222
+ <details>
223
+ <summary>Find this repo on other forges</summary>
121
224
 
122
- ```shell
123
- curl --request POST \
124
- --url 'https://login.microsoftonline.com/REDMOND_REDACTED/oauth2/token' \
125
- --header 'content-type: application/x-www-form-urlencoded' \
126
- --data grant_type=client_credentials \
127
- --data client_id=REDMOND_CLIENT_ID \
128
- --data client_secret=REDMOND_CLIENT_SECRET \
129
- --data resource=REDMOND_RESOURCE_UUID
130
- ```
225
+ | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
226
+ |-----------------------------------------------|-----------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------|
227
+ | 🧪 [ruby-oauth/oauth2 on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜wiki] | 🏀 Tiny Matrix | ➖ |
228
+ | 🧊 [ruby-oauth/oauth2 on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
229
+ | 🐙 [ruby-oauth/oauth2 on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | ➖ | 💯 Full Matrix | [💚][gh-discussions] |
230
+ | 🤼 [OAuth Ruby Google Group][⛳gg-discussions] | "Active" | ➖ | ➖ | ➖ | ➖ | [💚][⛳gg-discussions] |
231
+ | 🎮️ [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] |
131
232
 
132
- NOTE: In the ruby version below, certain params are passed to the `get_token` call, instead of the client creation.
233
+ </details>
133
234
 
134
- ```ruby
135
- OAuth2::Client.new(
136
- "REDMOND_CLIENT_ID", # client_id
137
- "REDMOND_CLIENT_SECRET", # client_secret
138
- auth_scheme: :request_body, # Other modes are supported: :basic_auth, :tls_client_auth, :private_key_jwt
139
- token_url: "oauth2/token", # relative path, except with leading `/`, then absolute path
140
- site: "https://login.microsoftonline.com/REDMOND_REDACTED",
141
- ). # The base path for token_url when it is relative
142
- client_credentials. # There are many other types to choose from!
143
- get_token(resource: "REDMOND_RESOURCE_UUID")
144
- ```
235
+ [gh-discussions]: https://github.com/ruby-oauth/oauth2/discussions
145
236
 
146
- NOTE: `header` - The content type specified in the `curl` is already the default!
237
+ ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/oauth2)](https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=readme)
147
238
 
148
- If any of the above makes you uncomfortable, you may be in the wrong place.
149
- One of these might be what you are looking for:
239
+ Available as part of the Tidelift Subscription.
150
240
 
151
- * [OAuth 2.0 Spec][oauth2-spec]
152
- * [doorkeeper gem][doorkeeper-gem] for OAuth 2.0 server/provider implementation.
153
- * [oauth sibling gem][sibling-gem] for OAuth 1.0 implementations in Ruby.
241
+ <details>
242
+ <summary>Need enterprise-level guarantees?</summary>
154
243
 
155
- [oauth2-spec]: https://oauth.net/2/
156
- [sibling-gem]: https://gitlab.com/oauth-xx/oauth
157
- [doorkeeper-gem]: https://github.com/doorkeeper-gem/doorkeeper
244
+ 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.
158
245
 
159
- ## 💡 Info you can shake a stick at
246
+ [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
247
+
248
+ - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
249
+ - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
250
+ - 💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
251
+
252
+ Alternatively:
160
253
 
161
- | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
162
- |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
163
- | Works with JRuby | [![JRuby 9.2 Compat][💎jruby-9.2i]][🚎10-j-wf] [![JRuby 9.3 Compat][💎jruby-9.3i]][🚎10-j-wf] [![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] |
164
- | Works with Truffle Ruby | [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] [![Truffle Ruby HEAD Compat][💎truby-headi]][🚎3-hd-wf] |
165
- | 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] |
166
- | Works with MRI Ruby 2 | [![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] |
167
- | 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] |
168
- | Documentation | [![Discussion][⛳gg-discussions-img]][⛳gg-discussions] [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![HEAD on RubyDoc.info][📜docs-head-rd-img]][🚎yard-head] [![BDFL Blog][🚂bdfl-blog-img]][🚂bdfl-blog] [![Wiki][📜wiki-img]][📜wiki] |
169
- | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Enforced Code Style][💎rlts-img]][💎rlts] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![FOSSA][🏘fossa-img]][🏘fossa] |
170
- | Expert 1:1 Support | [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] `or` [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
171
- | Enterprise Support | [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]<br/>💡Subscribe for support guarantees covering _all_ FLOSS dependencies!<br/>💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]!<br/>💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers! |
172
- | Comrade BDFL 🎖️ | [![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 BDFL][🚂bdfl-contact-img]][🚂bdfl-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
173
- | `...` 💖 | [![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] |
254
+ - [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
255
+ - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
256
+ - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
257
+
258
+ </details>
174
259
 
175
260
  ## 🚀 Release Documentation
176
261
 
177
262
  ### Version 2.0.x
178
263
 
179
264
  <details>
180
- <summary>2.0.x CHANGELOGs and READMEs</summary>
265
+ <summary>2.0.x CHANGELOG and README</summary>
181
266
 
182
267
  | Version | Release Date | CHANGELOG | README |
183
268
  |---------|--------------|---------------------------------------|---------------------------------|
184
- | 2.0.10 | 2025-05-16 | [v2.0.10 CHANGELOG][2.0.10-changelog] | [v2.0.10 README][2.0.10-readme] |
269
+ | 2.0.17 | 2025-09-15 | [v2.0.17 CHANGELOG][2.0.17-changelog] | [v2.0.17 README][2.0.17-readme] |
270
+ | 2.0.16 | 2025-09-14 | [v2.0.16 CHANGELOG][2.0.16-changelog] | [v2.0.16 README][2.0.16-readme] |
271
+ | 2.0.15 | 2025-09-08 | [v2.0.15 CHANGELOG][2.0.15-changelog] | [v2.0.15 README][2.0.15-readme] |
272
+ | 2.0.14 | 2025-08-31 | [v2.0.14 CHANGELOG][2.0.14-changelog] | [v2.0.14 README][2.0.14-readme] |
273
+ | 2.0.13 | 2025-08-30 | [v2.0.13 CHANGELOG][2.0.13-changelog] | [v2.0.13 README][2.0.13-readme] |
274
+ | 2.0.12 | 2025-05-31 | [v2.0.12 CHANGELOG][2.0.12-changelog] | [v2.0.12 README][2.0.12-readme] |
275
+ | 2.0.11 | 2025-05-23 | [v2.0.11 CHANGELOG][2.0.11-changelog] | [v2.0.11 README][2.0.11-readme] |
276
+ | 2.0.10 | 2025-05-17 | [v2.0.10 CHANGELOG][2.0.10-changelog] | [v2.0.10 README][2.0.10-readme] |
185
277
  | 2.0.9 | 2022-09-16 | [v2.0.9 CHANGELOG][2.0.9-changelog] | [v2.0.9 README][2.0.9-readme] |
186
278
  | 2.0.8 | 2022-09-01 | [v2.0.8 CHANGELOG][2.0.8-changelog] | [v2.0.8 README][2.0.8-readme] |
187
279
  | 2.0.7 | 2022-08-22 | [v2.0.7 CHANGELOG][2.0.7-changelog] | [v2.0.7 README][2.0.7-readme] |
@@ -192,31 +284,46 @@ One of these might be what you are looking for:
192
284
  | 2.0.2 | 2022-06-24 | [v2.0.2 CHANGELOG][2.0.2-changelog] | [v2.0.2 README][2.0.2-readme] |
193
285
  | 2.0.1 | 2022-06-22 | [v2.0.1 CHANGELOG][2.0.1-changelog] | [v2.0.1 README][2.0.1-readme] |
194
286
  | 2.0.0 | 2022-06-21 | [v2.0.0 CHANGELOG][2.0.0-changelog] | [v2.0.0 README][2.0.0-readme] |
287
+
195
288
  </details>
196
289
 
197
- [2.0.10-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2010---2025-05-16
198
- [2.0.9-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#209---2022-09-16
199
- [2.0.8-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#208---2022-09-01
200
- [2.0.7-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#207---2022-08-22
201
- [2.0.6-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#206---2022-07-13
202
- [2.0.5-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#205---2022-07-07
203
- [2.0.4-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#204---2022-07-01
204
- [2.0.3-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#203---2022-06-28
205
- [2.0.2-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#202---2022-06-24
206
- [2.0.1-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#201---2022-06-22
207
- [2.0.0-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#200---2022-06-21
208
-
209
- [2.0.10-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.10/README.md
210
- [2.0.9-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.9/README.md
211
- [2.0.8-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.8/README.md
212
- [2.0.7-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.7/README.md
213
- [2.0.6-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.6/README.md
214
- [2.0.5-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.5/README.md
215
- [2.0.4-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.4/README.md
216
- [2.0.3-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.3/README.md
217
- [2.0.2-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.2/README.md
218
- [2.0.1-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.1/README.md
219
- [2.0.0-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.0/README.md
290
+ [2.0.17-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2017---2025-09-15
291
+ [2.0.16-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2016---2025-09-14
292
+ [2.0.15-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2015---2025-09-08
293
+ [2.0.14-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2014---2025-08-31
294
+ [2.0.13-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2013---2025-08-30
295
+ [2.0.12-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2012---2025-05-31
296
+ [2.0.11-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2011---2025-05-23
297
+ [2.0.10-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#2010---2025-05-17
298
+ [2.0.9-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#209---2022-09-16
299
+ [2.0.8-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#208---2022-09-01
300
+ [2.0.7-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#207---2022-08-22
301
+ [2.0.6-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#206---2022-07-13
302
+ [2.0.5-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#205---2022-07-07
303
+ [2.0.4-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#204---2022-07-01
304
+ [2.0.3-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#203---2022-06-28
305
+ [2.0.2-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#202---2022-06-24
306
+ [2.0.1-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#201---2022-06-22
307
+ [2.0.0-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#200---2022-06-21
308
+
309
+ [2.0.17-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.17/README.md
310
+ [2.0.16-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.16/README.md
311
+ [2.0.15-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.15/README.md
312
+ [2.0.14-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.14/README.md
313
+ [2.0.13-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.13/README.md
314
+ [2.0.12-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.12/README.md
315
+ [2.0.11-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.11/README.md
316
+ [2.0.10-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.10/README.md
317
+ [2.0.9-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.9/README.md
318
+ [2.0.8-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.8/README.md
319
+ [2.0.7-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.7/README.md
320
+ [2.0.6-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.6/README.md
321
+ [2.0.5-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.5/README.md
322
+ [2.0.4-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.4/README.md
323
+ [2.0.3-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.3/README.md
324
+ [2.0.2-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.2/README.md
325
+ [2.0.1-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.1/README.md
326
+ [2.0.0-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v2.0.0/README.md
220
327
 
221
328
  ### Older Releases
222
329
 
@@ -239,77 +346,86 @@ One of these might be what you are looking for:
239
346
  | 1.4.0 | Jun 9, 2017 | [v1.4.0 CHANGELOG][1.4.0-changelog] | [v1.4.0 README][1.4.0-readme] |
240
347
  </details>
241
348
 
242
- [1.4.11-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1411---2022-09-16
243
- [1.4.10-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1410---2022-07-01
244
- [1.4.9-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#149---2022-02-20
245
- [1.4.8-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#148---2022-02-18
246
- [1.4.7-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#147---2021-03-19
247
- [1.4.6-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#146---2021-03-19
248
- [1.4.5-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#145---2021-03-18
249
- [1.4.4-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#144---2020-02-12
250
- [1.4.3-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#143---2020-01-29
251
- [1.4.2-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#142---2019-10-01
252
- [1.4.1-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#141---2018-10-13
253
- [1.4.0-changelog]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#140---2017-06-09
254
-
255
- [1.4.11-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.11/README.md
256
- [1.4.10-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.10/README.md
257
- [1.4.9-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.9/README.md
258
- [1.4.8-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.8/README.md
259
- [1.4.7-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.7/README.md
260
- [1.4.6-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.6/README.md
261
- [1.4.5-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.5/README.md
262
- [1.4.4-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.4/README.md
263
- [1.4.3-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.3/README.md
264
- [1.4.2-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.2/README.md
265
- [1.4.1-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.1/README.md
266
- [1.4.0-readme]: https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.0/README.md
349
+ [1.4.11-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1411---2022-09-16
350
+ [1.4.10-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#1410---2022-07-01
351
+ [1.4.9-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#149---2022-02-20
352
+ [1.4.8-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#148---2022-02-18
353
+ [1.4.7-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#147---2021-03-19
354
+ [1.4.6-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#146---2021-03-19
355
+ [1.4.5-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#145---2021-03-18
356
+ [1.4.4-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#144---2020-02-12
357
+ [1.4.3-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#143---2020-01-29
358
+ [1.4.2-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#142---2019-10-01
359
+ [1.4.1-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#141---2018-10-13
360
+ [1.4.0-changelog]: https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md?ref_type=heads#140---2017-06-09
361
+
362
+ [1.4.11-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.11/README.md
363
+ [1.4.10-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.10/README.md
364
+ [1.4.9-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.9/README.md
365
+ [1.4.8-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.8/README.md
366
+ [1.4.7-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.7/README.md
367
+ [1.4.6-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.6/README.md
368
+ [1.4.5-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.5/README.md
369
+ [1.4.4-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.4/README.md
370
+ [1.4.3-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.3/README.md
371
+ [1.4.2-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.2/README.md
372
+ [1.4.1-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.1/README.md
373
+ [1.4.0-readme]: https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.4.0/README.md
267
374
 
268
375
  <details>
269
376
  <summary>1.3.x Readmes</summary>
270
377
 
271
- | Version | Release Date | Readme |
272
- |----------|--------------|----------------------------------------------------------|
273
- | 1.3.1 | Mar 3, 2017 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.3.1/README.md |
274
- | 1.3.0 | Dec 27, 2016 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.3.0/README.md |
378
+ | Version | Release Date | Readme |
379
+ |---------|--------------|--------------------------------------------------------------|
380
+ | 1.3.1 | Mar 3, 2017 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.3.1/README.md |
381
+ | 1.3.0 | Dec 27, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.3.0/README.md |
382
+
275
383
  </details>
276
384
 
277
385
  <details>
278
386
  <summary>&le;= 1.2.x Readmes (2016 and before)</summary>
279
387
 
280
- | Version | Release Date | Readme |
281
- |----------|--------------|----------------------------------------------------------|
282
- | 1.2.0 | Jun 30, 2016 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.2.0/README.md |
283
- | 1.1.0 | Jan 30, 2016 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.1.0/README.md |
284
- | 1.0.0 | May 23, 2014 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.0.0/README.md |
285
- | < 1.0.0 | Find here | https://gitlab.com/oauth-xx/oauth2/-/tags |
388
+ | Version | Release Date | Readme |
389
+ |---------|--------------|--------------------------------------------------------------|
390
+ | 1.2.0 | Jun 30, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.2.0/README.md |
391
+ | 1.1.0 | Jan 30, 2016 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.1.0/README.md |
392
+ | 1.0.0 | May 23, 2014 | https://gitlab.com/ruby-oauth/oauth2/-/blob/v1.0.0/README.md |
393
+ | < 1.0.0 | Find here | https://gitlab.com/ruby-oauth/oauth2/-/tags |
394
+
286
395
  </details>
287
396
 
288
397
  ## ✨ Installation
289
398
 
290
399
  Install the gem and add to the application's Gemfile by executing:
291
400
 
292
- $ bundle add oauth2
401
+ ```console
402
+ bundle add oauth2
403
+ ```
293
404
 
294
405
  If bundler is not being used to manage dependencies, install the gem by executing:
295
406
 
296
- $ gem install oauth2
407
+ ```console
408
+ gem install oauth2
409
+ ```
297
410
 
298
411
  ### 🔒 Secure Installation
299
412
 
300
- `oauth2` is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
413
+ <details>
414
+ <summary>For Medium or High Security Installations</summary>
415
+
416
+ This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
301
417
  [stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
302
418
  by following the instructions below.
303
419
 
304
420
  Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
305
421
 
306
- ```shell
307
- gem cert --add <(curl -Ls https://raw.github.com/kettle-rb/oauth2/main/certs/pboling.pem)
422
+ ```console
423
+ gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
308
424
  ```
309
425
 
310
426
  You only need to do that once. Then proceed to install with:
311
427
 
312
- ```shell
428
+ ```console
313
429
  gem install oauth2 -P MediumSecurity
314
430
  ```
315
431
 
@@ -319,49 +435,39 @@ This is necessary because not all of `oauth2`’s dependencies are signed, so we
319
435
 
320
436
  If you want to up your security game full-time:
321
437
 
322
- ```shell
438
+ ```console
323
439
  bundle config set --global trust-policy MediumSecurity
324
440
  ```
325
441
 
326
442
  NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
327
443
 
328
- ## OAuth2 for Enterprise
329
-
330
- Available as part of the Tidelift Subscription.
331
-
332
- 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. [Learn more.][tidelift-ref]
333
-
334
- [tidelift-ref]: https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=enterprise
335
-
336
- ## Security contact information
337
-
338
- To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
339
- Tidelift will coordinate the fix and disclosure.
340
-
341
- For more see [SECURITY.md][🔐security].
444
+ </details>
342
445
 
343
446
  ## What is new for v2.0?
344
447
 
345
- - Officially support Ruby versions >= 2.7
346
- - Unofficially support Ruby versions >= 2.5
347
- - Incidentally support Ruby versions >= 2.2
448
+ - Works with Ruby versions >= 2.2
348
449
  - Drop support for the expired MAC Draft (all versions)
349
- - Support IETF rfc7523 JWT Bearer Tokens
350
- - Support IETF rfc7231 Relative Location in Redirect
351
- - Support IETF rfc6749 Don't set oauth params when nil
450
+ - Support IETF rfc7515 JSON Web Signature - JWS (since v2.0.12)
451
+ - Support JWT `kid` for key discovery and management
452
+ - Support IETF rfc7523 JWT Bearer Tokens (since v2.0.0)
453
+ - Support IETF rfc7231 Relative Location in Redirect (since v2.0.0)
454
+ - Support IETF rfc6749 Don't set oauth params when nil (since v2.0.0)
455
+ - Support IETF rfc7009 Token Revocation (since v2.0.10, updated in v2.0.13 to support revocation via URL-encoded parameters)
352
456
  - 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)
353
457
  - 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`
354
- - Adds new option to `OAuth2::Client#get_token`:
458
+ - Adds option to `OAuth2::Client#get_token`:
355
459
  - `:access_token_class` (`AccessToken`); user specified class to use for all calls to `get_token`
356
- - Adds new option to `OAuth2::AccessToken#initialize`:
460
+ - Adds option to `OAuth2::AccessToken#initialize`:
357
461
  - `:expires_latency` (`nil`); number of seconds by which AccessToken validity will be reduced to offset latency
358
462
  - By default, keys are transformed to snake case.
359
- - Original keys will still work as previously, in most scenarios, thanks to `rash_alt` gem.
360
- - 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.
361
- - As of version 2.0.4 you can turn key transformation off with the `snaky: false` option.
463
+ - Original keys will still work as previously, in most scenarios, thanks to [snaky_hash][snaky_hash] gem.
464
+ - 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.
465
+ - As of version 2.0.4 you can turn key transformation off with the `snaky: false` option.
362
466
  - By default, the `:auth_scheme` is now `:basic_auth` (instead of `:request_body`)
363
- - Third-party strategies and gems may need to be updated if a provider was requiring client id/secret in the request body
364
- - [... A lot more](https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md#200-2022-06-21-tag)
467
+ - Third-party strategies and gems may need to be updated if a provider was requiring client id/secret in the request body
468
+ - [... A lot more](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/CHANGELOG.md#200-2022-06-21-tag)
469
+
470
+ [snaky_hash]: https://gitlab.com/ruby-oauth/snaky_hash
365
471
 
366
472
  ## Compatibility
367
473
 
@@ -377,7 +483,7 @@ This gem is tested against MRI, JRuby, and Truffleruby.
377
483
  Each of those has varying versions that target a specific version of MRI Ruby.
378
484
  This gem should work in the just-listed Ruby engines according to the targeted MRI compatibility in the table below.
379
485
  If you would like to add support for additional engines,
380
- see `gemfiles/README.md`, then submit a PR to the correct maintenance branch as according to the table below.
486
+ see [gemfiles/README.md](gemfiles/README.md), then submit a PR to the correct maintenance branch as according to the table below.
381
487
  </details>
382
488
 
383
489
  <details>
@@ -404,25 +510,23 @@ of a major release, support for that Ruby version may be dropped.
404
510
  | 3️⃣ | older | N/A | Best of luck to you! | Please upgrade! | |
405
511
 
406
512
  NOTE: The 1.4 series will only receive critical security updates.
407
- See [SECURITY.md][🚎sec-pol]
408
-
409
- ## Usage Examples
513
+ See [SECURITY.md][🔐security].
410
514
 
411
- ### Global Configuration
515
+ ## ⚙️ Configuration
412
516
 
413
- If you started seeing this warning, but everything is working fine, you can now silence it.
414
- ```log
415
- OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key
416
- ```
517
+ You can turn on additional warnings.
417
518
 
418
519
  ```ruby
419
520
  OAuth2.configure do |config|
420
- config.silence_extra_tokens_warning = true # default: false
421
- config.silence_no_tokens_warning = true # default: false, if you want to also silence warnings about no tokens
521
+ # Turn on a warning like:
522
+ # OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key
523
+ config.silence_extra_tokens_warning = false # default: true
524
+ # Set to true if you want to also show warnings about no tokens
525
+ config.silence_no_tokens_warning = false # default: true,
422
526
  end
423
527
  ```
424
528
 
425
- This comes from ambiguity in the spec about which token is the right token.
529
+ The "extra tokens" problem comes from ambiguity in the spec about which token is the right token.
426
530
  Some OAuth 2.0 standards legitimately have multiple tokens.
427
531
  You may need to subclass `OAuth2::AccessToken`, or write your own custom alternative to it, and pass it in.
428
532
  Specify your custom class with the `access_token_class` option.
@@ -435,6 +539,8 @@ You'll likely need to do some source diving.
435
539
  This gem has 100% test coverage for lines and branches, so the specs are a great place to look for ideas.
436
540
  If you have time and energy please contribute to the documentation!
437
541
 
542
+ ## 🔧 Basic Usage
543
+
438
544
  ### `authorize_url` and `token_url` are on site root (Just Works!)
439
545
 
440
546
  ```ruby
@@ -491,10 +597,105 @@ response.parsed.access_token # => "aaaaaaaa"
491
597
  response.parsed[:access_token] # => "aaaaaaaa"
492
598
  response.parsed.additional_data # => "additional"
493
599
  response.parsed[:additional_data] # => "additional"
494
- response.parsed.class.name # => OAuth2::SnakyHash (subclass of Hashie::Mash::Rash, from `rash_alt` gem)
600
+ response.parsed.class.name # => SnakyHash::StringKeyed (from snaky_hash gem)
601
+ ```
602
+
603
+ #### Serialization
604
+
605
+ As of v2.0.11, if you need to serialize the parsed result, you can!
606
+
607
+ There are two ways to do this, globally, or discretely. The discrete way is recommended.
608
+
609
+ ##### Global Serialization Config
610
+
611
+ Globally configure `SnakyHash::StringKeyed` to use the serializer. Put this in your code somewhere reasonable (like an initializer for Rails).
612
+
613
+ ```ruby
614
+ SnakyHash::StringKeyed.class_eval do
615
+ extend SnakyHash::Serializer
616
+ end
617
+ ```
618
+
619
+ ##### Discrete Serialization Config
620
+
621
+ Discretely configure a custom Snaky Hash class to use the serializer.
622
+
623
+ ```ruby
624
+ class MySnakyHash < SnakyHash::StringKeyed
625
+ # Give this hash class `dump` and `load` abilities!
626
+ extend SnakyHash::Serializer
627
+ end
628
+
629
+ # And tell your client to use the custom class in each call:
630
+ client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/oauth2")
631
+ token = client.get_token({snaky_hash_klass: MySnakyHash})
495
632
  ```
496
633
 
497
- #### What if I hate snakes and/or indifference?
634
+ ##### Serialization Extensions
635
+
636
+ These extensions work regardless of whether you used the global or discrete config above.
637
+
638
+ There are a few hacks you may need in your class to support Ruby < 2.4.2 or < 2.6.
639
+ They are likely not needed if you are on a newer Ruby.
640
+ 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.
641
+
642
+ ```ruby
643
+ class MySnakyHash < SnakyHash::StringKeyed
644
+ # Give this hash class `dump` and `load` abilities!
645
+ extend SnakyHash::Serializer
646
+
647
+ #### Serialization Extentions
648
+ #
649
+ # Act on the non-hash values (including the values of hashes) as they are dumped to JSON
650
+ # In other words, this retains nested hashes, and only the deepest leaf nodes become bananas.
651
+ # WARNING: This is a silly example!
652
+ dump_value_extensions.add(:to_fruit) do |value|
653
+ "banana" # => Make values "banana" on dump
654
+ end
655
+
656
+ # Act on the non-hash values (including the values of hashes) as they are loaded from the JSON dump
657
+ # In other words, this retains nested hashes, and only the deepest leaf nodes become ***.
658
+ # WARNING: This is a silly example!
659
+ load_value_extensions.add(:to_stars) do |value|
660
+ "***" # Turn dumped bananas into *** when they are loaded
661
+ end
662
+
663
+ # Act on the entire hash as it is prepared for dumping to JSON
664
+ # WARNING: This is a silly example!
665
+ dump_hash_extensions.add(:to_cheese) do |value|
666
+ if value.is_a?(Hash)
667
+ value.transform_keys do |key|
668
+ split = key.split("_")
669
+ first_word = split[0]
670
+ key.sub(first_word, "cheese")
671
+ end
672
+ else
673
+ value
674
+ end
675
+ end
676
+
677
+ # Act on the entire hash as it is loaded from the JSON dump
678
+ # WARNING: This is a silly example!
679
+ load_hash_extensions.add(:to_pizza) do |value|
680
+ if value.is_a?(Hash)
681
+ res = klass.new
682
+ value.keys.each_with_object(res) do |key, result|
683
+ split = key.split("_")
684
+ last_word = split[-1]
685
+ new_key = key.sub(last_word, "pizza")
686
+ result[new_key] = value[key]
687
+ end
688
+ res
689
+ else
690
+ value
691
+ end
692
+ end
693
+ end
694
+ ```
695
+
696
+ 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.
697
+
698
+ #### Prefer camelCase over snake_case? => snaky: false
498
699
 
499
700
  ```ruby
500
701
  response = access.get("/api/resource", params: {"query_foo" => "bar"}, snaky: false)
@@ -506,9 +707,9 @@ response.parsed.class.name # => Hash (just, regular old Hash)
506
707
  ```
507
708
 
508
709
  <details>
509
- <summary>Debugging</summary>
710
+ <summary>Debugging & Logging</summary>
510
711
 
511
- Set an environment variable, however you would [normally do that](https://github.com/bkeepers/dotenv).
712
+ Set an environment variable as per usual (e.g. with [dotenv](https://github.com/bkeepers/dotenv)).
512
713
 
513
714
  ```ruby
514
715
  # will log both request and response, including bodies
@@ -529,27 +730,39 @@ client = OAuth2::Client.new(
529
730
  ```
530
731
  </details>
531
732
 
532
- ## OAuth2::Response
733
+ ### OAuth2::Response
533
734
 
534
735
  The `AccessToken` methods `#get`, `#post`, `#put` and `#delete` and the generic `#request`
535
736
  will return an instance of the #OAuth2::Response class.
536
737
 
537
738
  This instance contains a `#parsed` method that will parse the response body and
538
- return a Hash-like [`OAuth2::SnakyHash`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/snaky_hash.rb) if the `Content-Type` is `application/x-www-form-urlencoded` or if
739
+ 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
539
740
  the body is a JSON object. It will return an Array if the body is a JSON
540
741
  array. Otherwise, it will return the original body string.
541
742
 
542
743
  The original response body, headers, and status can be accessed via their
543
744
  respective methods.
544
745
 
545
- ## OAuth2::AccessToken
746
+ ### OAuth2::AccessToken
546
747
 
547
748
  If you have an existing Access Token for a user, you can initialize an instance
548
749
  using various class methods including the standard new, `from_hash` (if you have
549
750
  a hash of the values), or `from_kvform` (if you have an
550
751
  `application/x-www-form-urlencoded` encoded string of the values).
551
752
 
552
- ## OAuth2::Error
753
+ Options (since v2.0.x unless noted):
754
+ - expires_latency (Integer | nil): Seconds to subtract from expires_in when computing #expired? to offset latency.
755
+ - 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).
756
+ - mode (Symbol | Proc | Hash): Controls how the token is transmitted on requests made via this AccessToken instance.
757
+ - :header — Send as Authorization: Bearer <token> header (default and preferred by OAuth 2.1 draft guidance).
758
+ - :query — Send as access_token query parameter (discouraged in general, but required by some providers).
759
+ - Verb-dependent (since v2.0.15): Provide either:
760
+ - a Proc taking |verb| and returning :header or :query, or
761
+ - a Hash with verb symbols as keys, for example: {get: :query, post: :header, delete: :header}.
762
+
763
+ Note: Verb-dependent mode was added in v2.0.15 to support providers like Instagram that require query mode for GET and header mode for POST/DELETE.
764
+
765
+ ### OAuth2::Error
553
766
 
554
767
  On 400+ status code responses, an `OAuth2::Error` will be raised. If it is a
555
768
  standard OAuth2 error response, the body will be parsed and `#code` and `#description` will contain the values provided from the error and
@@ -561,15 +774,31 @@ option on initialization of the client. In this case the `OAuth2::Response`
561
774
  instance will be returned as usual and on 400+ status code responses, the
562
775
  Response instance will contain the `OAuth2::Error` instance.
563
776
 
564
- ## Authorization Grants
565
-
566
- Currently the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion
777
+ ### Authorization Grants
778
+
779
+ Note on OAuth 2.1 (draft):
780
+ - 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.
781
+ - Redirect URIs must be compared using exact string matching by the Authorization Server.
782
+ - 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.
783
+ - Bearer tokens in the query string are omitted due to security risks; prefer Authorization header usage.
784
+ - Refresh tokens for public clients must either be sender-constrained (e.g., DPoP/MTLS) or one-time use.
785
+ - The definitions of public and confidential clients are simplified to refer only to whether the client has credentials.
786
+
787
+ References:
788
+ - OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
789
+ - Aaron Parecki: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1
790
+ - FusionAuth: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1
791
+ - Okta: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs
792
+ - Video: https://www.youtube.com/watch?v=g_aVPdwBTfw
793
+ - Differences overview: https://fusionauth.io/learn/expert-advice/oauth/differences-between-oauth-2-oauth-2-1/
794
+
795
+ Currently, the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion
567
796
  authentication grant types have helper strategy classes that simplify client
568
- use. They are available via the [`#auth_code`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/auth_code.rb),
569
- [`#implicit`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/implicit.rb),
570
- [`#password`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/password.rb),
571
- [`#client_credentials`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/client_credentials.rb), and
572
- [`#assertion`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/assertion.rb) methods respectively.
797
+ use. They are available via the [`#auth_code`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/auth_code.rb),
798
+ [`#implicit`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/implicit.rb),
799
+ [`#password`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/password.rb),
800
+ [`#client_credentials`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/client_credentials.rb), and
801
+ [`#assertion`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/assertion.rb) methods respectively.
573
802
 
574
803
  These aren't full examples, but demonstrative of the differences between usage for each strategy.
575
804
  ```ruby
@@ -610,34 +839,506 @@ access = client.auth_code.get_token("code_value", redirect_uri: "http://localhos
610
839
  You can always use the `#request` method on the `OAuth2::Client` instance to make
611
840
  requests for tokens for any Authentication grant type.
612
841
 
613
- ### 🚀 Release Instructions
842
+ ## 📘 Comprehensive Usage
614
843
 
615
- See [CONTRIBUTING.md][🤝contributing].
844
+ ### Common Flows (end-to-end)
845
+
846
+ - Authorization Code (server-side web app):
847
+
848
+ ```ruby
849
+ require "oauth2"
850
+ client = OAuth2::Client.new(
851
+ ENV["CLIENT_ID"],
852
+ ENV["CLIENT_SECRET"],
853
+ site: "https://provider.example.com",
854
+ redirect_uri: "https://my.app.example.com/oauth/callback",
855
+ )
856
+
857
+ # Step 1: redirect user to consent
858
+ state = SecureRandom.hex(16)
859
+ auth_url = client.auth_code.authorize_url(scope: "openid profile email", state: state)
860
+ # redirect_to auth_url
861
+
862
+ # Step 2: handle the callback
863
+ # params[:code], params[:state]
864
+ raise "state mismatch" unless params[:state] == state
865
+ access = client.auth_code.get_token(params[:code])
866
+
867
+ # Step 3: call APIs
868
+ profile = access.get("/api/v1/me").parsed
869
+ ```
870
+
871
+ - Client Credentials (machine-to-machine):
872
+
873
+ ```ruby
874
+ client = OAuth2::Client.new(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"], site: "https://provider.example.com")
875
+ access = client.client_credentials.get_token(audience: "https://api.example.com")
876
+ resp = access.get("/v1/things")
877
+ ```
878
+
879
+ - Resource Owner Password (legacy; avoid when possible):
880
+
881
+ ```ruby
882
+ access = client.password.get_token("jdoe", "s3cret", scope: "read")
883
+ ```
884
+
885
+ #### Examples
886
+
887
+ <details>
888
+ <summary>JHipster UAA (Spring Cloud) password grant example (legacy; avoid when possible)</summary>
889
+
890
+ ```ruby
891
+ # This converts a Postman/Net::HTTP multipart token request to oauth2 gem usage.
892
+ # JHipster UAA typically exposes the token endpoint at /uaa/oauth/token.
893
+ # The original snippet included:
894
+ # - Basic Authorization header for the client (web_app:changeit)
895
+ # - X-XSRF-TOKEN header from a cookie (some deployments require it)
896
+ # - grant_type=password with username/password and client_id
897
+ # Using oauth2 gem, you don't need to build multipart bodies; the gem sends
898
+ # application/x-www-form-urlencoded as required by RFC 6749.
899
+
900
+ require "oauth2"
901
+
902
+ client = OAuth2::Client.new(
903
+ "web_app", # client_id
904
+ "changeit", # client_secret
905
+ site: "http://localhost:8080/uaa",
906
+ token_url: "/oauth/token", # absolute under site (or "oauth/token" relative)
907
+ auth_scheme: :basic_auth, # sends HTTP Basic Authorization header
908
+ )
909
+
910
+ # If your UAA requires an XSRF header for the token call, provide it as a header.
911
+ # Often this is not required for token endpoints, but if your gateway enforces it,
912
+ # obtain the value from the XSRF-TOKEN cookie and pass it here.
913
+ xsrf_token = ENV["X_XSRF_TOKEN"] # e.g., pulled from a prior set-cookie value
914
+
915
+ access = client.password.get_token(
916
+ "admin", # username
917
+ "admin", # password
918
+ headers: xsrf_token ? {"X-XSRF-TOKEN" => xsrf_token} : {},
919
+ # JHipster commonly also accepts/needs the client_id in the body; include if required:
920
+ # client_id: "web_app",
921
+ )
922
+
923
+ puts access.token
924
+ puts access.to_hash # full token response
925
+ ```
926
+
927
+ Notes:
928
+ - Resource Owner Password Credentials (ROPC) is deprecated in OAuth 2.1 and discouraged. Prefer Authorization Code + PKCE.
929
+ - 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.
930
+ - For Basic auth, auth_scheme: :basic_auth handles the Authorization header; you do not need to base64-encode manually.
931
+
932
+ </details>
933
+
934
+ ### Instagram API (verb‑dependent token mode)
935
+
936
+ Providers like Instagram require the access token to be sent differently depending on the HTTP verb:
937
+ - GET requests: token must be in the query string (?access_token=...)
938
+ - POST/DELETE requests: token must be in the Authorization header (Bearer ...)
939
+
940
+ 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.
941
+
942
+ Example: exchanging and refreshing long‑lived Instagram tokens, and making API calls
943
+
944
+ ```ruby
945
+ require "oauth2"
946
+
947
+ # NOTE: Users authenticate via Facebook Login to obtain a short‑lived user token (not shown here).
948
+ # See Facebook Login docs for obtaining the initial short‑lived token.
949
+
950
+ client = OAuth2::Client.new(nil, nil, site: "https://graph.instagram.com")
951
+
952
+ # Start with a short‑lived token you already obtained via Facebook Login
953
+ short_lived = OAuth2::AccessToken.new(
954
+ client,
955
+ ENV["IG_SHORT_LIVED_TOKEN"],
956
+ # Key part: verb‑dependent mode
957
+ mode: {get: :query, post: :header, delete: :header},
958
+ )
959
+
960
+ # 1) Exchange for a long‑lived token (Instagram requires GET with access_token in query)
961
+ # Endpoint: GET https://graph.instagram.com/access_token
962
+ # Params: grant_type=ig_exchange_token, client_secret=APP_SECRET
963
+ exchange = short_lived.get(
964
+ "/access_token",
965
+ params: {
966
+ grant_type: "ig_exchange_token",
967
+ client_secret: ENV["IG_APP_SECRET"],
968
+ # access_token param will be added automatically by the AccessToken (mode => :query for GET)
969
+ },
970
+ )
971
+ long_lived_token_value = exchange.parsed["access_token"]
972
+
973
+ long_lived = OAuth2::AccessToken.new(
974
+ client,
975
+ long_lived_token_value,
976
+ mode: {get: :query, post: :header, delete: :header},
977
+ )
978
+
979
+ # 2) Refresh the long‑lived token (Instagram uses GET with token in query)
980
+ # Endpoint: GET https://graph.instagram.com/refresh_access_token
981
+ refresh_resp = long_lived.get(
982
+ "/refresh_access_token",
983
+ params: {grant_type: "ig_refresh_token"},
984
+ )
985
+ long_lived = OAuth2::AccessToken.new(
986
+ client,
987
+ refresh_resp.parsed["access_token"],
988
+ mode: {get: :query, post: :header, delete: :header},
989
+ )
990
+
991
+ # 3) Typical API GET request (token in query automatically)
992
+ me = long_lived.get("/me", params: {fields: "id,username"}).parsed
993
+
994
+ # 4) Example POST (token sent via Bearer header automatically)
995
+ # Note: Replace the path/params with a real Instagram Graph API POST you need,
996
+ # such as publishing media via the Graph API endpoints.
997
+ # long_lived.post("/me/media", body: {image_url: "https://...", caption: "hello"})
998
+ ```
999
+
1000
+ Tips:
1001
+ - Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET.
1002
+ - If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }.
1003
+
1004
+ ### Refresh Tokens
1005
+
1006
+ When the server issues a refresh_token, you can refresh manually or implement an auto-refresh wrapper.
1007
+
1008
+ - Manual refresh:
1009
+
1010
+ ```ruby
1011
+ if access.expired?
1012
+ access = access.refresh
1013
+ end
1014
+ ```
1015
+
1016
+ - Auto-refresh wrapper pattern:
1017
+
1018
+ ```ruby
1019
+ class AutoRefreshingToken
1020
+ def initialize(token_provider, store: nil)
1021
+ @token = token_provider
1022
+ @store = store # e.g., something that responds to read/write for token data
1023
+ end
1024
+
1025
+ def with(&blk)
1026
+ tok = ensure_fresh!
1027
+ blk ? blk.call(tok) : tok
1028
+ rescue OAuth2::Error => e
1029
+ # If a 401 suggests token invalidation, try one refresh and retry once
1030
+ if e.response && e.response.status == 401 && @token.refresh_token
1031
+ @token = @token.refresh
1032
+ @store.write(@token.to_hash) if @store
1033
+ retry
1034
+ end
1035
+ raise
1036
+ end
1037
+
1038
+ private
1039
+
1040
+ def ensure_fresh!
1041
+ if @token.expired? && @token.refresh_token
1042
+ @token = @token.refresh
1043
+ @store.write(@token.to_hash) if @store
1044
+ end
1045
+ @token
1046
+ end
1047
+ end
1048
+
1049
+ # usage
1050
+ keeper = AutoRefreshingToken.new(access)
1051
+ keeper.with { |tok| tok.get("/v1/protected") }
1052
+ ```
1053
+
1054
+ Persist the token across processes using `AccessToken#to_hash` and `AccessToken.from_hash(client, hash)`.
1055
+
1056
+ ### Token Revocation (RFC 7009)
1057
+
1058
+ You can revoke either the access token or the refresh token.
1059
+
1060
+ ```ruby
1061
+ # Revoke the current access token
1062
+ access.revoke(token_type_hint: :access_token)
1063
+
1064
+ # Or explicitly revoke the refresh token (often also invalidates associated access tokens)
1065
+ access.revoke(token_type_hint: :refresh_token)
1066
+ ```
1067
+
1068
+ ### Client Configuration Tips
1069
+
1070
+ #### Mutual TLS (mTLS) client authentication
1071
+
1072
+ 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.
1073
+
1074
+ Example using PEM files (certificate and key):
1075
+
1076
+ ```ruby
1077
+ require "oauth2"
1078
+ require "openssl"
1079
+
1080
+ client = OAuth2::Client.new(
1081
+ ENV.fetch("CLIENT_ID"),
1082
+ ENV.fetch("CLIENT_SECRET"),
1083
+ site: "https://example.com",
1084
+ authorize_url: "/oauth/authorize/",
1085
+ token_url: "/oauth/token/",
1086
+ auth_scheme: :tls_client_auth, # if your AS requires mTLS-based client authentication
1087
+ connection_opts: {
1088
+ ssl: {
1089
+ client_cert: OpenSSL::X509::Certificate.new(File.read("localhost.pem")),
1090
+ client_key: OpenSSL::PKey::RSA.new(File.read("localhost-key.pem")),
1091
+ # Optional extras, uncomment as needed:
1092
+ # ca_file: "/path/to/ca-bundle.pem", # custom CA(s)
1093
+ # verify: true # enable server cert verification (recommended)
1094
+ },
1095
+ },
1096
+ )
1097
+
1098
+ # Example token request (any grant type can be used). The mTLS handshake
1099
+ # will occur automatically on HTTPS calls using the configured cert/key.
1100
+ access = client.client_credentials.get_token
1101
+
1102
+ # Subsequent resource requests will also use mTLS on HTTPS endpoints of `site`:
1103
+ resp = access.get("/v1/protected")
1104
+ ```
1105
+
1106
+ Notes:
1107
+ - 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"]).
1108
+ - If your certificate and key are in a PKCS#12/PFX bundle, you can load them like:
1109
+ - p12 = OpenSSL::PKCS12.new(File.read("client.p12"), ENV["P12_PASSWORD"])
1110
+ - client_cert = p12.certificate; client_key = p12.key
1111
+ - Server trust:
1112
+ - If your environment does not have system CAs, specify ca_file or ca_path inside the ssl: hash.
1113
+ - Keep verify: true in production. Set verify: false only for local testing.
1114
+ - Faraday adapter: Any adapter that supports Ruby’s OpenSSL should work. net_http (default) and net_http_persistent are common choices.
1115
+ - 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).
1116
+ - 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.
1117
+
1118
+ #### Authentication schemes for the token request
1119
+
1120
+ ```ruby
1121
+ OAuth2::Client.new(
1122
+ id,
1123
+ secret,
1124
+ site: "https://provider.example.com",
1125
+ auth_scheme: :basic_auth, # default. Alternatives: :request_body, :tls_client_auth, :private_key_jwt
1126
+ )
1127
+ ```
1128
+
1129
+ #### Faraday connection, timeouts, proxy, custom adapter/middleware:
1130
+
1131
+ ```ruby
1132
+ client = OAuth2::Client.new(
1133
+ id,
1134
+ secret,
1135
+ site: "https://provider.example.com",
1136
+ connection_opts: {
1137
+ request: {open_timeout: 5, timeout: 15},
1138
+ proxy: ENV["HTTPS_PROXY"],
1139
+ ssl: {verify: true},
1140
+ },
1141
+ ) do |faraday|
1142
+ faraday.request(:url_encoded)
1143
+ # faraday.response :logger, Logger.new($stdout) # see OAUTH_DEBUG below
1144
+ faraday.adapter(:net_http_persistent) # or any Faraday adapter you need
1145
+ end
1146
+ ```
1147
+
1148
+ ##### Using flat query params (Faraday::FlatParamsEncoder)
1149
+
1150
+ 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.
1151
+
1152
+ ```ruby
1153
+ require "faraday"
1154
+
1155
+ client = OAuth2::Client.new(
1156
+ id,
1157
+ secret,
1158
+ site: "https://api.example.com",
1159
+ # Pass Faraday connection options to make FlatParamsEncoder the default
1160
+ connection_opts: {
1161
+ request: {params_encoder: Faraday::FlatParamsEncoder},
1162
+ },
1163
+ ) do |faraday|
1164
+ faraday.request(:url_encoded)
1165
+ faraday.adapter(:net_http)
1166
+ end
1167
+
1168
+ access = client.client_credentials.get_token
1169
+
1170
+ # Example of a GET with two flat filter params (not an array):
1171
+ # Results in: ?filter=order.clientCreatedTime%3E1445006997000&filter=order.clientCreatedTime%3C1445611797000
1172
+ resp = access.get(
1173
+ "/v1/orders",
1174
+ params: {
1175
+ # Provide the values as an array; FlatParamsEncoder expands them as repeated keys
1176
+ filter: [
1177
+ "order.clientCreatedTime>1445006997000",
1178
+ "order.clientCreatedTime<1445611797000",
1179
+ ],
1180
+ },
1181
+ )
1182
+ ```
1183
+
1184
+ If you instead need to build a raw Faraday connection yourself, the equivalent configuration is:
1185
+
1186
+ ```ruby
1187
+ conn = Faraday.new("https://api.example.com", request: {params_encoder: Faraday::FlatParamsEncoder})
1188
+ ```
1189
+
1190
+ #### Redirection
1191
+
1192
+ The library follows up to `max_redirects` (default 5).
1193
+ You can override per-client via `options[:max_redirects]`.
1194
+
1195
+ ### Handling Responses and Errors
1196
+
1197
+ - Parsing:
1198
+
1199
+ ```ruby
1200
+ resp = access.get("/v1/thing")
1201
+ resp.status # Integer
1202
+ resp.headers # Hash
1203
+ resp.body # String
1204
+ resp.parsed # SnakyHash::StringKeyed or Array when JSON array
1205
+ ```
1206
+
1207
+ - Error handling:
1208
+
1209
+ ```ruby
1210
+ begin
1211
+ access.get("/v1/forbidden")
1212
+ rescue OAuth2::Error => e
1213
+ e.code # OAuth2 error code (when present)
1214
+ e.description # OAuth2 error description (when present)
1215
+ e.response # OAuth2::Response (full access to status/headers/body)
1216
+ end
1217
+ ```
1218
+
1219
+ - Disable raising on 4xx/5xx to inspect the response yourself:
1220
+
1221
+ ```ruby
1222
+ client = OAuth2::Client.new(id, secret, site: site, raise_errors: false)
1223
+ res = client.request(:get, "/v1/maybe-errors")
1224
+ if res.status == 429
1225
+ sleep res.headers["retry-after"].to_i
1226
+ end
1227
+ ```
1228
+
1229
+ ### Making Raw Token Requests
1230
+
1231
+ If a provider requires non-standard parameters or headers, you can call `client.get_token` directly:
1232
+
1233
+ ```ruby
1234
+ access = client.get_token({
1235
+ grant_type: "client_credentials",
1236
+ audience: "https://api.example.com",
1237
+ headers: {"X-Custom" => "value"},
1238
+ parse: :json, # override parsing
1239
+ })
1240
+ ```
1241
+
1242
+ ### OpenID Connect (OIDC) Notes
1243
+
1244
+ - 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.
1245
+ - For private_key_jwt client authentication, provide `auth_scheme: :private_key_jwt` and ensure your key configuration matches the provider requirements.
1246
+ - See [OIDC.md](OIDC.md) for a more complete OIDC overview, example, and links to the relevant specifications.
1247
+
1248
+ ### Debugging
1249
+
1250
+ - Set environment variable `OAUTH_DEBUG=true` to enable verbose Faraday logging (uses the client-provided logger).
1251
+ - 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.
1252
+
1253
+ ---
1254
+
1255
+ ## 🦷 FLOSS Funding
1256
+
1257
+ While ruby-oauth tools are free software and will always be, the project would benefit immensely from some funding.
1258
+ Raising a monthly budget of... "dollars" would make the project more sustainable.
1259
+
1260
+ We welcome both individual and corporate sponsors! We also offer a
1261
+ wide array of funding channels to account for your preferences
1262
+ (although currently [Open Collective][🖇osc] is our preferred funding platform).
1263
+
1264
+ **If you're working in a company that's making significant use of ruby-oauth tools we'd
1265
+ appreciate it if you suggest to your company to become a ruby-oauth sponsor.**
1266
+
1267
+ You can support the development of ruby-oauth tools via
1268
+ [GitHub Sponsors][🖇sponsor],
1269
+ [Liberapay][⛳liberapay],
1270
+ [PayPal][🖇paypal],
1271
+ [Open Collective][🖇osc]
1272
+ and [Tidelift][🏙️entsup-tidelift].
1273
+
1274
+ | 📍 NOTE |
1275
+ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
1276
+ | If doing a sponsorship in the form of donation is problematic for your company <br/> from an accounting standpoint, we'd recommend the use of Tidelift, <br/> where you can get a support-like subscription instead. |
1277
+
1278
+ ### Open Collective for Individuals
1279
+
1280
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
1281
+ No backers yet. Be the first!
1282
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
1283
+
1284
+ Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/ruby-oauth#backer)]
1285
+
1286
+ ### Open Collective for Organizations
1287
+
1288
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
1289
+ No sponsors yet. Be the first!
1290
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
1291
+
1292
+ 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)]
1293
+
1294
+ ### Another way to support open-source
1295
+
1296
+ > How wonderful it is that nobody need wait a single moment before starting to improve the world.<br/>
1297
+ >—Anne Frank
1298
+
1299
+ 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).
1300
+
1301
+ 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`.
1302
+
1303
+ 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.
1304
+
1305
+ **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
1306
+
1307
+ [![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]
616
1308
 
617
1309
  ## 🔐 Security
618
1310
 
619
- See [SECURITY.md][🔐security].
1311
+ To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
1312
+ Tidelift will coordinate the fix and disclosure.
1313
+
1314
+ For more see [SECURITY.md][🔐security].
620
1315
 
621
1316
  ## 🤝 Contributing
622
1317
 
623
1318
  If you need some ideas of where to help, you could work on adding more code coverage,
624
- or if it is already 💯 (see [below](#code-coverage)) check TODOs (see [below](#todos)),
625
- or check [issues][🤝issues], or [PRs][🤝pulls],
1319
+ or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
626
1320
  or use the gem and think about how it could be better.
627
1321
 
628
1322
  We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
629
1323
 
630
1324
  See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
631
1325
 
1326
+ ### 🚀 Release Instructions
1327
+
1328
+ See [CONTRIBUTING.md][🤝contributing].
1329
+
632
1330
  ### Code Coverage
633
1331
 
1332
+ [![Coverage Graph][🔑codecov-g]][🔑codecov]
1333
+
634
1334
  [![Coveralls Test Coverage][🔑coveralls-img]][🔑coveralls]
635
- [![QLTY Test Coverage][🔑cc-covi♻️]][🔑cc-cov]
1335
+
1336
+ [![QLTY Test Coverage][🔑qlty-covi]][🔑qlty-cov]
636
1337
 
637
1338
  ### 🪇 Code of Conduct
638
1339
 
639
- Everyone interacting in this project's codebases, issue trackers,
640
- chat rooms and mailing lists is expected to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
1340
+ Everyone interacting with this project's codebases, issue trackers,
1341
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
641
1342
 
642
1343
  ## 🌈 Contributors
643
1344
 
@@ -645,18 +1346,21 @@ chat rooms and mailing lists is expected to follow the [![Contributor Covenant 2
645
1346
 
646
1347
  Made with [contributors-img][🖐contrib-rocks].
647
1348
 
648
- Also see GitLab Contributors: [https://gitlab.com/oauth-xx/oauth2/-/graphs/main][🚎contributors-gl]
1349
+ Also see GitLab Contributors: [https://gitlab.com/ruby-oauth/oauth2/-/graphs/main][🚎contributors-gl]
649
1350
 
650
- ## ⭐️ Star History
1351
+ <details>
1352
+ <summary>⭐️ Star History</summary>
651
1353
 
652
- <a href="https://star-history.com/#oauth-xx/oauth2&Date">
1354
+ <a href="https://star-history.com/#ruby-oauth/oauth2&Date">
653
1355
  <picture>
654
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=oauth-xx/oauth2&type=Date&theme=dark" />
655
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=oauth-xx/oauth2&type=Date" />
656
- <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=oauth-xx/oauth2&type=Date" />
1356
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ruby-oauth/oauth2&type=Date&theme=dark" />
1357
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ruby-oauth/oauth2&type=Date" />
1358
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ruby-oauth/oauth2&type=Date" />
657
1359
  </picture>
658
1360
  </a>
659
1361
 
1362
+ </details>
1363
+
660
1364
  ## 📌 Versioning
661
1365
 
662
1366
  This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
@@ -665,34 +1369,35 @@ Specifically, if a minor or patch version is released that breaks backward compa
665
1369
  a new version should be immediately released that restores compatibility.
666
1370
  Breaking changes to the public API will only be introduced with new major versions.
667
1371
 
668
- ### 📌 Is "Platform Support" part of the public API?
1372
+ > dropping support for a platform is both obviously and objectively a breaking change <br/>
1373
+ >—Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
669
1374
 
670
- Yes. But I'm obligated to include notes...
1375
+ I understand that policy doesn't work universally ("exceptions to every rule!"),
1376
+ but it is the policy here.
1377
+ As such, in many cases it is good to specify a dependency on this library using
1378
+ the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
671
1379
 
672
- SemVer should, but doesn't explicitly, say that dropping support for specific Platforms
673
- is a *breaking change* to an API.
674
- It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless.
1380
+ For example:
1381
+
1382
+ ```ruby
1383
+ spec.add_dependency("oauth2", "~> 2.0")
1384
+ ```
675
1385
 
676
- > dropping support for a platform is both obviously and objectively a breaking change
1386
+ <details>
1387
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
677
1388
 
678
- - Jordan Harband (@ljharb, maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
1389
+ SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
1390
+ is a *breaking change* to an API.
1391
+ It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless.
679
1392
 
680
1393
  To get a better understanding of how SemVer is intended to work over a project's lifetime,
681
1394
  read this article from the creator of SemVer:
682
1395
 
683
1396
  - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
684
1397
 
685
- As a result of this policy, and the interpretive lens used by the maintainer,
686
- you can (and should) specify a dependency on these libraries using
687
- the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
688
-
689
- For example:
690
-
691
- ```ruby
692
- spec.add_dependency("oauth2", "~> 2.0")
693
- ```
1398
+ </details>
694
1399
 
695
- See [CHANGELOG.md][📌changelog] for list of releases.
1400
+ See [CHANGELOG.md][📌changelog] for a list of releases.
696
1401
 
697
1402
  ## 📄 License
698
1403
 
@@ -700,59 +1405,102 @@ The gem is available as open source under the terms of
700
1405
  the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
701
1406
  See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
702
1407
 
703
- [![FOSSA Status][fossa2-img])][fossa2]
704
-
705
- [fossa2]: https://app.fossa.io/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2?ref=badge_large
706
- [fossa2-img]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2.svg?type=large
707
-
708
1408
  ### © Copyright
709
1409
 
710
1410
  <ul>
711
1411
  <li>
712
- 2017 - 2025 Peter H. Boling, of
713
- <a href="https://railsbling.com">
714
- RailsBling.com
1412
+ Copyright (c) 2017–2025 Peter H. Boling, of
1413
+ <a href="https://discord.gg/3qme4XHNKN">
1414
+ Galtzo.com
715
1415
  <picture>
716
- <img alt="Rails Bling" height="20" src="https://railsbling.com/images/logos/RailsBling-TrainLogo.svg" />
1416
+ <img src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px-blank.svg" alt="Galtzo.com Logo (Wordless) by Aboling0, CC BY-SA 4.0" width="24">
717
1417
  </picture>
718
- </a>, and oauth2 contributors
1418
+ </a>, and oauth2 contributors.
719
1419
  </li>
720
1420
  <li>
721
- Copyright (c) 2011 - 2013 Michael Bleigh and Intridea, Inc.
1421
+ Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc.
722
1422
  </li>
723
1423
  </ul>
724
1424
 
725
- ## 🤑 One more thing
1425
+ ## 🤑 A request for help
1426
+
1427
+ Maintainers have teeth and need to pay their dentists.
1428
+ After getting laid off in an RIF in March and filled with many dozens of rejections,
1429
+ I'm now spending ~60+ hours a week building open source tools.
1430
+ I'm hoping to be able to pay for my kids' health insurance this month,
1431
+ so if you value the work I am doing, I need your support.
1432
+ Please consider sponsoring me or the project.
1433
+
1434
+ To join the community or get help 👇️ Join the Discord.
1435
+
1436
+ [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
726
1437
 
727
- You made it to the bottom of the page,
728
- so perhaps you'll indulge me for another 20 seconds.
729
- I maintain many dozens of gems, including this one,
730
- because I want Ruby to be a great place for people to solve problems, big and small.
731
- Please consider supporting my efforts via the giant yellow link below,
732
- or one of the others at the head of this README.
1438
+ To say "thanks!" ☝️ Join the Discord or 👇️ send money.
733
1439
 
734
- [![Buy me a latte][🖇buyme-img]][🖇buyme]
1440
+ [![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]
1441
+
1442
+ ### Please give the project a star ⭐ ♥.
1443
+
1444
+ Thanks for RTFM. ☺️
1445
+
1446
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
1447
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
1448
+ [⛳liberapay]: https://liberapay.com/pboling/donate
1449
+ [🖇osc-all-img]: https://img.shields.io/opencollective/all/ruby-oauth
1450
+ [🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/ruby-oauth
1451
+ [🖇osc-backers-img]: https://img.shields.io/opencollective/backers/ruby-oauth
1452
+ [🖇osc-backers]: https://opencollective.com/ruby-oauth#backer
1453
+ [🖇osc-backers-i]: https://opencollective.com/ruby-oauth/backers/badge.svg?style=flat
1454
+ [🖇osc-sponsors]: https://opencollective.com/ruby-oauth#sponsor
1455
+ [🖇osc-sponsors-i]: https://opencollective.com/ruby-oauth/sponsors/badge.svg?style=flat
1456
+ [🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/ruby-oauth?style=for-the-badge
1457
+ [🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/ruby-oauth?style=for-the-badge
1458
+ [🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/ruby-oauth?style=for-the-badge
1459
+ [🖇osc]: https://opencollective.com/ruby-oauth
1460
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
1461
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
1462
+ [🖇sponsor]: https://github.com/sponsors/pboling
1463
+ [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
1464
+ [🖇polar]: https://polar.sh/pboling
1465
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
1466
+ [🖇kofi]: https://ko-fi.com/O5O86SNP4
1467
+ [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
1468
+ [🖇patreon]: https://patreon.com/galtzo
1469
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
1470
+ [🖇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
1471
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
1472
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
1473
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
1474
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
1475
+ [🖇floss-funding.dev]: https://floss-funding.dev
1476
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
1477
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
1478
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
1479
+ [✉️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
1480
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
735
1481
 
736
1482
  [⛳gg-discussions]: https://groups.google.com/g/oauth-ruby
737
- [⛳gg-discussions-img]: https://img.shields.io/badge/google-group-purple.svg?style=flat
1483
+ [⛳gg-discussions-img]: https://img.shields.io/badge/google-group-0093D0.svg?style=for-the-badge&logo=google&logoColor=orange
738
1484
 
739
1485
  [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
740
- [⛳️gem-namespace]: https://github.com/oauth-xx/oauth2
741
- [⛳️namespace-img]: https://img.shields.io/badge/namespace-OAuth2-brightgreen.svg?style=flat&logo=ruby&logoColor=white
1486
+ [⛳️gem-namespace]: https://github.com/ruby-oauth/oauth2
1487
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-OAuth2-3C2D2D.svg?style=square&logo=ruby&logoColor=white
742
1488
  [⛳️gem-name]: https://rubygems.org/gems/oauth2
743
- [⛳️name-img]: https://img.shields.io/badge/name-oauth2-brightgreen.svg?style=flat&logo=rubygems&logoColor=red
744
- [🚂bdfl-blog]: http://www.railsbling.com/tags/oauth2
745
- [🚂bdfl-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
746
- [🚂bdfl-contact]: http://www.railsbling.com/contact
747
- [🚂bdfl-contact-img]: https://img.shields.io/badge/Contact-BDFL-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
1489
+ [⛳️name-img]: https://img.shields.io/badge/name-oauth2-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
1490
+ [⛳️tag-img]: https://img.shields.io/github/tag/ruby-oauth/oauth2.svg
1491
+ [⛳️tag]: http://github.com/ruby-oauth/oauth2/releases
1492
+ [🚂maint-blog]: http://www.railsbling.com/tags/oauth2
1493
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
1494
+ [🚂maint-contact]: http://www.railsbling.com/contact
1495
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
748
1496
  [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
749
1497
  [💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling
750
- [💖✌️wellfound]: https://angel.co/u/peter-boling
1498
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
751
1499
  [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
752
1500
  [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
753
1501
  [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
754
1502
  [💖🐘ruby-mast]: https://ruby.social/@galtzo
755
- [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https%3A%2F%2Fruby.social&style=flat&logo=mastodon&label=Ruby%20%40galtzo
1503
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
756
1504
  [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
757
1505
  [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
758
1506
  [💖🌳linktree]: https://linktr.ee/galtzo
@@ -769,76 +1517,70 @@ or one of the others at the head of this README.
769
1517
  [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
770
1518
  [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
771
1519
  [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
772
- [🏙️entsup-tidelift]: https://tidelift.com/subscription
1520
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=readme
773
1521
  [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
774
1522
  [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
775
1523
  [💁🏼‍♂️peterboling]: http://www.peterboling.com
776
1524
  [🚂railsbling]: http://www.railsbling.com
777
1525
  [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
778
- [📜src-gl]: https://gitlab.com/oauth-xx/oauth2/
1526
+ [📜src-gl]: https://gitlab.com/ruby-oauth/oauth2/
779
1527
  [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
780
- [📜src-cb]: https://codeberg.org/oauth-xx/oauth2
1528
+ [📜src-cb]: https://codeberg.org/ruby-oauth/oauth2
781
1529
  [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
782
- [📜src-gh]: https://github.com/oauth-xx/oauth2
1530
+ [📜src-gh]: https://github.com/ruby-oauth/oauth2
783
1531
  [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
784
- [📜docs-head-rd-img]: https://img.shields.io/badge/RubyDoc-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
785
- [📜wiki]: https://gitlab.com/oauth-xx/oauth2/-/wikis/home
1532
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
1533
+ [📜wiki]: https://gitlab.com/ruby-oauth/oauth2/-/wikis/home
786
1534
  [📜wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=Wiki&logoColor=white
787
1535
  [👽dl-rank]: https://rubygems.org/gems/oauth2
788
1536
  [👽dl-ranki]: https://img.shields.io/gem/rd/oauth2.svg
789
- [👽oss-help]: https://www.codetriage.com/oauth-xx/oauth2
790
- [👽oss-helpi]: https://www.codetriage.com/oauth-xx/oauth2/badges/users.svg
1537
+ [👽oss-help]: https://www.codetriage.com/ruby-oauth/oauth2
1538
+ [👽oss-helpi]: https://www.codetriage.com/ruby-oauth/oauth2/badges/users.svg
791
1539
  [👽version]: https://rubygems.org/gems/oauth2
792
1540
  [👽versioni]: https://img.shields.io/gem/v/oauth2.svg
793
- [🔑cc-mnt]: https://qlty.sh/gh/oauth-xx/projects/oauth2
794
- [🔑cc-mnti♻️]: https://qlty.sh/badges/d3370c2c-8791-4202-9759-76f527f76005/maintainability.svg
795
- [🔑cc-cov]: https://qlty.sh/gh/oauth-xx/projects/oauth2
796
- [🔑cc-covi♻️]: https://qlty.sh/badges/d3370c2c-8791-4202-9759-76f527f76005/test_coverage.svg
797
- [🔑codecov]: https://codecov.io/gh/oauth-xx/oauth2
798
- [🔑codecovi♻️]: https://codecov.io/gh/oauth-xx/oauth2/graph/badge.svg?token=bNqSzNiuo2
799
- [🔑coveralls]: https://coveralls.io/github/oauth-xx/oauth2?branch=main
800
- [🔑coveralls-img]: https://coveralls.io/repos/github/oauth-xx/oauth2/badge.svg?branch=main
801
- [🔑depfu]: https://depfu.com/github/oauth-xx/oauth2?project_id=5884
802
- [🔑depfui♻️]: https://badges.depfu.com/badges/6d34dc1ba682bbdf9ae2a97848241743/count.svg
803
- [🖐codeQL]: https://github.com/oauth-xx/oauth2/security/code-scanning
804
- [🖐codeQL-img]: https://github.com/oauth-xx/oauth2/actions/workflows/codeql-analysis.yml/badge.svg
805
- [🚎1-an-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/ancient.yml
806
- [🚎1-an-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/ancient.yml/badge.svg
807
- [🚎2-cov-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/coverage.yml
808
- [🚎2-cov-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/coverage.yml/badge.svg
809
- [🚎3-hd-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/heads.yml
810
- [🚎3-hd-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/heads.yml/badge.svg
811
- [🚎4-lg-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/legacy.yml
812
- [🚎4-lg-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/legacy.yml/badge.svg
813
- [🚎5-st-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/style.yml
814
- [🚎5-st-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/style.yml/badge.svg
815
- [🚎6-s-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/supported.yml
816
- [🚎6-s-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/supported.yml/badge.svg
817
- [🚎7-us-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/unsupported.yml
818
- [🚎7-us-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/unsupported.yml/badge.svg
819
- [🚎8-ho-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/hoary.yml
820
- [🚎8-ho-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/hoary.yml/badge.svg
821
- [🚎9-t-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/truffle.yml
822
- [🚎9-t-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/truffle.yml/badge.svg
823
- [🚎10-j-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/jruby.yml
824
- [🚎10-j-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/jruby.yml/badge.svg
825
- [🚎11-c-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/current.yml
826
- [🚎11-c-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/current.yml/badge.svg
827
- [🚎12-crh-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/current-runtime-heads.yml
828
- [🚎12-crh-wfi]: https://github.com/oauth-xx/oauth2/actions/workflows/current-runtime-heads.yml/badge.svg
829
- [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay
830
- [⛳liberapay]: https://liberapay.com/pboling/donate
831
- [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
832
- [🖇sponsor]: https://github.com/sponsors/pboling
833
- [🖇polar-img]: https://img.shields.io/badge/polar-donate-yellow.svg
834
- [🖇polar]: https://polar.sh/pboling
835
- [🖇kofi-img]: https://img.shields.io/badge/a_more_different_coffee-✓-yellow.svg
836
- [🖇kofi]: https://ko-fi.com/O5O86SNP4
837
- [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-yellow.svg
838
- [🖇patreon]: https://patreon.com/galtzo
839
- [🖇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
840
- [🖇buyme]: https://www.buymeacoffee.com/pboling
841
- [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-✓-yellow.svg?style=flat
1541
+ [🔑qlty-mnt]: https://qlty.sh/gh/ruby-oauth/projects/oauth2
1542
+ [🔑qlty-mnti]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/maintainability.svg
1543
+ [🔑qlty-cov]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/metrics/code?sort=coverageRating
1544
+ [🔑qlty-covi]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/coverage.svg
1545
+ [🔑codecov]: https://codecov.io/gh/ruby-oauth/oauth2
1546
+ [🔑codecovi]: https://codecov.io/gh/ruby-oauth/oauth2/graph/badge.svg
1547
+ [🔑coveralls]: https://coveralls.io/github/ruby-oauth/oauth2?branch=main
1548
+ [🔑coveralls-img]: https://coveralls.io/repos/github/ruby-oauth/oauth2/badge.svg?branch=main
1549
+ [🖐codeQL]: https://github.com/ruby-oauth/oauth2/security/code-scanning
1550
+ [🖐codeQL-img]: https://github.com/ruby-oauth/oauth2/actions/workflows/codeql-analysis.yml/badge.svg
1551
+ [🚎1-an-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ancient.yml
1552
+ [🚎1-an-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/ancient.yml/badge.svg
1553
+ [🚎2-cov-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/coverage.yml
1554
+ [🚎2-cov-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/coverage.yml/badge.svg
1555
+ [🚎3-hd-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/heads.yml
1556
+ [🚎3-hd-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/heads.yml/badge.svg
1557
+ [🚎4-lg-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/legacy.yml
1558
+ [🚎4-lg-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/legacy.yml/badge.svg
1559
+ [🚎5-st-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/style.yml
1560
+ [🚎5-st-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/style.yml/badge.svg
1561
+ [🚎6-s-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/supported.yml
1562
+ [🚎6-s-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/supported.yml/badge.svg
1563
+ [🚎7-us-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/unsupported.yml
1564
+ [🚎7-us-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/unsupported.yml/badge.svg
1565
+ [🚎8-ho-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/hoary.yml
1566
+ [🚎8-ho-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/hoary.yml/badge.svg
1567
+ [🚎9-t-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml
1568
+ [🚎9-t-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml/badge.svg
1569
+ [🚎10-j-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml
1570
+ [🚎10-j-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml/badge.svg
1571
+ [🚎11-c-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml
1572
+ [🚎11-c-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml/badge.svg
1573
+ [🚎12-crh-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/dep-heads.yml
1574
+ [🚎12-crh-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/dep-heads.yml/badge.svg
1575
+ [🚎13-cbs-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/caboose.yml
1576
+ [🚎13-cbs-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/caboose.yml/badge.svg
1577
+ [🚎13-🔒️-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/locked_deps.yml
1578
+ [🚎13-🔒️-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/locked_deps.yml/badge.svg
1579
+ [🚎14-🔓️-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/unlocked_deps.yml
1580
+ [🚎14-🔓️-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/unlocked_deps.yml/badge.svg
1581
+ [🚎15-🪪-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml
1582
+ [🚎15-🪪-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml/badge.svg
1583
+ [💎ruby-2.2i]: https://img.shields.io/badge/Ruby-2.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
842
1584
  [💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
843
1585
  [💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
844
1586
  [💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
@@ -850,55 +1592,62 @@ or one of the others at the head of this README.
850
1592
  [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
851
1593
  [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
852
1594
  [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
853
- [💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
854
- [💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
1595
+ [💎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
1596
+ [💎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
855
1597
  [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
856
1598
  [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
857
1599
  [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
858
- [💎jruby-9.1i]: https://img.shields.io/badge/JRuby-9.1-FBE742?style=for-the-badge&logo=ruby&logoColor=red
859
- [💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2-FBE742?style=for-the-badge&logo=ruby&logoColor=red
860
- [💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3-FBE742?style=for-the-badge&logo=ruby&logoColor=red
1600
+ [💎jruby-9.1i]: https://img.shields.io/badge/JRuby-9.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red
1601
+ [💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red
1602
+ [💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red
861
1603
  [💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red
862
1604
  [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
863
1605
  [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
864
- [🤝issues]: https://github.com/oauth-xx/oauth2/issues
865
- [🤝pulls]: https://github.com/oauth-xx/oauth2/pulls
1606
+ [🤝gh-issues]: https://github.com/ruby-oauth/oauth2/issues
1607
+ [🤝gh-pulls]: https://github.com/ruby-oauth/oauth2/pulls
1608
+ [🤝gl-issues]: https://gitlab.com/ruby-oauth/oauth2/-/issues
1609
+ [🤝gl-pulls]: https://gitlab.com/ruby-oauth/oauth2/-/merge_requests
1610
+ [🤝cb-issues]: https://codeberg.org/ruby-oauth/oauth2/issues
1611
+ [🤝cb-pulls]: https://codeberg.org/ruby-oauth/oauth2/pulls
1612
+ [🤝cb-donate]: https://donate.codeberg.org/
866
1613
  [🤝contributing]: CONTRIBUTING.md
867
- [🔑codecov-g♻️]: https://codecov.io/gh/oauth-xx/oauth2/graphs/tree.svg?token=bNqSzNiuo2
1614
+ [🔑codecov-g]: https://codecov.io/gh/ruby-oauth/oauth2/graphs/tree.svg
868
1615
  [🖐contrib-rocks]: https://contrib.rocks
869
- [🖐contributors]: https://github.com/oauth-xx/oauth2/graphs/contributors
870
- [🖐contributors-img]: https://contrib.rocks/image?repo=oauth-xx/oauth2
871
- [🚎contributors-gl]: https://gitlab.com/oauth-xx/oauth2/-/graphs/main
1616
+ [🖐contributors]: https://github.com/ruby-oauth/oauth2/graphs/contributors
1617
+ [🖐contributors-img]: https://contrib.rocks/image?repo=ruby-oauth/oauth2
1618
+ [🚎contributors-gl]: https://gitlab.com/ruby-oauth/oauth2/-/graphs/main
872
1619
  [🪇conduct]: CODE_OF_CONDUCT.md
873
- [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-4baaaa.svg
1620
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
874
1621
  [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
875
1622
  [📌semver]: https://semver.org/spec/v2.0.0.html
876
- [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-FFDD67.svg?style=flat
1623
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
877
1624
  [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
878
1625
  [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
879
1626
  [📌changelog]: CHANGELOG.md
880
1627
  [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
881
- [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-FFDD67.svg?style=flat
1628
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
882
1629
  [📌gitmoji]:https://gitmoji.dev
883
- [📌gitmoji-img]:https://img.shields.io/badge/gitmoji-%20😜%20😍-FFDD67.svg?style=flat-square
1630
+ [📌gitmoji-img]:https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
884
1631
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
885
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.518-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1632
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.526-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
886
1633
  [🔐security]: SECURITY.md
887
- [🔐security-img]: https://img.shields.io/badge/security-policy-brightgreen.svg?style=flat
1634
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
888
1635
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
889
1636
  [📄license]: LICENSE.txt
890
1637
  [📄license-ref]: https://opensource.org/licenses/MIT
891
- [📄license-img]: https://img.shields.io/badge/License-MIT-green.svg
1638
+ [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
1639
+ [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
1640
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
892
1641
  [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
893
- [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-brightgreen.svg?style=flat
1642
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
894
1643
  [🚎yard-current]: http://rubydoc.info/gems/oauth2
895
- [🚎yard-head]: https://rubydoc.info/github/oauth-xx/oauth2/main
896
- [💎stone_checksums]: https://github.com/pboling/stone_checksums
897
- [💎SHA_checksums]: https://gitlab.com/oauth-xx/oauth2/-/tree/main/checksums
1644
+ [🚎yard-head]: https://oauth2.galtzo.com
1645
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
1646
+ [💎SHA_checksums]: https://gitlab.com/ruby-oauth/oauth2/-/tree/main/checksums
898
1647
  [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
899
- [💎rlts-img]: https://img.shields.io/badge/code_style-rubocop--lts-brightgreen.svg?plastic&logo=ruby&logoColor=white
900
- [🏘fossa]: https://app.fossa.io/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2?ref=badge_shield
901
- [🏘fossa-img]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2.svg?type=shield
1648
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
1649
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
1650
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
902
1651
  [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
903
1652
 
904
1653
  <details>
@@ -906,17 +1655,16 @@ or one of the others at the head of this README.
906
1655
  rel="me" Social Proofs
907
1656
  </summary>
908
1657
 
909
- <a rel="me" alt="Follow me on Ruby.social" href="https://ruby.social/@galtzo"><img src="https://img.shields.io/mastodon/follow/109447111526622197?domain=https%3A%2F%2Fruby.social&style=social&label=Follow%20%40galtzo%20on%20Ruby.social"></a>
910
- <a rel="me" alt="Follow me on FLOSS.social" href="https://floss.social/@galtzo"><img src="https://img.shields.io/mastodon/follow/110304921404405715?domain=https%3A%2F%2Ffloss.social&style=social&label=Follow%20%40galtzo%20on%20Floss.social"></a>
911
- </details>
1658
+ <a rel="me" alt="Follow me on Ruby.social" href="https://ruby.social/@galtzo"><img src="https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=social&label=Follow%20@galtzo%20on%20Ruby.social"></a>
1659
+ <a rel="me" alt="Follow me on FLOSS.social" href="https://floss.social/@galtzo"><img src="https://img.shields.io/mastodon/follow/110304921404405715?domain=https://floss.social&style=social&label=Follow%20@galtzo%20on%20Floss.social"></a>
912
1660
 
913
- <details>
914
- <summary>Deprecated Badges</summary>
1661
+ </details>
915
1662
 
916
- CodeCov currently fails to parse the coverage upload.
917
1663
 
918
- [![CodeCov Test Coverage][🔑codecovi♻️]][🔑codecov]
1664
+ <details>
1665
+ <summary>Broken badges</summary>
919
1666
 
920
- [![Coverage Graph][🔑codecov-g♻️]][🔑codecov]
1667
+ [![Coverage Graph][🔑codecov-g]][🔑codecov]
1668
+ [![CodeCov Test Coverage][🔑codecovi]][🔑codecov]
921
1669
 
922
- </details>
1670
+ </details>