diff --git a/app/Http/Middleware/CaptureOidcNonce.php b/app/Http/Middleware/CaptureOidcNonce.php new file mode 100644 index 0000000..74e722e --- /dev/null +++ b/app/Http/Middleware/CaptureOidcNonce.php @@ -0,0 +1,24 @@ +isMethod('GET') && $request->has('nonce')) { + $request->session()->put('oidc_nonce', $request->input('nonce')); + } + + return $next($request); + } +} diff --git a/app/Http/Responses/LoginResponse.php b/app/Http/Responses/LoginResponse.php index 99d27ce..0570b5b 100644 --- a/app/Http/Responses/LoginResponse.php +++ b/app/Http/Responses/LoginResponse.php @@ -23,6 +23,6 @@ public function toResponse($request) ]) ->log('User logged in}'); - return to_route('dashboard'); + return redirect()->intended(route('dashboard')); } } diff --git a/app/OAuth/CustomAuthCodeRepository.php b/app/OAuth/CustomAuthCodeRepository.php index 8c04c84..c8678fa 100644 --- a/app/OAuth/CustomAuthCodeRepository.php +++ b/app/OAuth/CustomAuthCodeRepository.php @@ -17,12 +17,18 @@ public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): voi { parent::persistNewAuthCode($authCodeEntity); - if (request()->has('nonce')) { + $nonce = request()->input('nonce') ?? (request()->hasSession() ? request()->session()->get('oidc_nonce') : null); + + if ($nonce) { Cache::put( 'oidc_nonce_'.$authCodeEntity->getIdentifier(), - request()->input('nonce'), + $nonce, now()->addMinutes(10) ); + + if (request()->hasSession()) { + request()->session()->forget('oidc_nonce'); + } } } } diff --git a/bootstrap/app.php b/bootstrap/app.php index a0d6053..7dce968 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -13,6 +13,10 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { + $middleware->web(append: [ + App\Http\Middleware\CaptureOidcNonce::class, + ]); + $middleware->validateCsrfTokens(except: [ 'keka/callback', ]); diff --git a/tests/Feature/OidcNonceTest.php b/tests/Feature/OidcNonceTest.php index d6ee781..b52b02b 100644 --- a/tests/Feature/OidcNonceTest.php +++ b/tests/Feature/OidcNonceTest.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use App\Http\Middleware\CaptureOidcNonce; use App\OAuth\{CustomAuthCodeRepository, CustomTokenResponseType}; use Defuse\Crypto\Crypto; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -63,3 +64,43 @@ // 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(); +});