Skip to content

Commit f94765f

Browse files
authored
add support for custom oauth scopes (#379)
* add support for oauth_tokens in auth response * add support for provider_scopes parameter
1 parent 443e4d1 commit f94765f

File tree

6 files changed

+180
-2
lines changed

6 files changed

+180
-2
lines changed

lib/workos.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def self.key
6363
autoload :Invitation, 'workos/invitation'
6464
autoload :MagicAuth, 'workos/magic_auth'
6565
autoload :MFA, 'workos/mfa'
66+
autoload :OAuthTokens, 'workos/oauth_tokens'
6667
autoload :Organization, 'workos/organization'
6768
autoload :Organizations, 'workos/organizations'
6869
autoload :OrganizationMembership, 'workos/organization_membership'

lib/workos/authentication_response.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ class AuthenticationResponse
1212
:access_token,
1313
:refresh_token,
1414
:authentication_method,
15-
:sealed_session
15+
:sealed_session,
16+
:oauth_tokens
1617

1718
# rubocop:disable Metrics/AbcSize
1819
def initialize(authentication_response_json, session = nil)
@@ -27,6 +28,7 @@ def initialize(authentication_response_json, session = nil)
2728
reason: impersonator_json[:reason],)
2829
end
2930
@authentication_method = json[:authentication_method]
31+
@oauth_tokens = json[:oauth_tokens] ? WorkOS::OAuthTokens.new(json[:oauth_tokens].to_json) : nil
3032
@sealed_session =
3133
if session && session[:seal_session]
3234
WorkOS::Session.seal_data({
@@ -49,6 +51,7 @@ def to_json(*)
4951
refresh_token: refresh_token,
5052
authentication_method: authentication_method,
5153
sealed_session: sealed_session,
54+
oauth_tokens: oauth_tokens&.to_json,
5255
}
5356
end
5457
end

lib/workos/oauth_tokens.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
module WorkOS
4+
# The OAuthTokens class represents the third party provider OAuth tokens returned in the authentication response.
5+
# This class is not meant to be instantiated in user space, and is instantiated internally but exposed.
6+
class OAuthTokens
7+
include HashProvider
8+
9+
attr_accessor :access_token, :refresh_token, :scopes, :expires_at
10+
11+
def initialize(json)
12+
hash = JSON.parse(json, symbolize_names: true)
13+
14+
@access_token = hash[:access_token]
15+
@refresh_token = hash[:refresh_token]
16+
@scopes = hash[:scopes]
17+
@expires_at = hash[:expires_at]
18+
end
19+
20+
def to_json(*)
21+
{
22+
access_token: access_token,
23+
refresh_token: refresh_token,
24+
scopes: scopes,
25+
expires_at: expires_at,
26+
}
27+
end
28+
end
29+
end

lib/workos/user_management.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def load_sealed_session(client_id:, session_data:, cookie_password:)
7171
# field of the IdP sign-in page for the user, if you know their username ahead of time.
7272
# @param [String] domain_hint Can be used to pre-fill the domain field when
7373
# initiating authentication with Microsoft OAuth, or with a GoogleSAML connection type.
74+
# @param [Array<String>] provider_scopes An array of additional OAuth scopes to request from the provider.
7475
# @example
7576
# WorkOS::UserManagement.authorization_url(
7677
# connection_id: 'conn_123',
@@ -96,7 +97,8 @@ def authorization_url(
9697
provider: nil,
9798
connection_id: nil,
9899
organization_id: nil,
99-
state: ''
100+
state: '',
101+
provider_scopes: nil
100102
)
101103

102104
validate_authorization_url_arguments(
@@ -115,6 +117,7 @@ def authorization_url(
115117
provider: provider,
116118
connection_id: connection_id,
117119
organization_id: organization_id,
120+
provider_scopes: provider_scopes,
118121
}.compact)
119122

120123
"https://#{WorkOS.config.api_hostname}/user_management/authorize?#{query}"

spec/lib/workos/user_management_spec.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,32 @@
3636
'edit%22%7D&provider=authkit',
3737
)
3838
end
39+
40+
context 'with provider_scopes' do
41+
it 'returns a valid authorization URL that includes provider_scopes' do
42+
url = WorkOS::UserManagement.authorization_url(
43+
provider: 'GoogleOAuth',
44+
provider_scopes: %w[custom-scope-1 custom-scope-2],
45+
client_id: 'workos-proj-123',
46+
redirect_uri: 'foo.com/auth/callback',
47+
state: {
48+
next_page: '/dashboard/edit',
49+
}.to_s,
50+
)
51+
52+
expect(url).to eq(
53+
'https://api.workos.com/user_management/authorize?' \
54+
'client_id=workos-proj-123' \
55+
'&redirect_uri=foo.com%2Fauth%2Fcallback' \
56+
'&response_type=code' \
57+
'&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
58+
'edit%22%7D' \
59+
'&provider=GoogleOAuth' \
60+
'&provider_scopes=custom-scope-1' \
61+
'&provider_scopes=custom-scope-2',
62+
)
63+
end
64+
end
3965
end
4066

4167
context 'with a connection selector' do
@@ -453,6 +479,40 @@
453479
end
454480
end
455481

482+
context 'when oauth_tokens is present in the api response' do
483+
it 'returns an oauth_tokens object' do
484+
VCR.use_cassette('user_management/authenticate_with_code/valid_with_oauth_tokens') do
485+
authentication_response = WorkOS::UserManagement.authenticate_with_code(
486+
code: '01H93ZZHA0JBHFJH9RR11S83YN',
487+
client_id: 'client_123',
488+
ip_address: '200.240.210.16',
489+
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/108.0.0.0 Safari/537.36',
490+
)
491+
492+
expect(authentication_response.oauth_tokens).to be_a(WorkOS::OAuthTokens)
493+
expect(authentication_response.oauth_tokens.access_token).to eq('oauth_access_token')
494+
expect(authentication_response.oauth_tokens.refresh_token).to eq('oauth_refresh_token')
495+
expect(authentication_response.oauth_tokens.scopes).to eq(%w[read write])
496+
expect(authentication_response.oauth_tokens.expires_at).to eq(1_234_567_890)
497+
end
498+
end
499+
end
500+
501+
context 'when oauth_tokens is not present in the api response' do
502+
it 'returns nil oauth_tokens' do
503+
VCR.use_cassette('user_management/authenticate_with_code/valid') do
504+
authentication_response = WorkOS::UserManagement.authenticate_with_code(
505+
code: '01H93ZZHA0JBHFJH9RR11S83YN',
506+
client_id: 'client_123',
507+
ip_address: '200.240.210.16',
508+
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/108.0.0.0 Safari/537.36',
509+
)
510+
511+
expect(authentication_response.oauth_tokens).to be_nil
512+
end
513+
end
514+
end
515+
456516
context 'when the user is being impersonated' do
457517
it 'contains the impersonator metadata' do
458518
VCR.use_cassette('user_management/authenticate_with_code/valid_with_impersonator') do

spec/support/fixtures/vcr_cassettes/user_management/authenticate_with_code/valid_with_oauth_tokens.yml

Lines changed: 82 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)