→ AuthenticateOnceWithOAuth::handle() * Authorization: Basic → AuthenticateOnceWithBasicAuth::handle() * (no header) → 401 * * Auth logic lives in the dedicated classes — extend/fix them independently. */ class AuthenticateOnceWithDynamicApi { public function __construct( private readonly AuthenticateOnceWithOAuth $oauthMiddleware, private readonly AuthenticateOnceWithBasicAuth $basicMiddleware, ) {} public function handle(Request $request, Closure $next): mixed { $shouldLog = (bool) config('oauth.verbose_log', false) || ! app()->environment('production'); // Skip if OAuth module is disabled to avoid unnecessary processing; delegate to Basic Auth directly since it's simpler and more likely to be used without OAuth. if (! config('oauth.enabled', false)) { if ($shouldLog) { Log::debug('OAuth Dispatcher: OAuth disabled, using basic auth', [ 'path' => $request->path(), 'method' => $request->method(), ]); } return $this->basicMiddleware->handle($request, function (Request $req) use ($next) { $req->attributes->set('auth_method', 'basic'); return $next($req); }); } $authorization = (string) $request->header('Authorization', ''); if ($shouldLog) { Log::debug('OAuth Dispatcher: Authorization header detected', [ 'path' => $request->path(), 'method' => $request->method(), 'authorization_type' => preg_match('/^\s*(\w+)\s+/i', $authorization, $m) === 1 ? strtolower($m[1]) : 'none', 'has_header' => $authorization !== '' ? 'yes' : 'no', ]); } // ── Bearer token → delegate to OAuth middleware ─────────────────────── if (preg_match('/^\s*Bearer\s+/i', $authorization) === 1) { return $this->oauthMiddleware->handle($request, function (Request $req) use ($next) { $req->attributes->set('auth_method', 'oauth'); return $next($req); }); } // ── Basic credentials → delegate to Basic Auth middleware ───────────── if (preg_match('/^\s*Basic\s+/i', $authorization) === 1) { return $this->basicMiddleware->handle($request, function (Request $req) use ($next) { $req->attributes->set('auth_method', 'basic'); return $next($req); }); } // ── No Authorization header → 401 ───────────────────────────────────── $realm = config('app.name', 'Akaunting'); return response()->json([ 'message' => 'Unauthenticated.', 'error' => 'invalid_client', 'error_description' => 'Authentication required. Use an OAuth Bearer token or HTTP Basic credentials.', ], 401, [ 'WWW-Authenticate' => sprintf('Bearer realm="%s", Basic realm="%s"', $realm, $realm), ]); } }