oauth2 2.0.9 → 2.0.25

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,346 +1,312 @@
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>
1
+ <a href="https://github.com/ruby-oauth"><img alt="ruby-oauth Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/ruby-oauth/avatar-128px.svg" width="14%" align="right"/></a>
9
2
 
10
- ## What
3
+ # 🔐 OAuth2
11
4
 
12
- OAuth 2.0 is the industry-standard protocol for authorization.
13
- OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications,
14
- desktop applications, mobile phones, and living room devices.
15
- This is a RubyGem for implementing OAuth 2.0 clients and servers in Ruby applications.
16
- See the sibling `oauth` gem for OAuth 1.0 implementations in Ruby.
5
+ [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![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 Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
6
+
7
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][✉️discord-invite], as I may have missed the [discord notification][✉️discord-invite].
17
8
 
18
9
  ---
19
10
 
20
- * [OAuth 2.0 Spec][oauth2-spec]
21
- * [oauth sibling gem][sibling-gem] for OAuth 1.0 implementations in Ruby.
11
+ `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.
22
12
 
23
- [oauth2-spec]: https://oauth.net/2/
24
- [sibling-gem]: https://gitlab.com/oauth-xx/oauth
25
-
26
- ## Release Documentation
27
-
28
- ### Version 2.0.x
29
-
30
- <details>
31
- <summary>2.0.x Readmes</summary>
32
-
33
- | Version | Release Date | Readme |
34
- |---------|--------------|----------------------------------------------------------|
35
- | 2.0.9 | 2022-09-16 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.9/README.md |
36
- | 2.0.8 | 2022-09-01 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.8/README.md |
37
- | 2.0.7 | 2022-08-22 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.7/README.md |
38
- | 2.0.6 | 2022-07-13 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.6/README.md |
39
- | 2.0.5 | 2022-07-07 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.5/README.md |
40
- | 2.0.4 | 2022-07-01 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.4/README.md |
41
- | 2.0.3 | 2022-06-28 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.3/README.md |
42
- | 2.0.2 | 2022-06-24 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.2/README.md |
43
- | 2.0.1 | 2022-06-22 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.1/README.md |
44
- | 2.0.0 | 2022-06-21 | https://gitlab.com/oauth-xx/oauth2/-/blob/v2.0.0/README.md |
45
- </details>
13
+ [![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 at ko-fi.com][🖇kofi-img]][🖇kofi]
14
+
15
+ <details markdown="1">
16
+ <summary>👣 How will this project approach the September 2025 hostile takeover of RubyGems? 🚑️</summary>
17
+
18
+ I've summarized my thoughts in [this blog post](https://dev.to/galtzo/hostile-takeover-of-rubygems-my-thoughts-5hlo).
46
19
 
47
- ### Older Releases
48
-
49
- <details>
50
- <summary>1.4.x Readmes</summary>
51
-
52
- | Version | Release Date | Readme |
53
- |---------|--------------|-------------------------------------------------------------|
54
- | 1.4.11 | Sep 16, 2022 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.11/README.md |
55
- | 1.4.10 | Jul 1, 2022 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.10/README.md |
56
- | 1.4.9 | Feb 20, 2022 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.9/README.md |
57
- | 1.4.8 | Feb 18, 2022 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.8/README.md |
58
- | 1.4.7 | Mar 19, 2021 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.7/README.md |
59
- | 1.4.6 | Mar 19, 2021 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.6/README.md |
60
- | 1.4.5 | Mar 18, 2021 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.5/README.md |
61
- | 1.4.4 | Feb 12, 2020 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.4/README.md |
62
- | 1.4.3 | Jan 29, 2020 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.3/README.md |
63
- | 1.4.2 | Oct 1, 2019 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.2/README.md |
64
- | 1.4.1 | Oct 13, 2018 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.1/README.md |
65
- | 1.4.0 | Jun 9, 2017 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.4.0/README.md |
66
20
  </details>
67
21
 
68
- <details>
69
- <summary>1.3.x Readmes</summary>
22
+ ## 🌻 Synopsis <a href="https://discord.gg/3qme4XHNKN"><img alt="Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px.svg" width="8%" align="right"/></a> <a href="https://ruby-toolbox.com"><img alt="ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5" src="https://logos.galtzo.com/assets/images/ruby-lang/avatar-128px.svg" width="8%" align="right"/></a>
23
+
24
+ OAuth 2.0 is the industry-standard protocol for authorization.
25
+ This is a RubyGem for implementing OAuth 2.0 clients (not servers) in Ruby applications.
26
+
27
+ ⭐️ including OAuth 2.1 draft spec & OpenID Connect (OIDC)
28
+
29
+ ### Quick Examples
30
+
31
+ <details markdown="1">
32
+ <summary>Convert the following `curl` command into a token request using this gem...</summary>
33
+
34
+ ```shell
35
+ curl --request POST \
36
+ --url 'https://login.microsoftonline.com/REDMOND_REDACTED/oauth2/token' \
37
+ --header 'content-type: application/x-www-form-urlencoded' \
38
+ --data grant_type=client_credentials \
39
+ --data client_id=REDMOND_CLIENT_ID \
40
+ --data client_secret=REDMOND_CLIENT_SECRET \
41
+ --data resource=REDMOND_RESOURCE_UUID
42
+ ```
43
+
44
+ NOTE: In the ruby version below, certain params are passed to the `get_token` call, instead of the client creation.
45
+
46
+ ```ruby
47
+ client = OAuth2::Client.new(
48
+ "REDMOND_CLIENT_ID", # client_id
49
+ "REDMOND_CLIENT_SECRET", # client_secret
50
+ auth_scheme: :request_body, # Other modes are supported: :basic_auth, :tls_client_auth, :private_key_jwt
51
+ token_url: "oauth2/token", # relative path, except with leading `/`, then absolute path
52
+ site: "https://login.microsoftonline.com/REDMOND_REDACTED"
53
+ )
54
+ client.
55
+ client_credentials. # There are many other types to choose from!
56
+ get_token(resource: "REDMOND_RESOURCE_UUID")
57
+ ```
58
+
59
+ NOTE: `header` - The content type specified in the `curl` is already the default!
70
60
 
71
- | Version | Release Date | Readme |
72
- |----------|--------------|----------------------------------------------------------|
73
- | 1.3.1 | Mar 3, 2017 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.3.1/README.md |
74
- | 1.3.0 | Dec 27, 2016 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.3.0/README.md |
75
61
  </details>
76
62
 
77
- <details>
78
- <summary>&le;= 1.2.x Readmes (2016 and before)</summary>
63
+ <details markdown="1">
64
+ <summary>Complete E2E single file script against mock-oauth2-server</summary>
65
+
66
+ - E2E example uses [navikt/mock-oauth2-server](https://github.com/navikt/mock-oauth2-server), which was added in v2.0.11
67
+ - E2E example does not ship with the released gem, so clone the source to play with it.
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
+
113
+ - Discovery URL pattern is: `http://localhost:8080/<realm>/.well-known/openid-configuration`, where `<realm>` defaults to `default`.
114
+ - You can change these with env vars when running the example:
115
+ - `E2E_ISSUER_BASE` (default: http://localhost:8080)
116
+ - `E2E_REALM` (default: default)
79
117
 
80
- | Version | Release Date | Readme |
81
- |----------|--------------|----------------------------------------------------------|
82
- | 1.2.0 | Jun 30, 2016 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.2.0/README.md |
83
- | 1.1.0 | Jan 30, 2016 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.1.0/README.md |
84
- | 1.0.0 | May 23, 2014 | https://gitlab.com/oauth-xx/oauth2/-/blob/v1.0.0/README.md |
85
- | < 1.0.0 | Find here | https://gitlab.com/oauth-xx/oauth2/-/tags |
86
118
  </details>
87
119
 
88
- ## Status
89
-
90
- <!--
91
- Numbering rows and badges in each row as a visual "database" lookup,
92
- as the table is extremely dense, and it can be very difficult to find anything
93
- Putting one on each row here, to document the emoji that should be used, and for ease of copy/paste.
94
-
95
- row #s:
96
- 1️⃣
97
- 2️⃣
98
- 3️⃣
99
- 4️⃣
100
- 5️⃣
101
- 6️⃣
102
- 7️⃣
103
-
104
- badge #s:
105
- ⛳️
106
- 🖇
107
- 🏘
108
- 🚎
109
- 🖐
110
- 🧮
111
- 📗
112
-
113
- appended indicators:
114
- ♻️ - URL needs to be updated from SASS integration. Find / Replace is insufficient.
115
- -->
116
-
117
- | | Project | bundle add oauth2 |
118
- |:----|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
119
- | 1️⃣ | name, license, docs | [![RubyGems.org][⛳️name-img]][⛳️gem] [![License: MIT][🖇src-license-img]][🖇src-license] [![FOSSA][🏘fossa-img]][🏘fossa] [![RubyDoc.info][🚎yard-img]][🚎yard] [![InchCI][🖐inch-ci-img]][🚎yard] |
120
- | 2️⃣ | version & activity | [![Gem Version][⛳️version-img]][⛳️gem] [![Total Downloads][🖇DL-total-img]][⛳️gem] [![Download Rank][🏘DL-rank-img]][⛳️gem] [![Source Code][🚎src-home-img]][🚎src-home] |
121
- | 3️⃣ | maintanence & linting | [![Maintainability][⛳cclim-maint-img♻️]][⛳cclim-maint] [![Helpers][🖇triage-help-img]][🖇triage-help] [![Depfu][🏘depfu-img♻️]][🏘depfu♻️] [![Contributors][🚎contributors-img]][🚎contributors] [![Style][🖐style-wf-img]][🖐style-wf] [![Kloc Roll][🧮kloc-img]][🧮kloc] |
122
- | 4️⃣ | testing | [![Supported][🏘sup-wf-img]][🏘sup-wf] [![Heads][🚎heads-wf-img]][🚎heads-wf] [![Unofficial Support][🖐uns-wf-img]][🖐uns-wf] [![MacOS][🧮mac-wf-img]][🧮mac-wf] [![Windows][📗win-wf-img]][📗win-wf] |
123
- | 5️⃣ | coverage & security | [![CodeClimate][⛳cclim-cov-img♻️]][⛳cclim-cov] [![CodeCov][🖇codecov-img♻️]][🖇codecov] [![Coveralls][🏘coveralls-img]][🏘coveralls] [![Security Policy][🚎sec-pol-img]][🚎sec-pol] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Code Coverage][🧮cov-wf-img]][🧮cov-wf] |
124
- | 6️⃣ | resources | [![Discussion][⛳gg-discussions-img]][⛳gg-discussions] [![Get help on Codementor][🖇codementor-img]][🖇codementor] [![Chat][🏘chat-img]][🏘chat] [![Blog][🚎blog-img]][🚎blog] [![Blog][🖐wiki-img]][🖐wiki] |
125
- | 7️⃣ | spread 💖 | [![Liberapay Patrons][⛳liberapay-img]][⛳liberapay] [![Sponsor Me][🖇sponsor-img]][🖇sponsor] [![Tweet @ Peter][🏘tweet-img]][🏘tweet] [🌏][aboutme] [👼][angelme] [💻][coderme] |
126
-
127
- <!--
128
- The link tokens in the following sections should be kept ordered by the row and badge numbering scheme
129
- -->
130
-
131
- <!-- 1️⃣ name, license, docs -->
132
- [⛳️gem]: https://rubygems.org/gems/oauth2
133
- [⛳️name-img]: https://img.shields.io/badge/name-oauth2-brightgreen.svg?style=flat
134
- [🖇src-license]: https://opensource.org/licenses/MIT
135
- [🖇src-license-img]: https://img.shields.io/badge/License-MIT-green.svg
136
- [🏘fossa]: https://app.fossa.io/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2?ref=badge_shield
137
- [🏘fossa-img]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2.svg?type=shield
138
- [🚎yard]: https://www.rubydoc.info/github/oauth-xx/oauth2
139
- [🚎yard-img]: https://img.shields.io/badge/documentation-rubydoc-brightgreen.svg?style=flat
140
- [🖐inch-ci-img]: http://inch-ci.org/github/oauth-xx/oauth2.png
141
-
142
- <!-- 2️⃣ version & activity -->
143
- [⛳️version-img]: http://img.shields.io/gem/v/oauth2.svg
144
- [🖇DL-total-img]: https://img.shields.io/gem/dt/oauth2.svg
145
- [🏘DL-rank-img]: https://img.shields.io/gem/rt/oauth2.svg
146
- [🚎src-home]: https://gitlab.com/oauth-xx/oauth2/
147
- [🚎src-home-img]: https://img.shields.io/badge/source-gitlab-blue.svg?style=flat
148
-
149
- <!-- 3️⃣ maintenance & linting -->
150
- [⛳cclim-maint]: https://codeclimate.com/github/oauth-xx/oauth2/maintainability
151
- [⛳cclim-maint-img♻️]: https://api.codeclimate.com/v1/badges/688c612528ff90a46955/maintainability
152
- [🖇triage-help]: https://www.codetriage.com/oauth-xx/oauth2
153
- [🖇triage-help-img]: https://www.codetriage.com/oauth-xx/oauth2/badges/users.svg
154
- [🏘depfu♻️]: https://depfu.com/github/oauth-xx/oauth2?project_id=4445
155
- [🏘depfu-img♻️]: https://badges.depfu.com/badges/6d34dc1ba682bbdf9ae2a97848241743/count.svg
156
- [🚎contributors]: https://gitlab.com/oauth-xx/oauth2/-/graphs/main
157
- [🚎contributors-img]: https://img.shields.io/github/contributors-anon/oauth-xx/oauth2
158
- [🖐style-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/style.yml
159
- [🖐style-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/style.yml/badge.svg
160
- [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
161
- [🧮kloc-img]: https://img.shields.io/tokei/lines/github.com/oauth-xx/oauth2
162
-
163
- <!-- 4️⃣ testing -->
164
- [🏘sup-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/supported.yml
165
- [🏘sup-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/supported.yml/badge.svg
166
- [🚎heads-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/heads.yml
167
- [🚎heads-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/heads.yml/badge.svg
168
- [🖐uns-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/unsupported.yml
169
- [🖐uns-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/unsupported.yml/badge.svg
170
- [🧮mac-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/macos.yml
171
- [🧮mac-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/macos.yml/badge.svg
172
- [📗win-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/windows.yml
173
- [📗win-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/windows.yml/badge.svg
174
-
175
- <!-- 5️⃣ coverage & security -->
176
- [⛳cclim-cov]: https://codeclimate.com/github/oauth-xx/oauth2/test_coverage
177
- [⛳cclim-cov-img♻️]: https://api.codeclimate.com/v1/badges/688c612528ff90a46955/test_coverage
178
- [🖇codecov-img♻️]: https://codecov.io/gh/oauth-xx/oauth2/branch/main/graph/badge.svg?token=bNqSzNiuo2
179
- [🖇codecov]: https://codecov.io/gh/oauth-xx/oauth2
180
- [🏘coveralls]: https://coveralls.io/github/oauth-xx/oauth2?branch=main
181
- [🏘coveralls-img]: https://coveralls.io/repos/github/oauth-xx/oauth2/badge.svg?branch=main
182
- [🚎sec-pol]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/SECURITY.md
183
- [🚎sec-pol-img]: https://img.shields.io/badge/security-policy-brightgreen.svg?style=flat
184
- [🖐codeQL]: https://github.com/oauth-xx/oauth2/security/code-scanning
185
- [🖐codeQL-img]: https://github.com/oauth-xx/oauth2/actions/workflows/codeql-analysis.yml/badge.svg
186
- [🧮cov-wf]: https://github.com/oauth-xx/oauth2/actions/workflows/coverage.yml
187
- [🧮cov-wf-img]: https://github.com/oauth-xx/oauth2/actions/workflows/coverage.yml/badge.svg
188
-
189
- <!-- 6️⃣ resources -->
190
- [⛳gg-discussions]: https://groups.google.com/g/oauth-ruby
191
- [⛳gg-discussions-img]: https://img.shields.io/badge/google-group-purple.svg?style=flat
192
- [🖇codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
193
- [🖇codementor-img]: https://cdn.codementor.io/badges/get_help_github.svg
194
- [🏘chat]: https://gitter.im/oauth-xx/oauth2
195
- [🏘chat-img]: https://img.shields.io/gitter/room/oauth-xx/oauth2.svg
196
- [🚎blog]: http://www.railsbling.com/tags/oauth2/
197
- [🚎blog-img]: https://img.shields.io/badge/blog-railsbling-brightgreen.svg?style=flat
198
- [🖐wiki]: https://gitlab.com/oauth-xx/oauth2/-/wikis/home
199
- [🖐wiki-img]: https://img.shields.io/badge/wiki-examples-brightgreen.svg?style=flat
200
-
201
- <!-- 7️⃣ spread 💖 -->
202
- [⛳liberapay-img]: https://img.shields.io/liberapay/patrons/pboling.svg?logo=liberapay
203
- [⛳liberapay]: https://liberapay.com/pboling/donate
204
- [🖇sponsor-img]: https://img.shields.io/badge/sponsor-pboling.svg?style=social&logo=github
205
- [🖇sponsor]: https://github.com/sponsors/pboling
206
- [🏘tweet-img]: https://img.shields.io/twitter/follow/galtzo.svg?style=social&label=Follow
207
- [🏘tweet]: http://twitter.com/galtzo
120
+ ### Alternatives
208
121
 
209
- <!-- Maintainer Contact Links -->
210
- [railsbling]: http://www.railsbling.com
211
- [peterboling]: http://www.peterboling.com
212
- [aboutme]: https://about.me/peter.boling
213
- [angelme]: https://angel.co/peter-boling
214
- [coderme]:http://coderwall.com/pboling
122
+ This gem is a low-level OAuth 2.0 **client** (it talks _to_ an authorization server to obtain and use tokens).
123
+ If that isn't quite what you need, one of the following libraries may be a better fit (the first row is this gem for comparison):
215
124
 
216
- ## Installation
125
+ | Library | Role | When to use it |
126
+ |-------------------------------------------------------------------|------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
127
+ | **this gem** ([oauth2][📜src-gh]) | OAuth 2.0 / 2.1 + OIDC **client** | You are calling an OAuth 2.0 API, or signing in against a provider, and want a small, dependency-light, spec-faithful client with fine-grained control over the request/response cycle. |
128
+ | [oauth2-mcp][sibling-mcp-gem] | OAuth 2.1 MCP Auth client **and** server | Focused, spec-faithful, MCP Auth client with fine-grained control over the request/response cycle. Rack/Roda compatible clients and servers. Built on top of this gem, [oauth2][📜src-gh]. |
129
+ | [omniauth][omniauth-gem] + [omniauth-oauth2][omniauth-oauth2-gem] | "Log in with…" **client** (Rack) | You primarily want user _authentication_ ("Log in with GitHub/GitLab/Google") wired into a Rack/Rails app via a strategy, rather than driving the token flow yourself. Built on top of this gem, [oauth2][📜src-gh]. |
130
+ | [openid_connect][openid-connect-gem] | OpenID Connect client & server | You need full [OpenID Connect][oidc-spec] (ID-token validation, discovery, userinfo, etc.) with batteries included. Maintained by [@nov][nov]. |
131
+ | [rack-oauth2][rack-oauth2-gem] | OAuth 2.0 client **and** server | You want lower-level Rack primitives, need both client and server pieces, or are building on top of `openid_connect`. Maintained by [@nov][nov]. |
132
+ | [doorkeeper][doorkeeper-gem] | OAuth 2.0 **server / provider** | You want to _be_ the authorization server — issuing tokens to other apps — in a Rails/Grape/Sinatra application, rather than acting as a client. |
133
+ | [oauth][sibling-gem] | OAuth **1.0a** client & server | The provider you integrate with only speaks the older OAuth 1.0a protocol. This is our sibling gem. |
217
134
 
218
- Install the gem and add to the application's Gemfile by executing:
135
+ See also the [OAuth 2.0 Spec][oauth2-spec], the [OpenID Connect Spec][oidc-spec], and the [MCP Auth Spec][mcp-auth-spec].
219
136
 
220
- $ bundle add oauth2
137
+ [oauth2-spec]: https://oauth.net/2/
138
+ [oidc-spec]: https://openid.net/specs/openid-connect-core-1_0.html
139
+ [mcp-auth-spec]: https://modelcontextprotocol.io/specification/draft/basic/authorization
140
+ [sibling-gem]: https://gitlab.com/ruby-oauth/oauth
141
+ [sibling-mcp-gem]: https://github.com/ruby-oauth/oauth2-mcp
142
+ [doorkeeper-gem]: https://github.com/doorkeeper-gem/doorkeeper
143
+ [omniauth-gem]: https://github.com/omniauth/omniauth
144
+ [omniauth-oauth2-gem]: https://github.com/omniauth/omniauth-oauth2
145
+ [openid-connect-gem]: https://github.com/nov/openid_connect
146
+ [rack-oauth2-gem]: https://github.com/nov/rack-oauth2
147
+ [nov]: https://github.com/nov
148
+
149
+ ## 💡 Info you can shake a stick at
150
+
151
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
152
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
153
+ | Works with JRuby | [![JRuby 9.2 Compat][💎jruby-9.2i]][🚎jruby-9.2-wf] [![JRuby 9.3 Compat][💎jruby-9.3i]][🚎jruby-9.3-wf] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎jruby-9.4-wf] [![JRuby 10.0 Compat][💎jruby-10.0i]][🚎jruby-10.0-wf] [![JRuby current Compat][💎jruby-c-i]][🚎10-j-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf]|
154
+ | Works with Truffle Ruby | [![Truffle Ruby 22.3 Compat][💎truby-22.3i]][🚎truby-22.3-wf] [![Truffle Ruby 23.0 Compat][💎truby-23.0i]][🚎truby-23.0-wf] [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎truby-23.1-wf] <br/> [![Truffle Ruby 24.2 Compat][💎truby-24.2i]][🚎truby-24.2-wf] [![Truffle Ruby 25.0 Compat][💎truby-25.0i]][🚎truby-25.0-wf] [![Truffle Ruby 33.0 Compat][💎truby-33.0i]][🚎truby-33.0-wf] [![Truffle Ruby current Compat][💎truby-c-i]][🚎9-t-wf] [![Truffle Ruby HEAD Compat][💎truby-headi]][🚎3-hd-wf]|
155
+ | Works with MRI Ruby 4 | [![Ruby current Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf]|
156
+ | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎ruby-3.0-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎ruby-3.1-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎ruby-3.2-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎ruby-3.3-wf] [![Ruby 3.4 Compat][💎ruby-3.4i]][🚎ruby-3.4-wf]|
157
+ | Works with MRI Ruby 2 | ![Ruby 2.2 Compat][💎ruby-2.2i] ![Ruby 2.3 Compat][💎ruby-2.3i] <br/> [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎ruby-2.4-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎ruby-2.5-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎ruby-2.6-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎ruby-2.7-wf]|
158
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
159
+ | 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] |
160
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitLab Wiki][📜gl-wiki-img]][📜gl-wiki] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
161
+ | Compliance | [![License: MIT][📄license-img]][📄license] [![Apache license compatibility: Category A][📄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] |
162
+ | 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] |
163
+ | 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] |
164
+ | `...` 💖 | [![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] |
165
+
166
+ ### Compatibility
167
+
168
+ Compatible with MRI Ruby 2.2.0+, and concordant releases of JRuby, and TruffleRuby.
169
+ CI workflows and Appraisals are generated for MRI Ruby 2.4+.
170
+ This test floor is configured by `ruby.test_minimum` in `.kettle-jem.yml` and
171
+ may be higher than the gem's runtime compatibility floor when legacy Rubies are
172
+ not practical for the current toolchain.
173
+
174
+ <a href="https://github.com/kettle-dev"><img alt="kettle-dev Logo by Aboling0, CC BY-SA 4.0" src="https://logos.galtzo.com/assets/images/kettle-dev/avatar-128px.svg" width="14%" align="right"/></a>
175
+
176
+ The _amazing_ test matrix is powered by the kettle-dev stack.
177
+
178
+ <details markdown="1">
179
+ <summary>How kettle-dev manages complexity in tests</summary>
180
+
181
+ | Gem | Source | Role | Daily download rank |
182
+ |-----|--------|------|---------------------|
183
+ | [appraisal2](https://bestgems.org/gems/appraisal2) | [GitHub](https://github.com/appraisal-rb/appraisal2) | multi-dependency Appraisal matrix generation | [![Daily download rank for appraisal2](https://img.shields.io/gem/rd/appraisal2.svg?style=flat-square)](https://bestgems.org/gems/appraisal2) |
184
+ | [appraisal2-rubocop](https://bestgems.org/gems/appraisal2-rubocop) | [GitHub](https://github.com/appraisal-rb/appraisal2-rubocop) | RuboCop Appraisal generator integration | [![Daily download rank for appraisal2-rubocop](https://img.shields.io/gem/rd/appraisal2-rubocop.svg?style=flat-square)](https://bestgems.org/gems/appraisal2-rubocop) |
185
+ | [kettle-dev](https://bestgems.org/gems/kettle-dev) | [GitHub](https://github.com/kettle-dev/kettle-dev) | development, release, and CI workflow tooling | [![Daily download rank for kettle-dev](https://img.shields.io/gem/rd/kettle-dev.svg?style=flat-square)](https://bestgems.org/gems/kettle-dev) |
186
+ | [kettle-jem](https://bestgems.org/gems/kettle-jem) | [GitHub](https://github.com/kettle-dev/kettle-jem) | Appraisals & CI workflow templates | [![Daily download rank for kettle-jem](https://img.shields.io/gem/rd/kettle-jem.svg?style=flat-square)](https://bestgems.org/gems/kettle-jem) |
187
+ | [kettle-soup-cover](https://bestgems.org/gems/kettle-soup-cover) | [GitHub](https://github.com/kettle-dev/kettle-soup-cover) | SimpleCov coverage policy and reporting | [![Daily download rank for kettle-soup-cover](https://img.shields.io/gem/rd/kettle-soup-cover.svg?style=flat-square)](https://bestgems.org/gems/kettle-soup-cover) |
188
+ | [kettle-test](https://bestgems.org/gems/kettle-test) | [GitHub](https://github.com/kettle-dev/kettle-test) | standard test runner and coverage harness | [![Daily download rank for kettle-test](https://img.shields.io/gem/rd/kettle-test.svg?style=flat-square)](https://bestgems.org/gems/kettle-test) |
189
+ | [rubocop-lts](https://bestgems.org/gems/rubocop-lts) | [GitHub](https://github.com/rubocop-lts/rubocop-lts) | Ruby-version-aware linting | [![Daily download rank for rubocop-lts](https://img.shields.io/gem/rd/rubocop-lts.svg?style=flat-square)](https://bestgems.org/gems/rubocop-lts) |
190
+ | [turbo_tests2](https://bestgems.org/gems/turbo_tests2) | [GitHub](https://github.com/galtzo-floss/turbo_tests2) | parallel test execution | [![Daily download rank for turbo_tests2](https://img.shields.io/gem/rd/turbo_tests2.svg?style=flat-square)](https://bestgems.org/gems/turbo_tests2) |
221
191
 
222
- If bundler is not being used to manage dependencies, install the gem by executing:
192
+ </details>
223
193
 
224
- $ gem install oauth2
194
+ ### Federated DVCS
225
195
 
226
- ## OAuth2 for Enterprise
196
+ <details markdown="1">
197
+ <summary>Find this repo on federated forges (Coming soon!)</summary>
227
198
 
228
- Available as part of the Tidelift Subscription.
199
+ | Federated [DVCS][💎d-in-dvcs] Repository | Status | Issues | PRs | Wiki | CI | Discussions |
200
+ |-------------------------------------------------|-----------------------------------------------------------------------|---------------------------|--------------------------|---------------------------|--------------------------|------------------------------|
201
+ | 🧪 [ruby-oauth/oauth2 on GitLab][📜src-gl] | The Truth | [💚][🤝gl-issues] | [💚][🤝gl-pulls] | [💚][📜gl-wiki] | 🐭 Tiny Matrix | ➖ |
202
+ | 🧊 [ruby-oauth/oauth2 on CodeBerg][📜src-cb] | An Ethical Mirror ([Donate][🤝cb-donate]) | [💚][🤝cb-issues] | [💚][🤝cb-pulls] | ➖ | ⭕️ No Matrix | ➖ |
203
+ | 🐙 [ruby-oauth/oauth2 on GitHub][📜src-gh] | Another Mirror | [💚][🤝gh-issues] | [💚][🤝gh-pulls] | [💚][📜gh-wiki] | 💯 Full Matrix | [💚][gh-discussions] |
204
+ | 🎮️ [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] |
229
205
 
230
- The maintainers of OAuth2 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]
231
-
232
- [tidelift-ref]: https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=enterprise
233
-
234
- ## Security contact information
235
-
236
- To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
237
- Tidelift will coordinate the fix and disclosure.
238
-
239
- For more see [SECURITY.md][🚎sec-pol].
240
-
241
- ## What is new for v2.0?
242
-
243
- - Officially support Ruby versions >= 2.7
244
- - Unofficially support Ruby versions >= 2.5
245
- - Incidentally support Ruby versions >= 2.2
246
- - Drop support for the expired MAC Draft (all versions)
247
- - Support IETF rfc7523 JWT Bearer Tokens
248
- - Support IETF rfc7231 Relative Location in Redirect
249
- - Support IETF rfc6749 Don't set oauth params when nil
250
- - 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)
251
- - 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`
252
- - Adds new option to `OAuth2::Client#get_token`:
253
- - `:access_token_class` (`AccessToken`); user specified class to use for all calls to `get_token`
254
- - Adds new option to `OAuth2::AccessToken#initialize`:
255
- - `:expires_latency` (`nil`); number of seconds by which AccessToken validity will be reduced to offset latency
256
- - By default, keys are transformed to camel case.
257
- - Original keys will still work as previously, in most scenarios, thanks to `rash_alt` gem.
258
- - However, this is a _breaking_ change if you rely on `response.parsed.to_h`, as the keys in the result will be camel case.
259
- - As of version 2.0.4 you can turn key transformation off with the `snaky: false` option.
260
- - By default, the `:auth_scheme` is now `:basic_auth` (instead of `:request_body`)
261
- - Third-party strategies and gems may need to be updated if a provider was requiring client id/secret in the request body
262
- - [... A lot more](https://gitlab.com/oauth-xx/oauth2/-/blob/main/CHANGELOG.md#2.0.0)
263
-
264
- ## Compatibility
265
-
266
- Targeted ruby compatibility is non-EOL versions of Ruby, currently 2.7, 3.0 and
267
- 3.1. Compatibility is further distinguished by supported and unsupported versions of Ruby.
268
- Ruby is limited to 2.2+ for 2.x releases. See `1-4-stable` branch for older rubies.
269
-
270
- <details>
271
- <summary>Ruby Engine Compatibility Policy</summary>
272
-
273
- This gem is tested against MRI, JRuby, and Truffleruby.
274
- Each of those has varying versions that target a specific version of MRI Ruby.
275
- This gem should work in the just-listed Ruby engines according to the targeted MRI compatibility in the table below.
276
- If you would like to add support for additional engines,
277
- see `gemfiles/README.md`, then submit a PR to the correct maintenance branch as according to the table below.
278
206
  </details>
279
207
 
280
- <details>
281
- <summary>Ruby Version Compatibility Policy</summary>
208
+ [gh-discussions]: https://github.com/ruby-oauth/oauth2/discussions
209
+
210
+ ### 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)
211
+
212
+ Available as part of the Tidelift Subscription.
213
+
214
+ <details markdown="1">
215
+ <summary>Need enterprise-level guarantees?</summary>
216
+
217
+ 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.
282
218
 
283
- If something doesn't work on one of these interpreters, it's a bug.
219
+ [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
284
220
 
285
- This library may inadvertently work (or seem to work) on other Ruby
286
- implementations, however support will only be provided for the versions listed
287
- above.
221
+ - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
222
+ - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
223
+ - 💡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
224
+
225
+ Alternatively:
226
+
227
+ - [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
228
+ - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
229
+ - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
288
230
 
289
- If you would like this library to support another Ruby version, you may
290
- volunteer to be a maintainer. Being a maintainer entails making sure all tests
291
- run and pass on that implementation. When something breaks on your
292
- implementation, you will be responsible for providing patches in a timely
293
- fashion. If critical issues for a particular implementation exist at the time
294
- of a major release, support for that Ruby version may be dropped.
295
231
  </details>
296
232
 
297
- | | Ruby OAuth2 Version | Maintenance Branch | Supported Officially | Supported Unofficially | Supported Incidentally |
298
- |:----|---------------------|--------------------|-------------------------|------------------------|------------------------|
299
- | 1️⃣ | 2.0.x | `main` | 2.7, 3.0, 3.1 | 2.5, 2.6 | 2.2, 2.3, 2.4 |
300
- | 2️⃣ | 1.4.x | `1-4-stable` | 2.5, 2.6, 2.7, 3.0, 3.1 | 2.1, 2.2, 2.3, 2.4 | 1.9, 2.0 |
301
- | 3️⃣ | older | N/A | Best of luck to you! | Please upgrade! | |
233
+ ## Installation
302
234
 
303
- NOTE: The 1.4 series will only receive critical security updates.
304
- See [SECURITY.md][🚎sec-pol]
235
+ Install the gem and add to the application's Gemfile by executing:
305
236
 
306
- ## Usage Examples
237
+ ```console
238
+ bundle add oauth2
239
+ ```
307
240
 
308
- ### Global Configuration
241
+ If bundler is not being used to manage dependencies, install the gem by executing:
309
242
 
310
- If you started seeing this warning, but everything it working fine, you can now silence it.
311
- ```log
312
- OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key
243
+ ```console
244
+ gem install oauth2
313
245
  ```
314
246
 
247
+ ## ⚙️ Configuration
248
+
249
+ Global settings for the library:
250
+
315
251
  ```ruby
316
252
  OAuth2.configure do |config|
317
- config.silence_extra_tokens_warning = true # default: false
253
+ config.silence_extra_tokens_warning = false # default: true
254
+ config.silence_no_tokens_warning = false # default: true
318
255
  end
319
256
  ```
320
257
 
258
+ Filtering-related settings:
259
+
260
+ ```ruby
261
+ OAuth2.configure do |config|
262
+ config.filtered_label = "[REDACTED]" # default: "[FILTERED]"
263
+ config.filtered_debug_keys += ["client_assertion"]
264
+ end
265
+ ```
266
+
267
+ - `filtered_label` controls the placeholder used when sensitive values are filtered from inspected objects and debug logging output.
268
+ - `filtered_debug_keys` controls which key names have their values redacted from debug logging output when `OAUTH_DEBUG=true`.
269
+ - Debug logging remains opt-in and should still be used cautiously in production environments.
270
+
271
+ ## 🔧 Basic Usage
272
+
273
+ ### Client Initialization Options
274
+
275
+ `OAuth2::Client.new` accepts several options:
276
+
277
+ - `:site`: The base URL for the OAuth 2.0 provider.
278
+ - `:authorize_url`: The authorization endpoint (default: `"oauth/authorize"`).
279
+ - `:token_url`: The token endpoint (default: `"oauth/token"`).
280
+ - `:auth_scheme`: The authentication scheme (`:basic_auth`, `:request_body`, `:tls_client_auth`, `:private_key_jwt`). Default is `:basic_auth`.
281
+ - `:connection_opts`: Options for the underlying Faraday connection (timeouts, proxy, etc.).
282
+ - `:raise_errors`: Whether to raise `OAuth2::Error` on 400+ responses (default: `true`).
283
+
284
+ <details markdown="1">
285
+ <summary><em>authorize_url</em> and <em>token_url</em></summary>
286
+
321
287
  ### `authorize_url` and `token_url` are on site root (Just Works!)
322
288
 
323
289
  ```ruby
324
- require 'oauth2'
325
- client = OAuth2::Client.new('client_id', 'client_secret', site: 'https://example.org')
290
+ require "oauth2"
291
+ client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org")
326
292
  # => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
327
- client.auth_code.authorize_url(redirect_uri: 'http://localhost:8080/oauth2/callback')
293
+ client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
328
294
  # => "https://example.org/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
329
295
 
330
- access = client.auth_code.get_token('authorization_code_value', redirect_uri: 'http://localhost:8080/oauth2/callback', headers: {'Authorization' => 'Basic some_password'})
331
- response = access.get('/api/resource', params: {'query_foo' => 'bar'})
296
+ access = client.auth_code.get_token("authorization_code_value", redirect_uri: "http://localhost:8080/oauth2/callback", headers: {"Authorization" => "Basic some_password"})
297
+ response = access.get("/api/resource", params: {"query_foo" => "bar"})
332
298
  response.class.name
333
299
  # => OAuth2::Response
334
300
  ```
335
301
 
336
302
  ### Relative `authorize_url` and `token_url` (Not on site root, Just Works!)
337
303
 
338
- In above example, the default Authorization URL is `oauth/authorize` and default Access Token URL is `oauth/token`, and, as they are missing a leading `/`, both are relative.
304
+ In the above example, the default Authorization URL is `oauth/authorize` and default Access Token URL is `oauth/token`, and, as they are missing a leading `/`, both are relative.
339
305
 
340
306
  ```ruby
341
- client = OAuth2::Client.new('client_id', 'client_secret', site: 'https://example.org/nested/directory/on/your/server')
307
+ client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/nested/directory/on/your/server")
342
308
  # => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
343
- client.auth_code.authorize_url(redirect_uri: 'http://localhost:8080/oauth2/callback')
309
+ client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
344
310
  # => "https://example.org/nested/directory/on/your/server/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
345
311
  ```
346
312
 
@@ -349,21 +315,43 @@ client.auth_code.authorize_url(redirect_uri: 'http://localhost:8080/oauth2/callb
349
315
  You can specify custom URLs for authorization and access token, and when using a leading `/` they will _not be relative_, as shown below:
350
316
 
351
317
  ```ruby
352
- client = OAuth2::Client.new('client_id', 'client_secret',
353
- site: 'https://example.org/nested/directory/on/your/server',
354
- authorize_url: '/jaunty/authorize/',
355
- token_url: '/stirrups/access_token')
318
+ client = OAuth2::Client.new(
319
+ "client_id",
320
+ "client_secret",
321
+ site: "https://example.org/nested/directory/on/your/server",
322
+ authorize_url: "/jaunty/authorize/",
323
+ token_url: "/stirrups/access_token"
324
+ )
356
325
  # => #<OAuth2::Client:0x00000001204c8288 @id="client_id", @secret="client_sec...
357
- client.auth_code.authorize_url(redirect_uri: 'http://localhost:8080/oauth2/callback')
326
+ client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth2/callback")
358
327
  # => "https://example.org/jaunty/authorize/?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2%2Fcallback&response_type=code"
359
328
  client.class.name
360
329
  # => OAuth2::Client
361
330
  ```
362
331
 
332
+ </details>
333
+
334
+ ### Advanced Initializers
335
+
336
+ ```ruby
337
+ client = OAuth2::Client.new(id, secret, site: site) do |faraday|
338
+ faraday.request(:url_encoded)
339
+ faraday.adapter(:net_http_persistent)
340
+ end
341
+ ```
342
+
343
+ ### AccessToken Features
344
+
345
+ Instances of `OAuth2::AccessToken` handle request signing and token expiration.
346
+
347
+ - **Snake Case & Indifferent Access**: `response.parsed` returns a `SnakyHash` allowing access via string/symbol and snake_case keys even if the provider returns CamelCase.
348
+ - **Auto-Refresh**: You can manually check `token.expired?` and call `token.refresh`.
349
+ - **Serialization**: Persist tokens using `token.to_hash` and restore via `OAuth2::AccessToken.from_hash(client, hash)`.
350
+
363
351
  ### snake_case and indifferent access in Response#parsed
364
352
 
365
353
  ```ruby
366
- response = access.get('/api/resource', params: {'query_foo' => 'bar'})
354
+ response = access.get("/api/resource", params: {"query_foo" => "bar"})
367
355
  # Even if the actual response is CamelCase. it will be made available as snaky:
368
356
  JSON.parse(response.body) # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
369
357
  response.parsed # => {"access_token"=>"aaaaaaaa", "additional_data"=>"additional"}
@@ -371,65 +359,203 @@ response.parsed.access_token # => "aaaaaaaa"
371
359
  response.parsed[:access_token] # => "aaaaaaaa"
372
360
  response.parsed.additional_data # => "additional"
373
361
  response.parsed[:additional_data] # => "additional"
374
- response.parsed.class.name # => OAuth2::SnakyHash (subclass of Hashie::Mash::Rash, from `rash_alt` gem)
362
+ response.parsed.class.name # => SnakyHash::StringKeyed (from snaky_hash gem)
363
+ ```
364
+
365
+ #### Serialization
366
+
367
+ As of v2.0.11, if you need to serialize the parsed result, you can!
368
+
369
+ There are two ways to do this, globally, or discretely. The discrete way is recommended.
370
+
371
+ ##### Global Serialization Config
372
+
373
+ Globally configure `SnakyHash::StringKeyed` to use the serializer. Put this in your code somewhere reasonable (like an initializer for Rails).
374
+
375
+ ```ruby
376
+ SnakyHash::StringKeyed.class_eval do
377
+ extend SnakyHash::Serializer
378
+ end
379
+ ```
380
+
381
+ ##### Discrete Serialization Config
382
+
383
+ Discretely configure a custom Snaky Hash class to use the serializer.
384
+
385
+ ```ruby
386
+ class MySnakyHash < SnakyHash::StringKeyed
387
+ # Give this hash class `dump` and `load` abilities!
388
+ extend SnakyHash::Serializer
389
+ end
390
+
391
+ # And tell your client to use the custom class in each call:
392
+ client = OAuth2::Client.new("client_id", "client_secret", site: "https://example.org/oauth2")
393
+ token = client.get_token({snaky_hash_klass: MySnakyHash})
394
+ ```
395
+
396
+ ##### Serialization Extensions
397
+
398
+ These extensions work regardless of whether you used the global or discrete config above.
399
+
400
+ There are a few hacks you may need in your class to support Ruby < 2.4.2 or < 2.6.
401
+ They are likely not needed if you are on a newer Ruby.
402
+ Expand the examples below, or the [ruby-oauth/snaky_hash](https://gitlab.com/ruby-oauth/snaky_hash) gem,
403
+ or [response_spec.rb](https://github.com/ruby-oauth/oauth2/blob/main/spec/oauth2/response_spec.rb), for more ideas, especially if you need to study the hacks for older Rubies.
404
+
405
+ <details markdown="1">
406
+ <summary>See Examples</summary>
407
+
408
+ ```ruby
409
+ class MySnakyHash < SnakyHash::StringKeyed
410
+ # Give this hash class `dump` and `load` abilities!
411
+ extend SnakyHash::Serializer
412
+
413
+ #### Serialization Extentions
414
+ #
415
+ # Act on the non-hash values (including the values of hashes) as they are dumped to JSON
416
+ # In other words, this retains nested hashes, and only the deepest leaf nodes become bananas.
417
+ # WARNING: This is a silly example!
418
+ dump_value_extensions.add(:to_fruit) do |value|
419
+ "banana" # => Make values "banana" on dump
420
+ end
421
+
422
+ # Act on the non-hash values (including the values of hashes) as they are loaded from the JSON dump
423
+ # In other words, this retains nested hashes, and only the deepest leaf nodes become ***.
424
+ # WARNING: This is a silly example!
425
+ load_value_extensions.add(:to_stars) do |value|
426
+ "***" # Turn dumped bananas into *** when they are loaded
427
+ end
428
+
429
+ # Act on the entire hash as it is prepared for dumping to JSON
430
+ # WARNING: This is a silly example!
431
+ dump_hash_extensions.add(:to_cheese) do |value|
432
+ if value.is_a?(Hash)
433
+ value.transform_keys do |key|
434
+ split = key.split("_")
435
+ first_word = split[0]
436
+ key.sub(first_word, "cheese")
437
+ end
438
+ else
439
+ value
440
+ end
441
+ end
442
+
443
+ # Act on the entire hash as it is loaded from the JSON dump
444
+ # WARNING: This is a silly example!
445
+ load_hash_extensions.add(:to_pizza) do |value|
446
+ if value.is_a?(Hash)
447
+ res = klass.new
448
+ value.keys.each_with_object(res) do |key, result|
449
+ split = key.split("_")
450
+ last_word = split[-1]
451
+ new_key = key.sub(last_word, "pizza")
452
+ result[new_key] = value[key]
453
+ end
454
+ res
455
+ else
456
+ value
457
+ end
458
+ end
459
+ end
375
460
  ```
376
461
 
377
- #### What if I hate snakes and/or indifference?
462
+ </details>
463
+
464
+ #### Prefer camelCase over snake_case? => snaky: false
378
465
 
379
466
  ```ruby
380
- response = access.get('/api/resource', params: {'query_foo' => 'bar'}, snaky: false)
467
+ response = access.get("/api/resource", params: {"query_foo" => "bar"}, snaky: false)
381
468
  JSON.parse(response.body) # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
382
469
  response.parsed # => {"accessToken"=>"aaaaaaaa", "additionalData"=>"additional"}
383
- response.parsed['accessToken'] # => "aaaaaaaa"
384
- response.parsed['additionalData'] # => "additional"
470
+ response.parsed["accessToken"] # => "aaaaaaaa"
471
+ response.parsed["additionalData"] # => "additional"
385
472
  response.parsed.class.name # => Hash (just, regular old Hash)
386
473
  ```
387
474
 
388
- <details>
389
- <summary>Debugging</summary>
475
+ <details markdown="1">
476
+ <summary>Debugging & Logging</summary>
390
477
 
391
- Set an environment variable, however you would [normally do that](https://github.com/bkeepers/dotenv).
478
+ Set an environment variable as per usual (e.g. with [dotenv](https://github.com/bkeepers/dotenv)).
392
479
 
393
480
  ```ruby
394
481
  # will log both request and response, including bodies
395
- ENV['OAUTH_DEBUG'] = 'true'
482
+ ENV["OAUTH_DEBUG"] = "true"
396
483
  ```
397
484
 
398
485
  By default, debug output will go to `$stdout`. This can be overridden when
399
486
  initializing your OAuth2::Client.
400
487
 
488
+ Sensitive values are filtered from debug logging output using:
489
+
490
+ - `OAuth2.config[:filtered_label]`
491
+ - `OAuth2.config[:filtered_debug_keys]`
492
+
493
+ Debug logging remains opt-in and should still be used cautiously in production environments.
494
+
401
495
  ```ruby
402
- require 'oauth2'
496
+ require "oauth2"
403
497
  client = OAuth2::Client.new(
404
- 'client_id',
405
- 'client_secret',
406
- site: 'https://example.org',
407
- logger: Logger.new('example.log', 'weekly')
498
+ "client_id",
499
+ "client_secret",
500
+ site: "https://example.org",
501
+ logger: Logger.new("example.log", "weekly")
408
502
  )
409
503
  ```
504
+
410
505
  </details>
411
506
 
412
- ## OAuth2::Response
507
+ ### Request Target Trust Boundaries
508
+
509
+ This gem supports request flows that can involve absolute URLs in addition to relative paths.
510
+ That flexibility can expand trust boundaries when a token-bearing client is asked to send requests
511
+ to caller-provided targets.
512
+
513
+ Practical guidance:
514
+
515
+ - prefer relative paths where practical
516
+ - do not pass untrusted absolute URLs into token-bearing clients
517
+ - validate or allowlist request targets at the application layer today if your deployment has strict trust-boundary requirements
518
+
519
+ This release line does not yet enforce same-host or allowlist request policy automatically.
520
+ If stricter outbound request controls are needed, they should currently be implemented by the calling application.
521
+
522
+ ### OAuth2::Response
413
523
 
414
524
  The `AccessToken` methods `#get`, `#post`, `#put` and `#delete` and the generic `#request`
415
- will return an instance of the #OAuth2::Response class.
525
+ will return an instance of the `OAuth2::Response` class.
416
526
 
417
527
  This instance contains a `#parsed` method that will parse the response body and
418
- 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
419
- the body is a JSON object. It will return an Array if the body is a JSON
420
- array. Otherwise, it will return the original body string.
528
+ 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
529
+ the body is a JSON object. It will return an Array if the body is a JSON
530
+ array. Otherwise, it will return the original body string.
421
531
 
422
532
  The original response body, headers, and status can be accessed via their
423
533
  respective methods.
424
534
 
425
- ## OAuth2::AccessToken
535
+ ### OAuth2::AccessToken
426
536
 
427
537
  If you have an existing Access Token for a user, you can initialize an instance
428
538
  using various class methods including the standard new, `from_hash` (if you have
429
539
  a hash of the values), or `from_kvform` (if you have an
430
540
  `application/x-www-form-urlencoded` encoded string of the values).
431
541
 
432
- ## OAuth2::Error
542
+ Options (since v2.0.x unless noted):
543
+
544
+ - `expires_latency` (Integer | nil): Seconds to subtract from expires_in when computing #expired? to offset latency.
545
+ - `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).
546
+ - `mode` (Symbol | Proc | Hash): Controls how the token is transmitted on requests made via this AccessToken instance.
547
+ - `:header` — Send as Authorization: Bearer <token> header (default and preferred by OAuth 2.1 draft guidance).
548
+ - `:query` — Send as access_token query parameter (discouraged in general, but required by some providers).
549
+ - Verb-dependent (since v2.0.15): Provide either:
550
+ - a `Proc` taking `|verb|` and returning `:header` or `:query`, or
551
+ - a `Hash` with verb symbols as keys, for example `{get: :query, post: :header, delete: :header}`.
552
+
553
+ Note: Verb-dependent mode supports providers like Instagram that require query mode for `GET` and header mode for `POST`/`DELETE`
554
+
555
+ - Verb-dependent mode via `Proc` was added in v2.0.15
556
+ - Verb-dependent mode via `Hash` was added in v2.0.16
557
+
558
+ ### OAuth2::Error
433
559
 
434
560
  On 400+ status code responses, an `OAuth2::Error` will be raised. If it is a
435
561
  standard OAuth2 error response, the body will be parsed and `#code` and `#description` will contain the values provided from the error and
@@ -441,108 +567,571 @@ option on initialization of the client. In this case the `OAuth2::Response`
441
567
  instance will be returned as usual and on 400+ status code responses, the
442
568
  Response instance will contain the `OAuth2::Error` instance.
443
569
 
444
- ## Authorization Grants
570
+ ### Authorization Grants
445
571
 
446
- Currently the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion
572
+ Currently, the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion
447
573
  authentication grant types have helper strategy classes that simplify client
448
- use. They are available via the [`#auth_code`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/auth_code.rb),
449
- [`#implicit`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/implicit.rb),
450
- [`#password`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/password.rb),
451
- [`#client_credentials`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/client_credentials.rb), and
452
- [`#assertion`](https://gitlab.com/oauth-xx/oauth2/-/blob/main/lib/oauth2/strategy/assertion.rb) methods respectively.
574
+ use. They are available via the [`#auth_code`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/auth_code.rb),
575
+ [`#implicit`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/implicit.rb),
576
+ [`#password`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/password.rb),
577
+ [`#client_credentials`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/client_credentials.rb), and
578
+ [`#assertion`](https://gitlab.com/ruby-oauth/oauth2/-/blob/main/lib/oauth2/strategy/assertion.rb) methods respectively.
579
+
580
+ #### OAuth 2.1 (draft) Note:
581
+
582
+ - **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.
583
+ - **Implicit grant** (response_type=token) and **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.
584
+ - **Redirect URIs** must be compared using exact string matching by the Authorization Server.
585
+
586
+ <details markdown="1">
587
+ <summary>OAuth 2.1 (draft) References</summary>
588
+
589
+ - OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
590
+ - Aaron Parecki: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1
591
+ - FusionAuth: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1
592
+ - Okta: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs
593
+ - Video: https://www.youtube.com/watch?v=g_aVPdwBTfw
594
+ - Differences overview: https://fusionauth.io/learn/expert-advice/oauth/differences-between-oauth-2-oauth-2-1/
595
+
596
+ </details>
453
597
 
454
598
  These aren't full examples, but demonstrative of the differences between usage for each strategy.
599
+
455
600
  ```ruby
456
- auth_url = client.auth_code.authorize_url(redirect_uri: 'http://localhost:8080/oauth/callback')
457
- access = client.auth_code.get_token('code_value', redirect_uri: 'http://localhost:8080/oauth/callback')
601
+ auth_url = client.auth_code.authorize_url(redirect_uri: "http://localhost:8080/oauth/callback")
602
+ access = client.auth_code.get_token("code_value", redirect_uri: "http://localhost:8080/oauth/callback")
458
603
 
459
- auth_url = client.implicit.authorize_url(redirect_uri: 'http://localhost:8080/oauth/callback')
604
+ auth_url = client.implicit.authorize_url(redirect_uri: "http://localhost:8080/oauth/callback")
460
605
  # get the token params in the callback and
461
606
  access = OAuth2::AccessToken.from_kvform(client, query_string)
462
607
 
463
- access = client.password.get_token('username', 'password')
608
+ access = client.password.get_token("username", "password")
464
609
 
465
610
  access = client.client_credentials.get_token
466
611
 
467
612
  # Client Assertion Strategy
468
613
  # see: https://tools.ietf.org/html/rfc7523
469
614
  claimset = {
470
- iss: 'http://localhost:3001',
471
- aud: 'http://localhost:8080/oauth2/token',
472
- sub: 'me@example.com',
615
+ iss: "http://localhost:3001",
616
+ aud: "http://localhost:8080/oauth2/token",
617
+ sub: "me@example.com",
473
618
  exp: Time.now.utc.to_i + 3600,
474
619
  }
475
- assertion_params = [claimset, 'HS256', 'secret_key']
620
+ assertion_params = [claimset, "HS256", "secret_key"]
476
621
  access = client.assertion.get_token(assertion_params)
477
622
 
478
623
  # The `access` (i.e. access token) is then used like so:
479
624
  access.token # actual access_token string, if you need it somewhere
480
- access.get('/api/stuff') # making api calls with access token
625
+ access.get("/api/stuff") # making api calls with access token
481
626
  ```
482
627
 
483
628
  If you want to specify additional headers to be sent out with the
484
629
  request, add a 'headers' hash under 'params':
485
630
 
486
631
  ```ruby
487
- access = client.auth_code.get_token('code_value', redirect_uri: 'http://localhost:8080/oauth/callback', headers: {'Some' => 'Header'})
632
+ access = client.auth_code.get_token("code_value", redirect_uri: "http://localhost:8080/oauth/callback", headers: {"Some" => "Header"})
488
633
  ```
489
634
 
490
635
  You can always use the `#request` method on the `OAuth2::Client` instance to make
491
636
  requests for tokens for any Authentication grant type.
492
637
 
493
- ## Versioning
638
+ ## 🦷 FLOSS Funding
639
+
640
+ While ruby-oauth tools are free software and will always be, the project would benefit immensely from some funding.
641
+ Raising a monthly budget of... "dollars" would make the project more sustainable.
642
+
643
+ We welcome both individual and corporate sponsors! We also offer a
644
+ wide array of funding channels to account for your preferences.
645
+ Currently, [Open Collective][🖇osc] is our preferred funding platform.
646
+
647
+ **If you're working in a company that's making significant use of ruby-oauth tools we'd
648
+ appreciate it if you suggest to your company to become a ruby-oauth sponsor.**
649
+
650
+ You can support the development of ruby-oauth tools via
651
+ [GitHub Sponsors][🖇sponsor],
652
+ [Liberapay][⛳liberapay],
653
+ [PayPal][🖇paypal],
654
+ [Open Collective][🖇osc]
655
+ and [Tidelift][🏙️entsup-tidelift].
656
+
657
+ | 📍 NOTE |
658
+ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
659
+ | 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. |
660
+
661
+ ### Open Collective for Individuals
662
+
663
+ Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/ruby-oauth#backer)]
664
+
665
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
494
666
 
495
- This library aims to adhere to [Semantic Versioning 2.0.0][semver].
496
- Violations of this scheme should be reported as bugs. Specifically,
497
- if a minor or patch version is released that breaks backward
498
- compatibility, a new version should be immediately released that
499
- restores compatibility. Breaking changes to the public API will
500
- only be introduced with new major versions.
667
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:START -->
668
+ No backers yet. Be the first!
669
+ <!-- OPENCOLLECTIVE-INDIVIDUALS:END -->
501
670
 
502
- As a result of this policy, you can (and should) specify a
503
- dependency on this gem using the [Pessimistic Version Constraint][pvc] with two digits of precision.
671
+ ### Open Collective for Organizations
672
+
673
+ 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)]
674
+
675
+ NOTE: [kettle-readme-backers][kettle-readme-backers] updates this list every day, automatically.
676
+
677
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:START -->
678
+ No sponsors yet. Be the first!
679
+ <!-- OPENCOLLECTIVE-ORGANIZATIONS:END -->
680
+
681
+ [kettle-readme-backers]: https://github.com/ruby-oauth/oauth2/blob/main/bin/kettle-readme-backers
682
+
683
+ ### Another way to support open-source
684
+
685
+ I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
686
+
687
+ 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`.
688
+
689
+ 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.
690
+
691
+ **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
692
+
693
+ [![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 to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi]
694
+
695
+ ## 🔐 Security
696
+
697
+ See [SECURITY.md][🔐security].
698
+
699
+ ## 🤝 Contributing
700
+
701
+ If you need some ideas of where to help, you could work on adding more code coverage,
702
+ or if it is already 💯 (see [below](#code-coverage)) check [issues][🤝gh-issues] or [PRs][🤝gh-pulls],
703
+ or use the gem and think about how it could be better.
704
+
705
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
706
+
707
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
708
+
709
+ ### 🚀 Release Instructions
710
+
711
+ See [CONTRIBUTING.md][🤝contributing].
712
+
713
+ ### Code Coverage
714
+
715
+ <details markdown="1">
716
+ <summary>Coverage service badges</summary>
717
+
718
+ [![Coverage Graph][🏀codecov-g]][🏀codecov]
719
+
720
+ [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
721
+
722
+ [![QLTY Test Coverage][🏀qlty-covi]][🏀qlty-cov]
723
+
724
+ </details>
725
+
726
+ ### 🪇 Code of Conduct
727
+
728
+ Everyone interacting with this project's codebases, issue trackers,
729
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
730
+
731
+ ## 🌈 Contributors
732
+
733
+ [![Contributors][🖐contributors-img]][🖐contributors]
734
+
735
+ Made with [contributors-img][🖐contrib-rocks].
736
+
737
+ Also see GitLab Contributors: [https://gitlab.com/ruby-oauth/oauth2/-/graphs/main][🚎contributors-gl]
738
+
739
+ <details markdown="1">
740
+ <summary>⭐️ Star History</summary>
741
+
742
+ <a href="https://star-history.com/ruby-oauth/oauth2&Date">
743
+ <picture>
744
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=ruby-oauth/oauth2&type=Date&theme=dark" />
745
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=ruby-oauth/oauth2&type=Date" />
746
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=ruby-oauth/oauth2&type=Date" />
747
+ </picture>
748
+ </a>
749
+
750
+ </details>
751
+
752
+ ## 📌 Versioning
753
+
754
+ This library follows [![Semantic Versioning 2.0.0][📌semver-img]][📌semver] for its public API where practical.
755
+ For most applications, prefer the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
504
756
 
505
757
  For example:
506
758
 
507
759
  ```ruby
508
- spec.add_dependency 'oauth2', '~> 2.0'
760
+ spec.add_dependency("oauth2", "~> 2.0")
509
761
  ```
510
762
 
511
- [semver]: http://semver.org/
512
- [pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
763
+ <details markdown="1">
764
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
513
765
 
514
- ## License
766
+ Dropping support for a platform can be a breaking change for affected users.
767
+ If a release changes supported platforms, it should be called out clearly in the changelog and versioned with that impact in mind.
515
768
 
516
- [![License: MIT][🖇src-license-img]][🖇src-license]
769
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
770
+ read this article from the creator of SemVer:
517
771
 
518
- - Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc.
519
- - Copyright (c) 2017-2022 [oauth-xx organization][oauth-xx]
520
- - See [LICENSE][license] for details.
772
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
521
773
 
522
- [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2.svg?type=large)][fossa2]
774
+ </details>
523
775
 
524
- [license]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/LICENSE
525
- [oauth-xx]: https://gitlab.com/oauth-xx
526
- [fossa2]: https://app.fossa.io/projects/git%2Bgithub.com%2Foauth-xx%2Foauth2?ref=badge_large
776
+ See [CHANGELOG.md][📌changelog] for a list of releases.
777
+
778
+ ## 📄 License
779
+
780
+ The gem is available as open source under the terms of
781
+ the [MIT](MIT.md) [![License: MIT][📄license-img]][📄license-ref].
782
+
783
+ ### © Copyright
784
+
785
+ See [LICENSE.md][📄license] for the official copyright notice.
786
+
787
+ <details markdown="1">
788
+ <summary>Copyright holders</summary>
789
+
790
+ - Copyright (c) 2010-2014, 2016-2017 Erik Michaels-Ober
791
+ - Copyright (c) 2010 Jeremy Kemper
792
+ - Copyright (c) 2010-2011 Michael Bleigh
793
+ - Copyright (c) 2010-2011 Paul Walker
794
+ - Copyright (c) 2010 rick
795
+ - Copyright (c) 2010 Tim Habermaas
796
+ - Copyright (c) 2010 Wynn Netherland
797
+ - Copyright (c) 2011 Alexander Lang
798
+ - Copyright (c) 2011 Alexander Lang
799
+ - Copyright (c) 2011 Greg Spurrier
800
+ - Copyright (c) 2011 Jay Adkisson
801
+ - Copyright (c) 2011 Luke Saunders
802
+ - Copyright (c) 2011 Paul Walker
803
+ - Copyright (c) 2011 Simon Gate
804
+ - Copyright (c) 2012 Bas Vodde
805
+ - Copyright (c) 2012 Damian Janowski
806
+ - Copyright (c) 2012 Daniël van de Burgt
807
+ - Copyright (c) 2012 Dorren Chen
808
+ - Copyright (c) 2012 Igor Sales
809
+ - Copyright (c) 2012 Leigh Caplan
810
+ - Copyright (c) 2012 Michael Andrews
811
+ - Copyright (c) 2012 Omer Rauchwerger
812
+ - Copyright (c) 2012 Saverio Trioni
813
+ - Copyright (c) 2012 Trent Ogren
814
+ - Copyright (c) 2012 Vsevolod Romashov
815
+ - Copyright (c) 2013 Antonio Tapiador del Dujo
816
+ - Copyright (c) 2013 Eduardo Gurgel
817
+ - Copyright (c) 2013 Geostellar Developer
818
+ - Copyright (c) 2013-2014, 2018 Niels Ganser
819
+ - Copyright (c) 2013 Rainux Luo
820
+ - Copyright (c) 2013 Taylor Hedberg
821
+ - Copyright (c) 2013 Tim Clem
822
+ - Copyright (c) 2014 Dave Stevens
823
+ - Copyright (c) 2014 Ellis Berner
824
+ - Copyright (c) 2014 Frank Macreery
825
+ - Copyright (c) 2014 Michael Bleigh
826
+ - Copyright (c) 2014 Olivier Lacan
827
+ - Copyright (c) 2014 Peter Souter
828
+ - Copyright (c) 2014 Ryan Williams
829
+ - Copyright (c) 2015 Andrew Cantino and Jeff Moore
830
+ - Copyright (c) 2015 Thomas Walpole
831
+ - Copyright (c) 2016 Bo Jeanes
832
+ - Copyright (c) 2016 Cody Cutrer
833
+ - Copyright (c) 2016 Edward Rudd
834
+ - Copyright (c) 2016 Lawrence Oluyede
835
+ - Copyright (c) 2016 Linus Pettersson
836
+ - Copyright (c) 2016 Motoshi Nishihira
837
+ - Copyright (c) 2017 Adrian Setyadi
838
+ - Copyright (c) 2017-2018 Benjamin Quorning
839
+ - Copyright (c) 2017 Christoph Petschnig
840
+ - Copyright (c) 2017, 2022 Nathaniel Bibler
841
+ - Copyright (c) 2017 Oleg
842
+ - Copyright (c) 2017 Samuel Cochran
843
+ - Copyright (c) 2017 tetsuya
844
+ - Copyright (c) 2017 Yury Velikanau
845
+ - Copyright (c) 2018 Alex Kowalczuk
846
+ - Copyright (c) 2018 asm__
847
+ - Copyright (c) 2018 David Christensen
848
+ - Copyright (c) 2018 fossabot
849
+ - Copyright (c) 2018 Jeff Moore
850
+ - Copyright (c) 2018 Jeff Moore
851
+ - Copyright (c) 2018 Jonathan del Strother
852
+ - Copyright (c) 2018 Joseph Page
853
+ - Copyright (c) 2018 Joseph Page
854
+ - Copyright (c) 2018 Lomey
855
+ - Copyright (c) 2018 Markus Bengts
856
+ - Copyright (c) 2018 Mathias Klippinge
857
+ - Copyright (c) 2018 nikz
858
+ - Copyright (c) 2018-2019, 2021-2022, 2024-2026 Peter H. Boling
859
+ - Copyright (c) 2019 Daniel Fockler
860
+ - Copyright (c) 2019 Elliot Crosby-McCullough
861
+ - Copyright (c) 2019 João Paulo
862
+ - Copyright (c) 2019 Orien Madgwick
863
+ - Copyright (c) 2019 Ryan T. Hosford
864
+ - Copyright (c) 2019 Tom Corley
865
+ - Copyright (c) 2020 anvox
866
+ - Copyright (c) 2020 Jesse Cotton
867
+ - Copyright (c) 2020 Olle Jonsson
868
+ - Copyright (c) 2020 Stephen Reid
869
+ - Copyright (c) 2021 Anders Carling
870
+ - Copyright (c) 2021 dobon
871
+ - Copyright (c) 2021 Jan Zaydowicz
872
+ - Copyright (c) 2021 Nicholas Palaniuk
873
+ - Copyright (c) 2021-2022 Stan Hu
874
+ - Copyright (c) 2022 Benjamin Quorning
875
+ - Copyright (c) 2022 Bouke van der Bijl
876
+ - Copyright (c) 2022 nov
877
+ - Copyright (c) 2022 Rick Selby
878
+ - Copyright (c) 2022 Ryo Takahashi
879
+ - Copyright (c) 2023 Jessie Young
880
+ - Copyright (c) 2023 Карим Гимадеев
881
+ - Copyright (c) 2024 Aboling0
882
+ - Copyright (c) 2024 Elise Wood
883
+ - Copyright (c) 2024 Manuel van Rijn
884
+ - Copyright (c) 2025-2026 Annibelle Boling
885
+ - Copyright (c) 2025 Mark James
886
+ - Copyright (c) 2025 Mridang Agarwalla
887
+ - Copyright (c) 2025 Sasa Rosic
888
+ - Copyright (c) 2026 Jonathan Grinstead
889
+ - Copyright (c) 2026 kain
890
+ - Copyright (c) 2026 Rob Zolkos
891
+ - Copyright (c) 2026 StepSecurity Bot
527
892
 
528
- ## Development
893
+ </details>
529
894
 
530
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
895
+ ## 🤑 A request for help
531
896
 
532
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
897
+ Maintainers have teeth and need to pay their dentists.
898
+ After getting laid off in an RIF in March, and encountering difficulty finding a new one,
899
+ I began spending most of my time building open source tools.
900
+ I'm hoping to be able to pay for my kids' health insurance this month,
901
+ so if you value the work I am doing, I need your support.
902
+ Please consider sponsoring me or the project.
533
903
 
534
- ## Contributing
904
+ To join the community or get help 👇️ Join the Discord.
535
905
 
536
- See [CONTRIBUTING.md][contributing]
906
+ [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
537
907
 
538
- [contributing]: https://gitlab.com/oauth-xx/oauth2/-/blob/main/CONTRIBUTING.md
908
+ To say "thanks!" ☝️ Join the Discord or 👇️ send money.
539
909
 
540
- ## Contributors
910
+ [![Sponsor ruby-oauth/oauth2 on Open Source Collective][🖇osc-all-bottom-img]][🖇osc] 💌 [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
541
911
 
542
- [![Contributors](https://contrib.rocks/image?repo=oauth-xx/oauth2)]("https://gitlab.com/oauth-xx/oauth2/-/graphs/main")
912
+ ### Please give the project a star ⭐ ♥.
543
913
 
544
- Made with [contributors-img](https://contrib.rocks).
914
+ Many parts of this project are actively managed by a [kettle-jem](https://github.com/structuredmerge/structuredmerge-ruby/tree/main/gems/kettle-jem) smart template utilizing [StructuredMerge.org](https://structuredmerge.org) merge contracts.
545
915
 
546
- ## Code of Conduct
916
+ Thanks for RTFM. ☺️
547
917
 
548
- Everyone interacting in the OAuth2 project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://gitlab.com/oauth-xx/oauth2/-/blob/main/CODE_OF_CONDUCT.md).
918
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
919
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
920
+ [⛳liberapay]: https://liberapay.com/pboling/donate
921
+ [🖇osc-all-img]: https://img.shields.io/opencollective/all/ruby-oauth
922
+ [🖇osc-sponsors-img]: https://img.shields.io/opencollective/sponsors/ruby-oauth
923
+ [🖇osc-backers-img]: https://img.shields.io/opencollective/backers/ruby-oauth
924
+ [🖇osc-backers]: https://opencollective.com/ruby-oauth#backer
925
+ [🖇osc-backers-i]: https://opencollective.com/ruby-oauth/backers/badge.svg?style=flat
926
+ [🖇osc-sponsors]: https://opencollective.com/ruby-oauth#sponsor
927
+ [🖇osc-sponsors-i]: https://opencollective.com/ruby-oauth/sponsors/badge.svg?style=flat
928
+ [🖇osc-all-bottom-img]: https://img.shields.io/opencollective/all/ruby-oauth?style=for-the-badge
929
+ [🖇osc-sponsors-bottom-img]: https://img.shields.io/opencollective/sponsors/ruby-oauth?style=for-the-badge
930
+ [🖇osc-backers-bottom-img]: https://img.shields.io/opencollective/backers/ruby-oauth?style=for-the-badge
931
+ [🖇osc]: https://opencollective.com/ruby-oauth
932
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
933
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
934
+ [🖇sponsor]: https://github.com/sponsors/pboling
935
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
936
+ [🖇kofi]: https://ko-fi.com/pboling
937
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
938
+ [🖇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
939
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
940
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
941
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
942
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
943
+ [🖇floss-funding.dev]: https://floss-funding.dev
944
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
945
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
946
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
947
+ [✉️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
948
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
949
+
950
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
951
+ [⛳️gem-namespace]: https://github.com/ruby-oauth/oauth2
952
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-OAuth2-3C2D2D.svg?style=square&logo=ruby&logoColor=white
953
+ [⛳️gem-name]: https://bestgems.org/gems/oauth2
954
+ [⛳️name-img]: https://img.shields.io/badge/name-oauth2-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
955
+ [⛳️tag-img]: https://img.shields.io/github/tag/ruby-oauth/oauth2.svg
956
+ [⛳️tag]: https://github.com/ruby-oauth/oauth2/releases
957
+ [🚂maint-blog]: http://www.railsbling.com/tags/oauth2
958
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
959
+ [🚂maint-contact]: http://www.railsbling.com/contact
960
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
961
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
962
+ [💖🖇linkedin-img]: https://img.shields.io/badge/LinkedIn-Profile-0B66C2?style=flat&logo=newjapanprowrestling
963
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
964
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
965
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
966
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
967
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
968
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
969
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
970
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
971
+ [💖🌳linktree]: https://linktr.ee/galtzo
972
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
973
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
974
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
975
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
976
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
977
+ [💖🧊berg]: https://codeberg.org/pboling
978
+ [💖🐙hub]: https://github.org/pboling
979
+ [💖🛖hut]: https://sr.ht/~galtzo/
980
+ [💖🧪lab]: https://gitlab.com/pboling
981
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
982
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
983
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
984
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
985
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=readme
986
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
987
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
988
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
989
+ [🚂railsbling]: http://www.railsbling.com
990
+ [📜src-gl-img]: https://img.shields.io/badge/GitLab-FBA326?style=for-the-badge&logo=Gitlab&logoColor=orange
991
+ [📜src-gl]: https://gitlab.com/ruby-oauth/oauth2
992
+ [📜src-cb-img]: https://img.shields.io/badge/CodeBerg-4893CC?style=for-the-badge&logo=CodeBerg&logoColor=blue
993
+ [📜src-cb]: https://codeberg.org/ruby-oauth/oauth2
994
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
995
+ [📜src-gh]: https://github.com/ruby-oauth/oauth2
996
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
997
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
998
+ [📜gl-wiki]: https://gitlab.com/ruby-oauth/oauth2/-/wikis/home
999
+ [📜gh-wiki]: https://github.com/ruby-oauth/oauth2/wiki
1000
+ [📜gl-wiki-img]: https://img.shields.io/badge/wiki-gitlab-943CD2.svg?style=for-the-badge&logo=gitlab&logoColor=white
1001
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-github-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
1002
+ [👽dl-rank]: https://bestgems.org/gems/oauth2
1003
+ [👽dl-ranki]: https://img.shields.io/gem/rd/oauth2.svg
1004
+ [👽version]: https://bestgems.org/gems/oauth2
1005
+ [👽versioni]: https://img.shields.io/gem/v/oauth2.svg
1006
+ [🏀qlty-mnt]: https://qlty.sh/gh/ruby-oauth/projects/oauth2
1007
+ [🏀qlty-mnti]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/maintainability.svg
1008
+ [🏀qlty-cov]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/metrics/code?sort=coverageRating
1009
+ [🏀qlty-covi]: https://qlty.sh/gh/ruby-oauth/projects/oauth2/coverage.svg
1010
+ [🏀codecov]: https://codecov.io/gh/ruby-oauth/oauth2
1011
+ [🏀codecovi]: https://codecov.io/gh/ruby-oauth/oauth2/graph/badge.svg
1012
+ [🏀coveralls]: https://coveralls.io/github/ruby-oauth/oauth2?branch=main
1013
+ [🏀coveralls-img]: https://coveralls.io/repos/github/ruby-oauth/oauth2/badge.svg?branch=main
1014
+ [🚎ruby-2.4-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-2.4.yml
1015
+ [🚎ruby-2.5-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-2.5.yml
1016
+ [🚎ruby-2.6-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-2.6.yml
1017
+ [🚎ruby-2.7-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-2.7.yml
1018
+ [🚎ruby-3.0-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-3.0.yml
1019
+ [🚎ruby-3.1-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-3.1.yml
1020
+ [🚎ruby-3.2-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-3.2.yml
1021
+ [🚎ruby-3.3-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-3.3.yml
1022
+ [🚎ruby-3.4-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/ruby-3.4.yml
1023
+ [🚎jruby-9.2-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby-9.2.yml
1024
+ [🚎jruby-9.3-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby-9.3.yml
1025
+ [🚎jruby-9.4-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby-9.4.yml
1026
+ [🚎jruby-10.0-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby-10.0.yml
1027
+ [🚎truby-22.3-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffleruby-22.3.yml
1028
+ [🚎truby-23.0-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffleruby-23.0.yml
1029
+ [🚎truby-23.1-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffleruby-23.1.yml
1030
+ [🚎truby-24.2-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffleruby-24.2.yml
1031
+ [🚎truby-25.0-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffleruby-25.0.yml
1032
+ [🚎truby-33.0-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffleruby-33.0.yml
1033
+ [🚎2-cov-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/coverage.yml
1034
+ [🚎2-cov-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/coverage.yml/badge.svg
1035
+ [🚎3-hd-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/heads.yml
1036
+ [🚎3-hd-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/heads.yml/badge.svg
1037
+ [🚎5-st-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/style.yml
1038
+ [🚎5-st-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/style.yml/badge.svg
1039
+ [🚎9-t-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml
1040
+ [🚎9-t-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/truffle.yml/badge.svg
1041
+ [🚎10-j-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml
1042
+ [🚎10-j-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/jruby.yml/badge.svg
1043
+ [🚎11-c-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml
1044
+ [🚎11-c-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/current.yml/badge.svg
1045
+ [🚎12-crh-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/dep-heads.yml
1046
+ [🚎12-crh-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/dep-heads.yml/badge.svg
1047
+ [🚎13-🔒️-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/locked_deps.yml
1048
+ [🚎13-🔒️-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/locked_deps.yml/badge.svg
1049
+ [🚎14-🔓️-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/unlocked_deps.yml
1050
+ [🚎14-🔓️-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/unlocked_deps.yml/badge.svg
1051
+ [🚎15-🪪-wf]: https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml
1052
+ [🚎15-🪪-wfi]: https://github.com/ruby-oauth/oauth2/actions/workflows/license-eye.yml/badge.svg
1053
+ [💎ruby-2.2i]: https://img.shields.io/badge/Ruby-2.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
1054
+ [💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
1055
+ [💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
1056
+ [💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
1057
+ [💎ruby-2.6i]: https://img.shields.io/badge/Ruby-2.6-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
1058
+ [💎ruby-2.7i]: https://img.shields.io/badge/Ruby-2.7-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
1059
+ [💎ruby-3.0i]: https://img.shields.io/badge/Ruby-3.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white
1060
+ [💎ruby-3.1i]: https://img.shields.io/badge/Ruby-3.1-CC342D?style=for-the-badge&logo=ruby&logoColor=white
1061
+ [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
1062
+ [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
1063
+ [💎ruby-3.4i]: https://img.shields.io/badge/Ruby-3.4-CC342D?style=for-the-badge&logo=ruby&logoColor=white
1064
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
1065
+ [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
1066
+ [💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
1067
+ [💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
1068
+ [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
1069
+ [💎truby-24.2i]: https://img.shields.io/badge/Truffle_Ruby-24.2-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
1070
+ [💎truby-25.0i]: https://img.shields.io/badge/Truffle_Ruby-25.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
1071
+ [💎truby-33.0i]: https://img.shields.io/badge/Truffle_Ruby-33.0-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
1072
+ [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
1073
+ [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
1074
+ [💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2-FBE742?style=for-the-badge&logo=ruby&logoColor=red
1075
+ [💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3-FBE742?style=for-the-badge&logo=ruby&logoColor=red
1076
+ [💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red
1077
+ [💎jruby-10.0i]: https://img.shields.io/badge/JRuby-10.0-FBE742?style=for-the-badge&logo=ruby&logoColor=red
1078
+ [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
1079
+ [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
1080
+ [🤝gh-issues]: https://github.com/ruby-oauth/oauth2/issues
1081
+ [🤝gh-pulls]: https://github.com/ruby-oauth/oauth2/pulls
1082
+ [🤝gl-issues]: https://gitlab.com/ruby-oauth/oauth2/-/issues
1083
+ [🤝gl-pulls]: https://gitlab.com/ruby-oauth/oauth2/-/merge_requests
1084
+ [🤝cb-issues]: https://codeberg.org/ruby-oauth/oauth2/issues
1085
+ [🤝cb-pulls]: https://codeberg.org/ruby-oauth/oauth2/pulls
1086
+ [🤝cb-donate]: https://donate.codeberg.org/
1087
+ [🤝contributing]: https://github.com/ruby-oauth/oauth2/blob/main/CONTRIBUTING.md
1088
+ [🏀codecov-g]: https://codecov.io/gh/ruby-oauth/oauth2/graph/badge.svg
1089
+ [🖐contrib-rocks]: https://contrib.rocks
1090
+ [🖐contributors]: https://github.com/ruby-oauth/oauth2/graphs/contributors
1091
+ [🖐contributors-img]: https://contrib.rocks/image?repo=ruby-oauth/oauth2
1092
+ [🚎contributors-gl]: https://gitlab.com/ruby-oauth/oauth2/-/graphs/main
1093
+ [🪇conduct]: https://github.com/ruby-oauth/oauth2/blob/main/CODE_OF_CONDUCT.md
1094
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
1095
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
1096
+ [📌semver]: https://semver.org/spec/v2.0.0.html
1097
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
1098
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
1099
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
1100
+ [📌changelog]: https://github.com/ruby-oauth/oauth2/blob/main/CHANGELOG.md
1101
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
1102
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
1103
+ [📌gitmoji]: https://gitmoji.dev
1104
+ [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
1105
+ [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
1106
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.558-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
1107
+ [🔐security]: https://github.com/ruby-oauth/oauth2/blob/main/SECURITY.md
1108
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
1109
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
1110
+ [📄license]: LICENSE.md
1111
+ [📄license-ref]: MIT.md
1112
+ [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
1113
+ [📄license-compat]: https://www.apache.org/legal/resolved.html#category-a
1114
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
1115
+
1116
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
1117
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
1118
+ [🚎yard-current]: http://rubydoc.info/gems/oauth2
1119
+ [🚎yard-head]: https://oauth2.galtzo.com
1120
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
1121
+ [💎SHA_checksums]: https://gitlab.com/ruby-oauth/oauth2/-/tree/main/checksums
1122
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
1123
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
1124
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
1125
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
1126
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
1127
+
1128
+ <!-- kettle-jem:metadata:start -->
1129
+ | Field | Value |
1130
+ |---|---|
1131
+ | Package | oauth2 |
1132
+ | Description | 🔐 A Ruby wrapper for the OAuth 2.0 Authorization Framework, including the OAuth 2.1 draft spec, and OpenID Connect (OIDC) |
1133
+ | Homepage | https://github.com/ruby-oauth/oauth2 |
1134
+ | Source | https://github.com/ruby-oauth/oauth2 |
1135
+ | License | `MIT` |
1136
+ | Funding | https://github.com/sponsors/pboling, https://ko-fi.com/pboling, https://liberapay.com/pboling/donate, https://opencollective.com/ruby-oauth, https://thanks.dev/u/gh/pboling, https://tidelift.com/funding/github/rubygems/oauth2, https://www.buymeacoffee.com/pboling |
1137
+ <!-- kettle-jem:metadata:end -->