From b9d5999bdd6fe52b21412885db5d9977cb5123c0 Mon Sep 17 00:00:00 2001 From: Stacy Curry Date: Wed, 18 Jun 2025 20:47:35 -0500 Subject: [PATCH 1/2] add support for custom scopes --- lib/Resource/AuthenticationResponse.php | 7 +++ lib/Resource/OAuthTokens.php | 40 ++++++++++++ lib/UserManagement.php | 8 ++- tests/WorkOS/UserManagementTest.php | 81 ++++++++++++++++++++++++- 4 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 lib/Resource/OAuthTokens.php diff --git a/lib/Resource/AuthenticationResponse.php b/lib/Resource/AuthenticationResponse.php index f4df1885..799b9241 100644 --- a/lib/Resource/AuthenticationResponse.php +++ b/lib/Resource/AuthenticationResponse.php @@ -10,6 +10,7 @@ * @property string $accessToken * @property string $refreshToken * @property ?Impersonator $impersonator + * @property ?OAuthTokens $oauthTokens */ class AuthenticationResponse extends BaseWorkOSResource { @@ -19,12 +20,14 @@ class AuthenticationResponse extends BaseWorkOSResource "impersonator", "accessToken", "refreshToken", + "oauthTokens", ]; public const RESPONSE_TO_RESOURCE_KEY = [ "organization_id" => "organizationId", "access_token" => "accessToken", "refresh_token" => "refreshToken", + "oauth_tokens" => "oauthTokens", ]; public static function constructFromResponse($response) @@ -39,6 +42,10 @@ public static function constructFromResponse($response) ); } + if (isset($response["oauth_tokens"])) { + $instance->values["oauthTokens"] = OAuthTokens::constructFromResponse($response["oauth_tokens"]); + } + return $instance; } } diff --git a/lib/Resource/OAuthTokens.php b/lib/Resource/OAuthTokens.php new file mode 100644 index 00000000..ac92c714 --- /dev/null +++ b/lib/Resource/OAuthTokens.php @@ -0,0 +1,40 @@ + "accessToken", + "refresh_token" => "refreshToken", + "expires_at" => "expiresAt", + "scopes" => "scopes" + ]; + + public static function constructFromResponse($response) + { + $instance = parent::constructFromResponse($response); + + // Ensure scopes is always an array + if (!isset($instance->values["scopes"])) { + $instance->values["scopes"] = []; + } + + return $instance; + } +} diff --git a/lib/UserManagement.php b/lib/UserManagement.php index 86afacad..de1f3931 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -613,6 +613,7 @@ public function revokeInvitation($invitationId) * @param null|string $domainHint DDomain hint that will be passed as a parameter to the IdP login page * @param null|string $loginHint Username/email hint that will be passed as a parameter to the to IdP login page * @param null|string $screenHint The page that the user will be redirected to when the provider is authkit + * @param null|array $providerScopes An array of provider-specific scopes * * @throws Exception\UnexpectedValueException * @throws Exception\ConfigurationException @@ -627,7 +628,8 @@ public function getAuthorizationUrl( $organizationId = null, $domainHint = null, $loginHint = null, - $screenHint = null + $screenHint = null, + $providerScopes = null ) { $path = "user_management/authorize"; @@ -689,6 +691,10 @@ public function getAuthorizationUrl( $params["screen_hint"] = $screenHint; } + if ($providerScopes && is_array($providerScopes)) { + $params["provider_scopes"] = implode(" ", $providerScopes); + } + return Client::generateUrl($path, $params); } diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index 5eb281e9..d50f406c 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -118,7 +118,9 @@ public static function authorizationUrlTestDataProvider() ["https://papagenos.com/auth/callback", null, null, "connection_123", null, null, "foo@workos.com"], ["https://papagenos.com/auth/callback", null, null, "connection_123"], [null, null, null, "connection_123"], - ["https://papagenos.com/auth/callback", ["toppings" => "ham"], null, "connection_123"] + ["https://papagenos.com/auth/callback", ["toppings" => "ham"], null, "connection_123"], + ["https://papagenos.com/auth/callback", null, null, "connection_123", null, null, null, null, ["read", "write"]], + [null, null, Resource\ConnectionType::GoogleOAuth, null, null, null, null, null, ["email", "profile"]] ]; } @@ -132,7 +134,9 @@ public function testAuthorizationURLExpectedParams( $connectionId, $organizationId = null, $domainHint = null, - $loginHint = null + $loginHint = null, + $screenHint = null, + $providerScopes = null ) { $expectedParams = [ "client_id" => WorkOS::getClientId(), @@ -167,6 +171,10 @@ public function testAuthorizationURLExpectedParams( $expectedParams["login_hint"] = $loginHint; } + if ($providerScopes && is_array($providerScopes)) { + $expectedParams["provider_scopes"] = implode(" ", $providerScopes); + } + $authorizationUrl = $this->userManagement->getAuthorizationUrl( $redirectUri, $state, @@ -174,7 +182,9 @@ public function testAuthorizationURLExpectedParams( $connectionId, $organizationId, $domainHint, - $loginHint + $loginHint, + $screenHint, + $providerScopes ); $paramsString = \parse_url($authorizationUrl, \PHP_URL_QUERY); \parse_str($paramsString, $paramsArray); @@ -347,6 +357,43 @@ public function testAuthenticateImpersonatorWithCode() ], $response->impersonator->toArray()); } + public function testAuthenticateWithOAuthTokensReturned() + { + $path = "user_management/authenticate"; + WorkOS::setApiKey("sk_test_12345"); + $result = $this->userAndOAuthTokensResponseFixture(); + + $params = [ + "client_id" => "project_0123456", + "code" => "01E2RJ4C05B52KKZ8FSRDAP23J", + "ip_address" => null, + "user_agent" => null, + "grant_type" => "authorization_code", + "client_secret" => WorkOS::getApiKey() + ]; + + $this->mockRequest( + Client::METHOD_POST, + $path, + null, + $params, + true, + $result + ); + + $userFixture = $this->userFixture(); + + $response = $this->userManagement->authenticateWithCode("project_0123456", "01E2RJ4C05B52KKZ8FSRDAP23J"); + $this->assertSame($userFixture, $response->user->toArray()); + + // Test OAuth tokens + $this->assertNotNull($response->oauthTokens); + $this->assertSame("oauth_access_token_123", $response->oauthTokens->accessToken); + $this->assertSame("oauth_refresh_token_456", $response->oauthTokens->refreshToken); + $this->assertSame(1640995200, $response->oauthTokens->expiresAt); + $this->assertSame(["read", "write"], $response->oauthTokens->scopes); + } + public function testEnrollAuthFactor() { $userId = "user_123456"; @@ -1491,6 +1538,34 @@ private function userAndImpersonatorResponseFixture() ]); } + private function userAndOAuthTokensResponseFixture() + { + return json_encode([ + "user" => [ + "object" => "user", + "id" => "user_01H7X1M4TZJN5N4HG4XXMA1234", + "email" => "test@test.com", + "first_name" => "Damien", + "last_name" => "Alabaster", + "email_verified" => true, + "profile_picture_url" => "https://example.com/photo.jpg", + "last_sign_in_at" => "2021-06-25T19:07:33.155Z", + "created_at" => "2021-06-25T19:07:33.155Z", + "updated_at" => "2021-06-25T19:07:33.155Z", + "external_id" => null, + "metadata" => [] + ], + "access_token" => "01DMEK0J53CVMC32CK5SE0KZ8Q", + "refresh_token" => "refresh_token_123", + "oauth_tokens" => [ + "access_token" => "oauth_access_token_123", + "refresh_token" => "oauth_refresh_token_456", + "expires_at" => 1640995200, + "scopes" => ["read", "write"] + ] + ]); + } + private function createUserAndTokenResponseFixture() { return json_encode([ From fc202f2f2fb330871d6884890d6bd9849982ec25 Mon Sep 17 00:00:00 2001 From: Stacy Curry Date: Mon, 23 Jun 2025 12:51:35 -0500 Subject: [PATCH 2/2] fix - use comma-separated list of scopes --- lib/UserManagement.php | 2 +- tests/WorkOS/UserManagementTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/UserManagement.php b/lib/UserManagement.php index de1f3931..41d9153b 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -692,7 +692,7 @@ public function getAuthorizationUrl( } if ($providerScopes && is_array($providerScopes)) { - $params["provider_scopes"] = implode(" ", $providerScopes); + $params["provider_scopes"] = implode(",", $providerScopes); } return Client::generateUrl($path, $params); diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index d50f406c..9b9042c9 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -172,7 +172,7 @@ public function testAuthorizationURLExpectedParams( } if ($providerScopes && is_array($providerScopes)) { - $expectedParams["provider_scopes"] = implode(" ", $providerScopes); + $expectedParams["provider_scopes"] = implode(",", $providerScopes); } $authorizationUrl = $this->userManagement->getAuthorizationUrl(