'secure-nonce-value-xyz']); app()->instance('request', $authorizeRequest); // 2. Create the mock AuthCodeEntity $client = Mockery::mock(ClientEntityInterface::class); $client->shouldReceive('getIdentifier')->andReturn('client-123'); $authCodeEntity = Mockery::mock(AuthCodeEntityInterface::class); $authCodeEntity->shouldReceive('getIdentifier')->andReturn('code-id-abc'); $authCodeEntity->shouldReceive('getUserIdentifier')->andReturn('user-1'); $authCodeEntity->shouldReceive('getClient')->andReturn($client); $authCodeEntity->shouldReceive('getScopes')->andReturn([]); $authCodeEntity->shouldReceive('getExpiryDateTime')->andReturn(new DateTimeImmutable('+10 minutes')); // 4. Run the repository persist method $repository = new CustomAuthCodeRepository(); $repository->persistNewAuthCode($authCodeEntity); // 5. Assert the nonce is cached expect(Cache::get('oidc_nonce_code-id-abc'))->toBe('secure-nonce-value-xyz'); // --- Phase 2: Token Exchange --- // 1. Prepare mock encrypted auth code payload $key = Passport::tokenEncryptionKey(app('encrypter')); $payload = json_encode([ 'client_id' => 'client-123', 'auth_code_id' => 'code-id-abc', 'user_id' => 'user-1', 'scopes' => [], 'expire_time' => time() + 600, ]); $encryptedAuthCode = Crypto::encryptWithPassword($payload, $key); // 2. Set up the request to token endpoint containing the encrypted code $tokenRequest = Request::create('/oauth/token', 'POST', ['code' => $encryptedAuthCode]); app()->instance('request', $tokenRequest); // 3. Resolve CustomTokenResponseType and call the protected resolveNonce method using reflection $responseType = app(CustomTokenResponseType::class); $reflection = new ReflectionClass(CustomTokenResponseType::class); $method = $reflection->getMethod('resolveNonce'); $method->setAccessible(true); $resolvedNonce = $method->invoke($responseType); // 4. Assert the resolved nonce is correct expect($resolvedNonce)->toBe('secure-nonce-value-xyz'); }); it('captures nonce via middleware and resolves it from session during POST approval request', function (): void { // 1. Setup session $session = app('session.store'); // 2. GET Request (Middleware captures nonce into session) $getRequest = Request::create('/oauth/authorize', 'GET', ['nonce' => 'session-nonce-value-999']); $getRequest->setLaravelSession($session); $middleware = new CaptureOidcNonce(); $middleware->handle($getRequest, fn () => new Symfony\Component\HttpFoundation\Response()); expect($session->get('oidc_nonce'))->toBe('session-nonce-value-999'); // 3. POST Request (No nonce in parameters, but resolved from session) $postRequest = Request::create('/oauth/authorize', 'POST'); $postRequest->setLaravelSession($session); app()->instance('request', $postRequest); // Mock AuthCodeEntity $client = Mockery::mock(ClientEntityInterface::class); $client->shouldReceive('getIdentifier')->andReturn('client-123'); $authCodeEntity = Mockery::mock(AuthCodeEntityInterface::class); $authCodeEntity->shouldReceive('getIdentifier')->andReturn('code-id-session'); $authCodeEntity->shouldReceive('getUserIdentifier')->andReturn('user-1'); $authCodeEntity->shouldReceive('getClient')->andReturn($client); $authCodeEntity->shouldReceive('getScopes')->andReturn([]); $authCodeEntity->shouldReceive('getExpiryDateTime')->andReturn(new DateTimeImmutable('+10 minutes')); // Persist $repository = new CustomAuthCodeRepository(); $repository->persistNewAuthCode($authCodeEntity); // Verify cache has it expect(Cache::get('oidc_nonce_code-id-session'))->toBe('session-nonce-value-999'); // Verify session was cleared expect($session->has('oidc_nonce'))->toBeFalse(); });