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:
parent
2682a234d4
commit
0921535ae2
24
app/Http/Middleware/CaptureOidcNonce.php
Normal file
24
app/Http/Middleware/CaptureOidcNonce.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,6 @@ public function toResponse($request)
|
||||
])
|
||||
->log('User logged in}');
|
||||
|
||||
return to_route('dashboard');
|
||||
return redirect()->intended(route('dashboard'));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,10 @@
|
||||
health: '/up',
|
||||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
$middleware->web(append: [
|
||||
App\Http\Middleware\CaptureOidcNonce::class,
|
||||
]);
|
||||
|
||||
$middleware->validateCsrfTokens(except: [
|
||||
'keka/callback',
|
||||
]);
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user