Skip to content

Commit e11cec5

Browse files
committed
📝 (more) Comprehensive documentation / examples
1 parent 193c78e commit e11cec5

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
99
- [gh656][gh656] - Support revocation with URL-encoded parameters
1010
- [gh660][gh660] - Inline yard documentation by @pboling
1111
- [gh660][gh660] - Complete RBS types documentation by @pboling
12+
- [gh660][gh660]- (more) Comprehensive documentation / examples by @pboling
1213
- [gh657][gh657] - Updated documentation for org-rename by @pboling
1314
- More funding links by @Aboling0
1415
### Changed

README.md

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,206 @@ access = client.auth_code.get_token("code_value", redirect_uri: "http://localhos
631631
You can always use the `#request` method on the `OAuth2::Client` instance to make
632632
requests for tokens for any Authentication grant type.
633633

634+
## 📘 Comprehensive Usage
635+
636+
### Common Flows (end-to-end)
637+
638+
- Authorization Code (server-side web app):
639+
640+
```ruby
641+
require "oauth2"
642+
client = OAuth2::Client.new(
643+
ENV["CLIENT_ID"],
644+
ENV["CLIENT_SECRET"],
645+
site: "https://provider.example.com",
646+
redirect_uri: "https://my.app.example.com/oauth/callback",
647+
)
648+
649+
# Step 1: redirect user to consent
650+
state = SecureRandom.hex(16)
651+
auth_url = client.auth_code.authorize_url(scope: "openid profile email", state: state)
652+
# redirect_to auth_url
653+
654+
# Step 2: handle the callback
655+
# params[:code], params[:state]
656+
raise "state mismatch" unless params[:state] == state
657+
access = client.auth_code.get_token(params[:code])
658+
659+
# Step 3: call APIs
660+
profile = access.get("/api/v1/me").parsed
661+
```
662+
663+
- Client Credentials (machine-to-machine):
664+
665+
```ruby
666+
client = OAuth2::Client.new(ENV["CLIENT_ID"], ENV["CLIENT_SECRET"], site: "https://provider.example.com")
667+
access = client.client_credentials.get_token(audience: "https://api.example.com")
668+
resp = access.get("/v1/things")
669+
```
670+
671+
- Resource Owner Password (legacy; avoid when possible):
672+
673+
```ruby
674+
access = client.password.get_token("jdoe", "s3cret", scope: "read")
675+
```
676+
677+
### Refresh Tokens
678+
679+
When the server issues a refresh_token, you can refresh manually or implement an auto-refresh wrapper.
680+
681+
- Manual refresh:
682+
683+
```ruby
684+
if access.expired?
685+
access = access.refresh
686+
end
687+
```
688+
689+
- Auto-refresh wrapper pattern:
690+
691+
```ruby
692+
class AutoRefreshingToken
693+
def initialize(token_provider, store: nil)
694+
@token = token_provider
695+
@store = store # e.g., something that responds to read/write for token data
696+
end
697+
698+
def with(&blk)
699+
tok = ensure_fresh!
700+
blk ? blk.call(tok) : tok
701+
rescue OAuth2::Error => e
702+
# If a 401 suggests token invalidation, try one refresh and retry once
703+
if e.response && e.response.status == 401 && @token.refresh_token
704+
@token = @token.refresh
705+
@store.write(@token.to_hash) if @store
706+
retry
707+
end
708+
raise
709+
end
710+
711+
private
712+
713+
def ensure_fresh!
714+
if @token.expired? && @token.refresh_token
715+
@token = @token.refresh
716+
@store.write(@token.to_hash) if @store
717+
end
718+
@token
719+
end
720+
end
721+
722+
# usage
723+
keeper = AutoRefreshingToken.new(access)
724+
keeper.with { |tok| tok.get("/v1/protected") }
725+
```
726+
727+
Persist the token across processes using `AccessToken#to_hash` and `AccessToken.from_hash(client, hash)`.
728+
729+
### Token Revocation (RFC 7009)
730+
731+
You can revoke either the access token or the refresh token.
732+
733+
```ruby
734+
# Revoke the current access token
735+
access.revoke(token_type_hint: :access_token)
736+
737+
# Or explicitly revoke the refresh token (often also invalidates associated access tokens)
738+
access.revoke(token_type_hint: :refresh_token)
739+
```
740+
741+
### Client Configuration Tips
742+
743+
- Authentication schemes for the token request:
744+
745+
```ruby
746+
OAuth2::Client.new(
747+
id,
748+
secret,
749+
site: "https://provider.example.com",
750+
auth_scheme: :basic_auth, # default. Alternatives: :request_body, :tls_client_auth, :private_key_jwt
751+
)
752+
```
753+
754+
- Faraday connection, timeouts, proxy, custom adapter/middleware:
755+
756+
```ruby
757+
client = OAuth2::Client.new(
758+
id,
759+
secret,
760+
site: "https://provider.example.com",
761+
connection_opts: {
762+
request: {open_timeout: 5, timeout: 15},
763+
proxy: ENV["HTTPS_PROXY"],
764+
ssl: {verify: true},
765+
},
766+
) do |faraday|
767+
faraday.request(:url_encoded)
768+
# faraday.response :logger, Logger.new($stdout) # see OAUTH_DEBUG below
769+
faraday.adapter(:net_http_persistent) # or any Faraday adapter you need
770+
end
771+
```
772+
773+
- Redirection: The library follows up to `max_redirects` (default 5). You can override per-client via `options[:max_redirects]`.
774+
775+
### Handling Responses and Errors
776+
777+
- Parsing:
778+
779+
```ruby
780+
resp = access.get("/v1/thing")
781+
resp.status # Integer
782+
resp.headers # Hash
783+
resp.body # String
784+
resp.parsed # SnakyHash::StringKeyed or Array when JSON array
785+
```
786+
787+
- Error handling:
788+
789+
```ruby
790+
begin
791+
access.get("/v1/forbidden")
792+
rescue OAuth2::Error => e
793+
e.code # OAuth2 error code (when present)
794+
e.description # OAuth2 error description (when present)
795+
e.response # OAuth2::Response (full access to status/headers/body)
796+
end
797+
```
798+
799+
- Disable raising on 4xx/5xx to inspect the response yourself:
800+
801+
```ruby
802+
client = OAuth2::Client.new(id, secret, site: site, raise_errors: false)
803+
res = client.request(:get, "/v1/maybe-errors")
804+
if res.status == 429
805+
sleep res.headers["retry-after"].to_i
806+
end
807+
```
808+
809+
### Making Raw Token Requests
810+
811+
If a provider requires non-standard parameters or headers, you can call `client.get_token` directly:
812+
813+
```ruby
814+
access = client.get_token({
815+
grant_type: "client_credentials",
816+
audience: "https://api.example.com",
817+
headers: {"X-Custom" => "value"},
818+
parse: :json, # override parsing
819+
})
820+
```
821+
822+
### OpenID Connect (OIDC) Notes
823+
824+
- 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.
825+
- For private_key_jwt client authentication, provide `auth_scheme: :private_key_jwt` and ensure your key configuration matches the provider requirements.
826+
827+
### Debugging
828+
829+
- Set environment variable `OAUTH_DEBUG=true` to enable verbose Faraday logging (uses the client-provided logger).
830+
- To mirror a working curl request, ensure you set the same auth scheme, params, and content type. The Quick Example at the top shows a curl-to-ruby translation.
831+
832+
---
833+
634834
## 🦷 FLOSS Funding
635835

636836
While ruby-oauth tools are free software and will always be, the project would benefit immensely from some funding.

0 commit comments

Comments
 (0)