nextcloud/server · error · Sabre\DAV\Exception\NotAuthenticated
Cannot authenticate over ajax calls
Error message
Cannot authenticate over ajax calls
What it means
Raised at the end of Sabre\Auth::check(): parent::check() (Sabre's standard Basic auth) rejected the credentials AND the request carries X-Requested-With containing XMLHttpRequest. Like error 315, the server answers 401 with a DummyBasic WWW-Authenticate realm to stop the browser from showing its native basic-auth popup, then throws NotAuthenticated. Root cause is genuinely invalid credentials; the ajax branch only changes the response shape.
Source
Thrown at apps/dav/lib/Connector/Sabre/Auth.php:211
|| \OC_User::handleApacheAuth()
) {
$user = $this->userSession->getUser()->getUID();
$this->currentUser = $user;
$this->session->close();
return [true, $this->principalPrefix . $user];
}
}
$data = parent::check($request, $response);
if ($data[0] === true) {
$startPos = strrpos($data[1], '/') + 1;
$user = $this->userSession->getUser()->getUID();
$data[1] = substr_replace($data[1], $user, $startPos);
} elseif (in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With') ?? ''))) {
// For ajax requests use dummy auth name to prevent browser popup in case of invalid creditials
$response->addHeader('WWW-Authenticate', 'DummyBasic realm="' . $this->realm . '"');
$response->setStatus(Http::STATUS_UNAUTHORIZED);
throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
}
return $data;
}
}
View on GitHub (pinned to ecdeb153ff)
Solutions
- Verify and refresh the credentials (create a new app password: Settings > Security, or `occ user:add-app-password <uid>`), then retry
- Confirm the username format (login name, not display name/email depending on instance settings)
- If your client handles its own auth UI, remove the X-Requested-With header for DAV calls so you receive the standard Basic challenge and can distinguish error sources
- Check for bruteforce throttling on repeated failures (`occ security:bruteforce:reset` for the affected IP)
Defensive patterns
Strategy: try-catch
Validate before calling
// JS: verify credentials once before entering DAV-heavy flows
const probe = await fetch('/remote.php/dav/avatars/current-user.png', {
headers: { Authorization: 'Basic ' + btoa(user + ':' + pass) },
});
if (probe.status === 401) {
showCredentialError(); // avoid triggering repeated DummyBasic 401s from DAV calls
} Try / catch
try {
await davRequest('PUT', url, body, basicAuthHeader);
} catch (e) {
if (e.status === 401 && e.headers['WWW-Authenticate']?.startsWith('DummyBasic')) {
// credentials rejected on an AJAX request — the popup was suppressed on purpose.
// Stop retrying, surface a re-login UI, and refresh the stored app password.
invalidateStoredCredentials();
} else {
throw e;
}
} Prevention
- Treat a DummyBasic WWW-Authenticate header as a deliberate signal: invalid credentials on an AJAX call — never loop
- Rotate stored app passwords when users revoke them in Settings > Security
- Remove global X-Requested-With headers from HTTP clients if you need the standard Basic challenge for debugging
- Reset bruteforce throttling (`occ security:bruteforce:reset <ip>`) after investigating repeated failures
When it happens
Trigger: An AJAX request to any Sabre/DAV endpoint with a wrong password, revoked app password, or wrong username, plus the X-Requested-With header. Non-AJAX callers get the ordinary Basic 401 challenge instead.
Common situations: App password revoked or regenerated (device list cleanup) while JS still uses the old one; username typos in embedded uploaders; password changed but cached in the client; custom apps sending X-Requested-With on DAV fetches by default (some frameworks add it globally).
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Cannot authenticate over ajax calls
- CSRF check not passed.
- $class: $msg
- NoValidCredentials
- Too many calendars created
AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17).
Data as JSON: /api/errors/35ecf1cc38f6eb5b.
Report an issue: GitHub.