Skip to content

Commit 8a2f848

Browse files
committed
📝 Complete Instagram example
1 parent 6ca3ae7 commit 8a2f848

File tree

4 files changed

+91
-6
lines changed

4 files changed

+91
-6
lines changed

.rubocop_gradual.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"lib/oauth2.rb:2435263975": [
77
[73, 11, 7, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 651502127]
88
],
9-
"lib/oauth2/access_token.rb:1775225572": [
9+
"lib/oauth2/access_token.rb:3678262936": [
1010
[64, 13, 5, "Style/IdenticalConditionalBranches: Move `t_key` out of the conditional.", 183811513],
1111
[70, 13, 5, "Style/IdenticalConditionalBranches: Move `t_key` out of the conditional.", 183811513]
1212
],
@@ -21,11 +21,11 @@
2121
"lib/oauth2/response.rb:2054901929": [
2222
[53, 5, 204, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 996912427]
2323
],
24-
"spec/oauth2/access_token_spec.rb:1202129469": [
24+
"spec/oauth2/access_token_spec.rb:373808463": [
2525
[3, 1, 34, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/access_token*_spec.rb`.", 1972107547],
26-
[789, 13, 25, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 770233088],
27-
[859, 9, 101, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3022740639],
28-
[863, 9, 79, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2507338967]
26+
[809, 13, 25, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 770233088],
27+
[879, 9, 101, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3022740639],
28+
[883, 9, 79, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2507338967]
2929
],
3030
"spec/oauth2/authenticator_spec.rb:853320290": [
3131
[3, 1, 36, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/authenticator*_spec.rb`.", 819808017],

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Please file a bug if you notice a violation of semantic versioning.
2222
- note lack of builds for JRuby 9.2, 9.3 & Truffleruby 22.3, 23.0
2323
- [actions/runner - issues/2347][GHA-continue-on-error-ui]
2424
- [community/discussions/15452][GHA-allow-failure]
25+
- [gh!670][gh!670] - AccessToken: verb-dependent token transmission mode by @mrj
26+
- e.g., Instagram GET=:query, POST/DELETE=:header
2527
### Changed
2628
- [gh!669][gh!669] - Upgrade to kettle-dev v1.1.9
2729
### Deprecated
@@ -32,6 +34,7 @@ Please file a bug if you notice a violation of semantic versioning.
3234
### Security
3335

3436
[gh!669]: https://github.com/ruby-oauth/oauth2/pull/669
37+
[gh!670]: https://github.com/ruby-oauth/oauth2/pull/670
3538
[GHA-continue-on-error-ui]: https://github.com/actions/runner/issues/2347
3639
[GHA-allow-failure]: https://github.com/orgs/community/discussions/15452
3740

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,18 @@ using various class methods including the standard new, `from_hash` (if you have
683683
a hash of the values), or `from_kvform` (if you have an
684684
`application/x-www-form-urlencoded` encoded string of the values).
685685

686+
Options (since v2.0.x unless noted):
687+
- expires_latency (Integer | nil): Seconds to subtract from expires_in when computing #expired? to offset latency.
688+
- 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).
689+
- mode (Symbol | Proc | Hash): Controls how the token is transmitted on requests made via this AccessToken instance.
690+
- :header — Send as Authorization: Bearer <token> header (default and preferred by OAuth 2.1 draft guidance).
691+
- :query — Send as access_token query parameter (discouraged in general, but required by some providers).
692+
- Verb-dependent (since v2.0.15): Provide either:
693+
- a Proc taking |verb| and returning :header or :query, or
694+
- a Hash with verb symbols as keys, for example: {get: :query, post: :header, delete: :header}.
695+
696+
Note: Verb-dependent mode was added in v2.0.15 to support providers like Instagram that require query mode for GET and header mode for POST/DELETE.
697+
686698
### OAuth2::Error
687699

688700
On 400+ status code responses, an `OAuth2::Error` will be raised. If it is a
@@ -852,6 +864,76 @@ Notes:
852864

853865
</details>
854866

867+
### Instagram API (verb‑dependent token mode)
868+
869+
Providers like Instagram require the access token to be sent differently depending on the HTTP verb:
870+
- GET requests: token must be in the query string (?access_token=...)
871+
- POST/DELETE requests: token must be in the Authorization header (Bearer ...)
872+
873+
Since v2.0.15, you can configure an AccessToken with a verb‑dependent mode. The gem will choose how to send the token based on the request method.
874+
875+
Example: exchanging and refreshing long‑lived Instagram tokens, and making API calls
876+
877+
```ruby
878+
require "oauth2"
879+
880+
# NOTE: Users authenticate via Facebook Login to obtain a short‑lived user token (not shown here).
881+
# See Facebook Login docs for obtaining the initial short‑lived token.
882+
883+
client = OAuth2::Client.new(nil, nil, site: "https://graph.instagram.com")
884+
885+
# Start with a short‑lived token you already obtained via Facebook Login
886+
short_lived = OAuth2::AccessToken.new(
887+
client,
888+
ENV["IG_SHORT_LIVED_TOKEN"],
889+
# Key part: verb‑dependent mode
890+
mode: {get: :query, post: :header, delete: :header},
891+
)
892+
893+
# 1) Exchange for a long‑lived token (Instagram requires GET with access_token in query)
894+
# Endpoint: GET https://graph.instagram.com/access_token
895+
# Params: grant_type=ig_exchange_token, client_secret=APP_SECRET
896+
exchange = short_lived.get(
897+
"/access_token",
898+
params: {
899+
grant_type: "ig_exchange_token",
900+
client_secret: ENV["IG_APP_SECRET"],
901+
# access_token param will be added automatically by the AccessToken (mode => :query for GET)
902+
},
903+
)
904+
long_lived_token_value = exchange.parsed["access_token"]
905+
906+
long_lived = OAuth2::AccessToken.new(
907+
client,
908+
long_lived_token_value,
909+
mode: {get: :query, post: :header, delete: :header},
910+
)
911+
912+
# 2) Refresh the long‑lived token (Instagram uses GET with token in query)
913+
# Endpoint: GET https://graph.instagram.com/refresh_access_token
914+
refresh_resp = long_lived.get(
915+
"/refresh_access_token",
916+
params: {grant_type: "ig_refresh_token"},
917+
)
918+
long_lived = OAuth2::AccessToken.new(
919+
client,
920+
refresh_resp.parsed["access_token"],
921+
mode: {get: :query, post: :header, delete: :header},
922+
)
923+
924+
# 3) Typical API GET request (token in query automatically)
925+
me = long_lived.get("/me", params: {fields: "id,username"}).parsed
926+
927+
# 4) Example POST (token sent via Bearer header automatically)
928+
# Note: Replace the path/params with a real Instagram Graph API POST you need,
929+
# such as publishing media via the Graph API endpoints.
930+
# long_lived.post("/me/media", body: {image_url: "https://...", caption: "hello"})
931+
```
932+
933+
Tips:
934+
- Avoid query‑string bearer tokens unless required by your provider. Instagram explicitly requires it for GET.
935+
- If you need a custom rule, you can pass a Proc for mode, e.g. mode: ->(verb) { verb == :get ? :query : :header }.
936+
855937
### Refresh Tokens
856938

857939
When the server issues a refresh_token, you can refresh manually or implement an auto-refresh wrapper.

spec/oauth2/access_token_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ def assert_initialized_token(target)
423423
end
424424

425425
let(:options) { {mode: mode} }
426-
426+
427427
VERBS.each do |verb|
428428
it "correctly handles a #{verb.to_s.upcase}" do
429429
expect(subject.__send__(verb, "/token/#{mode.call(verb)}").body).to include(token)

0 commit comments

Comments
 (0)