From e1faf33dd1d58552d4fbc102a497d5d84d5f8635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Viguier?= Date: Sun, 14 Jan 2024 16:50:52 +0100 Subject: [PATCH] Improve token guard to not crash when provided with Basic Auth (#2193) --- app/Services/Auth/SessionOrTokenGuard.php | 37 +++++++++++++++++++++-- config/auth.php | 21 ++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/app/Services/Auth/SessionOrTokenGuard.php b/app/Services/Auth/SessionOrTokenGuard.php index 5c96c5687c0..65ffd4ac1d8 100644 --- a/app/Services/Auth/SessionOrTokenGuard.php +++ b/app/Services/Auth/SessionOrTokenGuard.php @@ -9,6 +9,8 @@ use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Foundation\Application; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Str; /** * A custom Guard which combines the default Laravel Session Guard with @@ -308,8 +310,39 @@ protected function getUserByToken(): ?Authenticatable { $token = $this->request->headers->get(self::HTTP_TOKEN_HEADER); - return is_string($token) && $token !== '' ? $this->provider->retrieveByCredentials([ + // Skip if token is not found. + if ($token === null || !is_string($token) || $token === '') { + return null; + } + + // Skip if token starts with Basic: it is not related to Lychee. + if (Str::startsWith('Basic', $token)) { + return null; + } + + // Check if token starts with Bearer + $hasBearer = Str::startsWith('Bearer', $token); + /** @var bool $configLog */ + $configLog = config('auth.token_guard.log_warn_no_scheme_bearer'); + /** @var bool $configThrow */ + $configThrow = config('auth.token_guard.fail_bearer_authenticable_not_found', true); + + // If Token does not start with Bearer + if (!$hasBearer && $configLog) { + Log::warning('Auth token found, but Bearer prefix not provided.'); + } + + // Remove prefix and fetch authenticable. + $token = trim(Str::remove('Bearer', $token)); + $authenticable = $this->provider->retrieveByCredentials([ self::TOKEN_COLUMN_NAME => hash(self::TOKEN_HASH_METHOD, $token), - ]) ?? throw new BadRequestHeaderException('Invalid token') : null; + ]); + + return match (true) { + $authenticable !== null => $authenticable, + $hasBearer && $configThrow => throw new BadRequestHeaderException('Invalid token'), + $hasBearer => null, + default => throw new BadRequestHeaderException('Invalid token') + }; } } diff --git a/config/auth.php b/config/auth.php index e3f3e405ca9..4face21e4e1 100644 --- a/config/auth.php +++ b/config/auth.php @@ -39,7 +39,7 @@ 'guards' => [ 'lychee' => [ - 'driver' => env('ENABLE_TOKEN_AUTH', true) ? 'session-or-token' : 'session', // @phpstan-ignore-line + 'driver' => env('ENABLE_BEARER_TOKEN_AUTH', env('ENABLE_TOKEN_AUTH', true)) ? 'session-or-token' : 'session', // @phpstan-ignore-line 'provider' => 'users', ], ], @@ -110,4 +110,23 @@ */ 'password_timeout' => 10800, + + /* + |-------------------------------------------------------------------------- + | Hard fail on bearer token + |-------------------------------------------------------------------------- + | + | When a bearer token is found, we fail hard by throwing an exception when the + | associated authenticable (user) is not found. + | + | This is only used if ENABLE_BEARER_TOKEN_AUTH = true + */ + + 'token_guard' => [ + // Hard fail if bearer token is provided but no authenticable user is found + 'fail_bearer_authenticable_not_found' => (bool) env('FAIL_NO_AUTHENTICABLE_BEARER_TOKEN', true), + + // Log if token is provided but no bearer prefix. + 'log_warn_no_scheme_bearer' => (bool) env('LOG_WARN_NO_BEARER_TOKEN', true), + ], ];