Feat: Enhance OIDC nonce handling with middleware and session support

- Introduced `CaptureOidcNonce` middleware to capture and store nonce in session during GET requests.
- Updated `CustomAuthCodeRepository` to resolve nonce from session if not present in POST requests, ensuring seamless handling.
- Registered middleware in `bootstrap/app.php` for web routes.
- Adjusted `LoginResponse` to use `redirect()->intended` for improved redirection logic.
- Added comprehensive feature tests to validate nonce capture, session storage, and cache resolution workflows.
This commit is contained in:
= 2026-06-17 09:17:06 +00:00
parent 2682a234d4
commit 0921535ae2
5 changed files with 78 additions and 3 deletions

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CaptureOidcNonce
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->isMethod('GET') && $request->has('nonce')) {
$request->session()->put('oidc_nonce', $request->input('nonce'));
}
return $next($request);
}
}

View File

@ -23,6 +23,6 @@ public function toResponse($request)
])
->log('User logged in}');
return to_route('dashboard');
return redirect()->intended(route('dashboard'));
}
}

View File

@ -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');
}
}
}
}

View File

@ -13,6 +13,10 @@
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
$middleware->web(append: [
App\Http\Middleware\CaptureOidcNonce::class,
]);
$middleware->validateCsrfTokens(except: [
'keka/callback',
]);

View File

@ -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();
});