Skip to content

Conversation

@cbcoutinho
Copy link

Fix Bearer Token Authentication for CORS Endpoints

Problem

Bearer token authentication with OAuth 2.0/OIDC currently fails for app-specific APIs (Notes, Calendar, Contacts, etc.) with 401 Unauthorized errors, even though the same Bearer tokens work correctly for OCS APIs.

Root Cause

When using Bearer token authentication with CORS-annotated endpoints:

  1. ✅ Bearer token validation successfully authenticates the user
  2. ✅ A session is created for the authenticated user
  3. CORSMiddleware detects the logged-in session but no CSRF token
  4. CORSMiddleware calls session->logout() to prevent CSRF attacks
  5. The logout invalidates the session, breaking the API request with 401

This occurs because app-specific APIs (Notes, Calendar, etc.) use the #[CORS] attribute, which triggers CORSMiddleware security checks. OCS APIs don't have this attribute and handle Bearer tokens correctly via SecurityMiddleware::isValidOCSRequest() (lines 234-237).

Error Manifestation

CORS requires basic auth (401 Unauthorized)

Or in logs:

[TokenInvalidatedListener] Could not find the OIDC session related with an invalidated token
Session token invalidated before logout
Logging out

Solution

This PR extends CORSMiddleware to accept Bearer token authentication without requiring CSRF tokens, aligning with the existing pattern used by SecurityMiddleware for OCS routes.

Changes

lib/private/AppFramework/Middleware/Security/CORSMiddleware.php

  • Added check for Authorization: Bearer header before CSRF and app_api checks
  • Bearer-authenticated requests now bypass session logout logic
  • Maintains backward compatibility with existing authentication flows

tests/lib/AppFramework/Middleware/Security/CORSMiddlewareTest.php

  • Added testCORSShouldAllowBearerAuth() test to verify Bearer tokens are accepted
  • Ensures no session logout occurs for Bearer-authenticated requests

Implementation Details

// Allow Bearer token authentication for CORS requests
// Bearer tokens are stateless and don't require CSRF protection
$authorizationHeader = $this->request->getHeader('Authorization');
if (!empty($authorizationHeader) && str_starts_with($authorizationHeader, 'Bearer ')) {
    return;
}

This check is placed before the CSRF token and app_api checks to allow Bearer tokens to bypass session-based security requirements.

Security Considerations

Why is this safe?

  1. Stateless Authentication: Bearer tokens are inherently stateless and don't rely on cookies or sessions
  2. CSRF Protection Not Needed: CSRF attacks target session-based authentication; Bearer tokens are immune as they're sent explicitly via headers
  3. Existing Pattern: SecurityMiddleware already allows Bearer tokens for OCS endpoints (line 234-237)
  4. OAuth 2.0 Standard: Bearer token usage is a standard OAuth 2.0 pattern for API authentication

What doesn't change?

  • Session-based authentication still requires CSRF tokens
  • Basic Auth still works as before
  • AppAPI authentication (app_api flag) still works
  • Public pages maintain existing behavior

Backward Compatibility

Fully backward compatible

  • Existing CSRF token authentication continues to work
  • Basic Auth (PHP_AUTH_USER/PHP_AUTH_PW) continues to work
  • AppAPI authentication continues to work
  • Only adds a new authentication path without modifying existing flows

Related Issues & PRs

Configuration

No configuration changes required. Works automatically with any authentication backend that provides Bearer tokens via the Authorization header:

  • user_oidc app with Bearer token validation
  • OAuth2 apps
  • Custom authentication backends
  • AppAPI external apps (still supported via existing app_api flag)

Documentation Impact

No documentation changes required. This fix makes Bearer token authentication work as users would naturally expect, aligning with OAuth 2.0 best practices.


Checklist

  • Implementation follows existing patterns in SecurityMiddleware
  • Tests added for new functionality
  • Backward compatibility maintained
  • Security considerations addressed
  • Related issues linked
  • Commit message follows Nextcloud conventions

Bearer token authentication with OAuth 2.0/OIDC currently fails for
app-specific APIs (Notes, Calendar, Contacts, etc.) with 401 errors,
even though it works correctly for OCS APIs.

Root cause:
- Bearer token validation successfully authenticates the user
- A session is created for the authenticated user
- CORSMiddleware detects the logged-in session but no CSRF token
- CORSMiddleware calls session->logout() to prevent CSRF attacks
- The logout invalidates the session, breaking the API request

This fix allows Bearer token authentication to bypass the CSRF check
and logout logic in CORSMiddleware, as Bearer tokens are stateless and
don't require CSRF protection. This aligns with how SecurityMiddleware
already handles Bearer tokens for OCS routes (line 234-237).

The fix adds a check for the Authorization: Bearer header before the
CSRF and app_api checks, allowing Bearer-authenticated requests to
proceed without triggering session logout.

This enables proper Bearer token authentication for all Nextcloud APIs
including app-specific APIs that use the #[CORS] attribute.

Related: nextcloud#44365
Related: nextcloud/user_oidc#836

Signed-off-by: Chris Coutinho <[email protected]>
@cbcoutinho cbcoutinho requested a review from a team as a code owner October 21, 2025 08:43
@cbcoutinho cbcoutinho requested review from Altahrim, ArtificialOwl and sorbaugh and removed request for a team October 21, 2025 08:43
@cbcoutinho cbcoutinho marked this pull request as draft October 21, 2025 08:43
@cbcoutinho cbcoutinho marked this pull request as ready for review October 25, 2025 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: "CORS requires basic auth" error when authenticating with OIDC token

1 participant