Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
## [Unreleased]

### Added
- Added `AVAILABLE_SCOPES` constant with all supported X (Twitter) OAuth 2.0 scopes
- Added `DEFAULT_SCOPE` constant set to "tweet.read users.read"
- Added default `authorize_params` with scope configuration
- Added `authorize_params` method to handle scope validation and defaults
- Added comprehensive documentation for available scopes in README
Comment on lines +4 to +8
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Added `AVAILABLE_SCOPES` constant with all supported X (Twitter) OAuth 2.0 scopes
- Added `DEFAULT_SCOPE` constant set to "tweet.read users.read"
- Added default `authorize_params` with scope configuration
- Added `authorize_params` method to handle scope validation and defaults
- Added comprehensive documentation for available scopes in README
- Add OAuth2 scope constants and validation ([#8](https://github.com/unasuke/omniauth-twitter2/pull/8))
- Added `AVAILABLE_SCOPES` constant with all supported X (Twitter) OAuth 2.0 scopes
- Added `DEFAULT_SCOPE` constant set to "tweet.read users.read"
- Added default `authorize_params` with scope configuration
- Added `authorize_params` method to handle scope validation and defaults
- Added comprehensive documentation for available scopes in README


### Changed
- Scope parameter is now explicitly handled with defaults and validation
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Scope parameter is now explicitly handled with defaults and validation
- Add OAuth2 scope constants and validation ([#8](https://github.com/unasuke/omniauth-twitter2/pull/8))
- Scope parameter is now explicitly handled with defaults and validation


## [1.0.0] - 2025-08-03

- Update api endpoints from `twitter.com` to `x.com` ([#7](https://github.com/unasuke/omniauth-twitter2/pull/7))
Expand Down
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,46 @@ $ gem install omniauth-twitter2
```ruby
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter2, ENV["TWITTER_CLIENT_ID"], ENV["TWITTER_CLIENT_SECRET"], callback_path: '/auth/twitter2/callback', scope: "tweet.read users.read"
provider :twitter2, ENV["TWITTER_CLIENT_ID"], ENV["TWITTER_CLIENT_SECRET"],
callback_path: '/auth/twitter2/callback',
scope: "tweet.read users.read" # Default scope
end
```

### Available Scopes

The following OAuth 2.0 scopes are available for X (Twitter) API v2:

- `tweet.read` - All the Tweets you can view, including Tweets from protected accounts.
- `tweet.write` - Tweet and Retweet for you.
- `tweet.moderate.write` - Hide and unhide replies to your Tweets.
- `users.email` - Email from an authenticated user.
- `users.read` - Any account you can view, including protected accounts.
- `follows.read` - People who follow you and people who you follow.
- `follows.write` - Follow and unfollow people for you.
- `offline.access` - Stay connected to your account until you revoke access.
- `space.read` - All the Spaces you can view.
- `mute.read` - Accounts you've muted.
- `mute.write` - Mute and unmute accounts for you.
- `like.read` - Tweets you've liked and likes you can view.
- `like.write` - Like and un-like Tweets for you.
- `list.read` - Lists, list members, and list followers of lists you've created or are a member of, including private lists.
- `list.write` - Create and manage Lists for you.
- `block.read` - Accounts you've blocked.
- `block.write` - Block and unblock accounts for you.
- `bookmark.read` - Get Bookmarked Tweets from an authenticated user.
- `bookmark.write` - Bookmark and remove Bookmarks from Tweets.
- `media.write` - Upload media.

Default scope is `"tweet.read users.read"` if not specified.

You can customize the scope like this:

```ruby
Rails.application.config.middleware.use OmniAuth::Builder do
provider :twitter2, ENV["TWITTER_CLIENT_ID"], ENV["TWITTER_CLIENT_SECRET"],
callback_path: '/auth/twitter2/callback',
scope: "tweet.read tweet.write users.read offline.access"
end
```

Expand Down
36 changes: 36 additions & 0 deletions lib/omniauth/strategies/twitter2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
module OmniAuth
module Strategies
class Twitter2 < OmniAuth::Strategies::OAuth2 # :nodoc:
# Available OAuth 2.0 scopes for X (Twitter) API v2
# Reference: https://developer.x.com/en/docs/authentication/oauth-2-0/authorization-code
AVAILABLE_SCOPES = %w[
tweet.read tweet.write tweet.moderate.write
users.email users.read
follows.read follows.write
offline.access space.read
mute.read mute.write
like.read like.write
list.read list.write
block.read block.write
bookmark.read bookmark.write
media.write
].freeze

DEFAULT_SCOPE = "tweet.read users.read"

option :name, "twitter2"
# https://docs.x.com/fundamentals/authentication/oauth-2-0/overview
option :client_options, {
Expand All @@ -14,6 +31,9 @@ class Twitter2 < OmniAuth::Strategies::OAuth2 # :nodoc:
authorize_url: "https://x.com/i/oauth2/authorize"
}
option :pkce, true
option :authorize_params, {
scope: DEFAULT_SCOPE
}

uid { raw_info["data"]["id"] }

Expand Down Expand Up @@ -47,6 +67,22 @@ def raw_info
# https://github.com/zquestz/omniauth-google-oauth2/blob/475efe41ecfcf04b63921bd723ccf6fad429d1b1/lib/omniauth/strategies/google_oauth2.rb#L105
# https://github.com/simi/omniauth-facebook/blob/e1e572db2e9464871c98148621df1bbbe1e9f9c3/lib/omniauth/strategies/facebook.rb#L88
# https://github.com/omniauth/omniauth-oauth2/commit/85fdbe117c2a4400d001a6368cc359d88f40abc7
def authorize_params
super.tap do |params|
# Ensure scope is included in authorize params
params[:scope] ||= options[:authorize_params][:scope] || DEFAULT_SCOPE

# Validate scopes if provided
if params[:scope]
requested_scopes = params[:scope].split
invalid_scopes = requested_scopes - AVAILABLE_SCOPES
if !invalid_scopes.empty? && defined?(Rails)
Rails.logger.warn "Invalid Twitter OAuth2 scopes requested: #{invalid_scopes.join(", ")}"
end
end
end
end

def callback_url
options[:callback_url] || (full_host + script_name + callback_path)
end
Expand Down
38 changes: 38 additions & 0 deletions test/omniauth/test_twitter2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,42 @@ def test_it_has_uid
assert_equal "108252390", subject.uid
end
end

def test_it_has_available_scopes_constant
assert_equal 20, OmniAuth::Strategies::Twitter2::AVAILABLE_SCOPES.length
assert_includes OmniAuth::Strategies::Twitter2::AVAILABLE_SCOPES, "tweet.read"
assert_includes OmniAuth::Strategies::Twitter2::AVAILABLE_SCOPES, "users.read"
assert_includes OmniAuth::Strategies::Twitter2::AVAILABLE_SCOPES, "users.email"
assert_includes OmniAuth::Strategies::Twitter2::AVAILABLE_SCOPES, "media.write"
assert_includes OmniAuth::Strategies::Twitter2::AVAILABLE_SCOPES, "offline.access"
end

def test_it_has_default_scope
assert_equal "tweet.read users.read", OmniAuth::Strategies::Twitter2::DEFAULT_SCOPE
end

def test_it_has_default_authorize_params
subject = strategy
assert_equal "tweet.read users.read", subject.options.authorize_params[:scope]
end

def test_authorize_params_includes_scope
subject = strategy
params = subject.authorize_params
assert_equal "tweet.read users.read", params[:scope]
end

def test_authorize_params_with_custom_scope
subject = strategy(authorize_params: { scope: "tweet.read tweet.write users.read" })
params = subject.authorize_params
assert_equal "tweet.read tweet.write users.read", params[:scope]
end

def test_authorize_params_validates_scopes
# This test ensures that invalid scopes are handled gracefully
subject = strategy(authorize_params: { scope: "invalid.scope tweet.read" })
params = subject.authorize_params
# The method should still return params even with invalid scopes (just warn)
assert_equal "invalid.scope tweet.read", params[:scope]
end
end