From 63cefc87639904d122422ccabaa2a606e5c3584a Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Mon, 27 Jul 2026 10:54:42 -0400 Subject: [PATCH] feat(pkce): default clientId to the client's configured client ID PKCEHelper's four flows (AuthKit/SSO authorization URL and code exchange) required callers to re-pass a clientId the WorkOS client already carries (constructor arg / WORKOS_CLIENT_ID). Make the parameter optional with a fallback to requireClientId(), aligning PHP with the other backend SDKs' override-with-fallback pattern. Explicit arguments still win; an unconfigured client now throws ConfigurationException instead of a TypeError. Co-Authored-By: Claude Fable 5 --- lib/PKCEHelper.php | 22 ++++++++++++------- tests/PKCEHelperTest.php | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/lib/PKCEHelper.php b/lib/PKCEHelper.php index 8ccfab10..c087c3d9 100644 --- a/lib/PKCEHelper.php +++ b/lib/PKCEHelper.php @@ -75,7 +75,7 @@ public static function generate(): array * Generate an AuthKit authorization URL with auto-generated PKCE parameters and state. * * @param string $redirectUri The redirect URI. - * @param string $clientId The WorkOS client ID. + * @param string|null $clientId The WorkOS client ID. Defaults to the client's configured client ID. * @param string|null $state Optional state parameter. Auto-generated if null. * @param string|null $provider Optional auth provider. * @param string|null $connectionId Optional connection ID. @@ -87,7 +87,7 @@ public static function generate(): array */ public function getAuthKitAuthorizationUrl( string $redirectUri, - string $clientId, + ?string $clientId = null, ?string $state = null, ?string $provider = null, ?string $connectionId = null, @@ -96,6 +96,7 @@ public function getAuthKitAuthorizationUrl( ?string $loginHint = null, ?string $screenHint = null, ): array { + $clientId ??= $this->client->requireClientId(); $pkce = self::generate(); $state ??= bin2hex(random_bytes(16)); @@ -134,14 +135,16 @@ public function getAuthKitAuthorizationUrl( * * @param string $code The authorization code. * @param string $codeVerifier The PKCE code verifier. - * @param string $clientId The WorkOS client ID. + * @param string|null $clientId The WorkOS client ID. Defaults to the client's configured client ID. * @return array The authentication response. */ public function authKitCodeExchange( string $code, string $codeVerifier, - string $clientId, + ?string $clientId = null, ): array { + $clientId ??= $this->client->requireClientId(); + return $this->client->request( method: 'POST', path: 'user_management/authenticate', @@ -160,7 +163,7 @@ public function authKitCodeExchange( * Generate an SSO authorization URL with auto-generated PKCE parameters and state. * * @param string $redirectUri The redirect URI. - * @param string $clientId The WorkOS client ID. + * @param string|null $clientId The WorkOS client ID. Defaults to the client's configured client ID. * @param string|null $state Optional state parameter. Auto-generated if null. * @param string|null $domain Optional SSO domain. * @param string|null $provider Optional SSO provider. @@ -172,7 +175,7 @@ public function authKitCodeExchange( */ public function getSsoAuthorizationUrl( string $redirectUri, - string $clientId, + ?string $clientId = null, ?string $state = null, ?string $domain = null, ?string $provider = null, @@ -181,6 +184,7 @@ public function getSsoAuthorizationUrl( ?string $domainHint = null, ?string $loginHint = null, ): array { + $clientId ??= $this->client->requireClientId(); $pkce = self::generate(); $state ??= bin2hex(random_bytes(16)); @@ -219,14 +223,16 @@ public function getSsoAuthorizationUrl( * * @param string $code The authorization code. * @param string $codeVerifier The PKCE code verifier. - * @param string $clientId The WorkOS client ID. + * @param string|null $clientId The WorkOS client ID. Defaults to the client's configured client ID. * @return array The SSO token response. */ public function ssoCodeExchange( string $code, string $codeVerifier, - string $clientId, + ?string $clientId = null, ): array { + $clientId ??= $this->client->requireClientId(); + return $this->client->request( method: 'POST', path: 'sso/token', diff --git a/tests/PKCEHelperTest.php b/tests/PKCEHelperTest.php index df2e19de..0d7bd743 100644 --- a/tests/PKCEHelperTest.php +++ b/tests/PKCEHelperTest.php @@ -86,6 +86,41 @@ public function testGetAuthKitAuthorizationUrl(): void $this->assertSame('S256', $query['code_challenge_method']); } + public function testGetAuthKitAuthorizationUrlFallsBackToConfiguredClientId(): void + { + $client = $this->createMockClient([['status' => 200, 'body' => ['url' => 'https://auth.workos.com/...']]]); + $client->pkce()->getAuthKitAuthorizationUrl( + redirectUri: 'https://example.com/callback', + ); + $query = []; + parse_str($this->getLastRequest()->getUri()->getQuery(), $query); + $this->assertSame('test_client_id', $query['client_id']); + } + + public function testGetAuthKitAuthorizationUrlExplicitClientIdWins(): void + { + $client = $this->createMockClient([['status' => 200, 'body' => ['url' => 'https://auth.workos.com/...']]]); + $client->pkce()->getAuthKitAuthorizationUrl( + redirectUri: 'https://example.com/callback', + clientId: 'client_override', + ); + $query = []; + parse_str($this->getLastRequest()->getUri()->getQuery(), $query); + $this->assertSame('client_override', $query['client_id']); + } + + public function testGetAuthKitAuthorizationUrlThrowsWithoutAnyClientId(): void + { + $client = $this->createMockClient( + [['status' => 200, 'body' => []]], + clientId: null, + ); + $this->expectException(\WorkOS\Exception\ConfigurationException::class); + $client->pkce()->getAuthKitAuthorizationUrl( + redirectUri: 'https://example.com/callback', + ); + } + // -- H11: AuthKit PKCE code exchange -- public function testAuthKitCodeExchange(): void @@ -105,6 +140,17 @@ public function testAuthKitCodeExchange(): void $this->assertSame('verifier_123', $body['code_verifier']); } + public function testAuthKitCodeExchangeFallsBackToConfiguredClientId(): void + { + $client = $this->createMockClient([['status' => 200, 'body' => ['access_token' => 'at_123']]]); + $client->pkce()->authKitCodeExchange( + code: 'auth_code_123', + codeVerifier: 'verifier_123', + ); + $body = json_decode((string) $this->getLastRequest()->getBody(), true); + $this->assertSame('test_client_id', $body['client_id']); + } + // -- H15: SSO PKCE authorization URL -- public function testGetSsoAuthorizationUrl(): void