Skip to content

Commit dea4d16

Browse files
authored
Merge pull request #663 from ruby-oauth/oidc
2 parents 8baff19 + f4e2ba3 commit dea4d16

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+525
-89
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- avoid bearer tokens in query,
1515
- refresh token guidance for public clients,
1616
- simplified client definitions)
17+
- document how to implement an OIDC client with this gem in OIDC.md
18+
- also, list libraries built on top of the oauth2 gem that implement OIDC
1719
### Changed
1820
### Deprecated
1921
### Removed
@@ -32,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3234
- [gh660][gh660]- (more) Comprehensive documentation / examples by @pboling
3335
- [gh657][gh657] - Updated documentation for org-rename by @pboling
3436
- More funding links by @Aboling0
37+
- Documentation: Added docs/OIDC.md with OIDC 1.0 overview, example, and references
3538
### Changed
3639
- Upgrade Code of Conduct to Contributor Covenant 2.1 by @pboling
3740
- [gh660][gh660] - Shrink post-install message by 4 lines by @pboling

OIDC.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# OpenID Connect (OIDC) with ruby-oauth/oauth2
2+
3+
## OIDC Libraries
4+
5+
Libraries built on top of the oauth2 gem that implement OIDC.
6+
7+
- [gamora](https://github.com/amco/gamora-rb) - OpenID Connect Relying Party for Rails apps
8+
- [omniauth-doximity-oauth2](https://github.com/doximity/omniauth-doximity-oauth2) - OmniAuth strategy for Doximity, supporting OIDC, and using PKCE
9+
- [omniauth-himari](https://github.com/sorah/himari) - OmniAuth strategy to act as OIDC RP and use [Himari](https://github.com/sorah/himari) for OP
10+
- [omniauth-mit-oauth2](https://github.com/MITLibraries/omniauth-mit-oauth2) - OmniAuth strategy for MIT OIDC
11+
12+
If any other libraries would like to be added to this list, please open an issue or pull request.
13+
14+
## Raw OIDC with ruby-oauth/oauth2
15+
16+
This document complements the inline documentation by focusing on OpenID Connect (OIDC) 1.0 usage patterns when using this gem as an OAuth 2.0 client library.
17+
18+
Scope of this document
19+
- Audience: Developers building an OAuth 2.0/OIDC Relying Party (RP, aka client) in Ruby.
20+
- Non-goals: This gem does not implement an OIDC Provider (OP, aka Authorization Server); for OP/server see other projects (e.g., doorkeeper + oidc extensions).
21+
- Status: Informational documentation with links to normative specs. The gem intentionally remains protocol-agnostic beyond OAuth 2.0; OIDC specifics (like ID Token validation) must be handled by your application.
22+
23+
Key concepts refresher
24+
- OAuth 2.0 delegates authorization; it does not define authentication of the end-user.
25+
- OIDC layers an identity layer on top of OAuth 2.0, introducing:
26+
- ID Token: a JWT carrying claims about the authenticated end-user and the authentication event.
27+
- Standardized scopes: openid (mandatory), profile, email, address, phone, offline_access, and others.
28+
- UserInfo endpoint: a protected resource for retrieving user profile claims.
29+
- Discovery and Dynamic Client Registration (optional for providers/clients that support them).
30+
31+
What this gem provides for OIDC
32+
- All OAuth 2.0 client capabilities required for OIDC flows: building authorization requests, exchanging authorization codes, refreshing tokens, and making authenticated resource requests.
33+
- Transport and parsing conveniences (snaky hash, Faraday integration, error handling, etc.).
34+
- Optional client authentication schemes useful with OIDC deployments:
35+
- basic_auth (default)
36+
- request_body (legacy)
37+
- tls_client_auth (MTLS)
38+
- private_key_jwt (OIDC-compliant when configured per OP requirements)
39+
40+
What you must add in your app for OIDC
41+
- ID Token validation: This gem surfaces id_token values but does not verify them. Your app should:
42+
1) Parse the JWT (header, payload, signature)
43+
2) Fetch the OP JSON Web Key Set (JWKS) from discovery (or configure statically)
44+
3) Select the correct key by kid (when present) and verify the signature and algorithm
45+
4) Validate standard claims (iss, aud, exp, iat, nbf, azp, nonce when used, at_hash/c_hash when applicable)
46+
5) Enforce expected client_id, issuer, and clock skew policies
47+
- Nonce handling for Authorization Code flow with OIDC: generate a cryptographically-random nonce, bind it to the user session before redirect, include it in authorize request, and verify it in the ID Token on return.
48+
- PKCE is best practice and often required by OPs: generate/verifier, send challenge in authorize, send verifier in token request.
49+
- Session/state management: continue to validate state to mitigate CSRF; use exact redirect_uri matching.
50+
51+
Minimal OIDC Authorization Code example
52+
53+
```ruby
54+
require "oauth2"
55+
require "jwt" # jwt/ruby-jwt
56+
require "net/http"
57+
require "json"
58+
59+
client = OAuth2::Client.new(
60+
ENV.fetch("OIDC_CLIENT_ID"),
61+
ENV.fetch("OIDC_CLIENT_SECRET"),
62+
site: ENV.fetch("OIDC_ISSUER"), # e.g. https://accounts.example.com
63+
authorize_url: "/authorize", # or discovered
64+
token_url: "/token", # or discovered
65+
)
66+
67+
# Step 1: Redirect to OP for consent/auth
68+
state = SecureRandom.hex(16)
69+
nonce = SecureRandom.hex(16)
70+
pkce_verifier = SecureRandom.urlsafe_base64(64)
71+
pkce_challenge = Base64.urlsafe_encode64(Digest::SHA256.digest(pkce_verifier)).delete("=")
72+
73+
authz_url = client.auth_code.authorize_url(
74+
scope: "openid profile email",
75+
state: state,
76+
nonce: nonce,
77+
code_challenge: pkce_challenge,
78+
code_challenge_method: "S256",
79+
redirect_uri: ENV.fetch("OIDC_REDIRECT_URI"),
80+
)
81+
# redirect_to authz_url
82+
83+
# Step 2: Handle callback
84+
# params[:code], params[:state]
85+
raise "state mismatch" unless params[:state] == state
86+
87+
token = client.auth_code.get_token(
88+
params[:code],
89+
redirect_uri: ENV.fetch("OIDC_REDIRECT_URI"),
90+
code_verifier: pkce_verifier,
91+
)
92+
93+
# The token may include: access_token, id_token, refresh_token, etc.
94+
id_token = token.params["id_token"] || token.params[:id_token]
95+
96+
# Step 3: Validate the ID Token (simplified – add your own checks!)
97+
# Discover keys (example using .well-known)
98+
issuer = ENV.fetch("OIDC_ISSUER")
99+
jwks_uri = JSON.parse(Net::HTTP.get(URI.join(issuer, "/.well-known/openid-configuration"))).
100+
fetch("jwks_uri")
101+
jwks = JSON.parse(Net::HTTP.get(URI(jwks_uri)))
102+
keys = jwks.fetch("keys")
103+
104+
# Use ruby-jwt JWK loader
105+
jwk_set = JWT::JWK::Set.new(keys.map { |k| JWT::JWK.import(k) })
106+
107+
decoded, headers = JWT.decode(
108+
id_token,
109+
nil,
110+
true,
111+
algorithms: ["RS256", "ES256", "PS256"],
112+
jwks: jwk_set,
113+
verify_iss: true,
114+
iss: issuer,
115+
verify_aud: true,
116+
aud: ENV.fetch("OIDC_CLIENT_ID"),
117+
)
118+
119+
# Verify nonce
120+
raise "nonce mismatch" unless decoded["nonce"] == nonce
121+
122+
# Optionally: call UserInfo
123+
userinfo = token.get("/userinfo").parsed
124+
```
125+
126+
Notes on discovery and registration
127+
- Discovery: Most OPs publish configuration at {issuer}/.well-known/openid-configuration (OIDC Discovery 1.0). From there, resolve authorization_endpoint, token_endpoint, jwks_uri, userinfo_endpoint, etc.
128+
- Dynamic Client Registration: Some OPs allow registering clients programmatically (OIDC Dynamic Client Registration 1.0). This gem does not implement registration; use a plain HTTP client or Faraday and store credentials securely.
129+
130+
Common pitfalls and tips
131+
- Always request the openid scope when you expect an ID Token. Without it, the OP may behave as vanilla OAuth 2.0.
132+
- Validate ID Token signature and claims before trusting any identity data. Do not rely solely on the presence of an id_token field.
133+
- Prefer Authorization Code + PKCE. Avoid Implicit; it is discouraged in modern guidance and may be disabled by providers.
134+
- Use exact redirect_uri matching, and keep your allow-list short.
135+
- For public clients that use refresh tokens, prefer sender-constrained tokens (DPoP/MTLS) or rotation with one-time-use refresh tokens, per modern best practices.
136+
- When using private_key_jwt, ensure the "aud" (or token_url) and "iss/sub" claims are set per the OP’s rules, and include kid in the JWT header when required so the OP can select the right key.
137+
138+
Relevant specifications and references
139+
- OpenID Connect Core 1.0: https://openid.net/specs/openid-connect-core-1_0.html
140+
- OIDC Core (final): https://openid.net/specs/openid-connect-core-1_0-final.html
141+
- How OIDC works: https://openid.net/developers/how-connect-works/
142+
- OpenID Connect home: https://openid.net/connect/
143+
- OIDC Discovery 1.0: https://openid.net/specs/openid-connect-discovery-1_0.html
144+
- OIDC Dynamic Client Registration 1.0: https://openid.net/specs/openid-connect-registration-1_0.html
145+
- OIDC Session Management 1.0: https://openid.net/specs/openid-connect-session-1_0.html
146+
- OIDC RP-Initiated Logout 1.0: https://openid.net/specs/openid-connect-rpinitiated-1_0.html
147+
- OIDC Back-Channel Logout 1.0: https://openid.net/specs/openid-connect-backchannel-1_0.html
148+
- OIDC Front-Channel Logout 1.0: https://openid.net/specs/openid-connect-frontchannel-1_0.html
149+
- Auth0 OIDC overview: https://auth0.com/docs/authenticate/protocols/openid-connect-protocol
150+
- Spring Authorization Server’s list of OAuth2/OIDC specs: https://github.com/spring-projects/spring-authorization-server/wiki/OAuth2-and-OIDC-Specifications
151+
152+
See also
153+
- README sections on OAuth 2.1 notes and OIDC notes
154+
- Strategy classes under lib/oauth2/strategy for flow helpers
155+
- Specs under spec/oauth2 for concrete usage patterns
156+
157+
Contributions welcome
158+
- If you discover provider-specific nuances, consider contributing examples or clarifications (without embedding provider-specific hacks into the library).

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,7 @@ access = client.get_token({
947947

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

951952
### Debugging
952953

docs/OAuth2.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ <h3 class="signature first" id="configure-class_method">
415415
</div>
416416

417417
<div id="footer">
418-
Generated on Sun Aug 31 03:36:08 2025 by
418+
Generated on Sun Aug 31 04:15:43 2025 by
419419
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
420420
0.9.37 (ruby-3.4.5).
421421
</div>

docs/OAuth2/AccessToken.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3069,7 +3069,7 @@ <h3 class="signature " id="to_hash-instance_method">
30693069
</div>
30703070

30713071
<div id="footer">
3072-
Generated on Sun Aug 31 03:36:08 2025 by
3072+
Generated on Sun Aug 31 04:15:43 2025 by
30733073
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
30743074
0.9.37 (ruby-3.4.5).
30753075
</div>

docs/OAuth2/Authenticator.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,7 +883,7 @@ <h3 class="signature first" id="apply-instance_method">
883883
</div>
884884

885885
<div id="footer">
886-
Generated on Sun Aug 31 03:36:08 2025 by
886+
Generated on Sun Aug 31 04:15:43 2025 by
887887
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
888888
0.9.37 (ruby-3.4.5).
889889
</div>

docs/OAuth2/Client.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2656,7 +2656,7 @@ <h3 class="signature " id="token_url-instance_method">
26562656
</div>
26572657

26582658
<div id="footer">
2659-
Generated on Sun Aug 31 03:36:08 2025 by
2659+
Generated on Sun Aug 31 04:15:43 2025 by
26602660
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
26612661
0.9.37 (ruby-3.4.5).
26622662
</div>

docs/OAuth2/Error.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,7 @@ <h3 class="signature " id="response-instance_method">
772772
</div>
773773

774774
<div id="footer">
775-
Generated on Sun Aug 31 03:36:08 2025 by
775+
Generated on Sun Aug 31 04:15:43 2025 by
776776
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
777777
0.9.37 (ruby-3.4.5).
778778
</div>

docs/OAuth2/FilteredAttributes.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ <h3 class="signature first" id="inspect-instance_method">
335335
</div>
336336

337337
<div id="footer">
338-
Generated on Sun Aug 31 03:36:08 2025 by
338+
Generated on Sun Aug 31 04:15:43 2025 by
339339
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
340340
0.9.37 (ruby-3.4.5).
341341
</div>

docs/OAuth2/FilteredAttributes/ClassMethods.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ <h3 class="signature " id="filtered_attributes-instance_method">
280280
</div>
281281

282282
<div id="footer">
283-
Generated on Sun Aug 31 03:36:08 2025 by
283+
Generated on Sun Aug 31 04:15:43 2025 by
284284
<a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
285285
0.9.37 (ruby-3.4.5).
286286
</div>

0 commit comments

Comments
 (0)