nextcloud/server · error · Sabre\DAV\Exception\NotAuthenticated
Cannot authenticate over ajax calls
Error message
Cannot authenticate over ajax calls
What it means
Raised in LegacyPublicAuth::auth while authenticating a public DAV share (password-protected link/email/circle share): the supplied password failed checkPassword(), the session is not already authenticated for this specific share id (PublicAuth::DAV_AUTHENTICATED), and the request carries X-Requested-With containing XMLHttpRequest. The server responds 401 with a DummyBasic WWW-Authenticate header (to suppress the browser's native basic-auth popup) and then throws Sabre\DAV\Exception\NotAuthenticated. It exists so AJAX callers get a clean 401 instead of a popup storm.
Source
Thrown at apps/dav/lib/Connector/LegacyPublicAuth.php:84
\OC_User::setIncognitoMode(true);
// check if the share is password protected
if ($share->isPasswordProtected()) {
if ($share->getShareType() === IShare::TYPE_LINK
|| $share->getShareType() === IShare::TYPE_EMAIL
|| $share->getShareType() === IShare::TYPE_CIRCLE) {
if ($this->shareManager->checkPassword($share, $password)) {
return true;
} elseif ($this->session->exists(PublicAuth::DAV_AUTHENTICATED)
&& $this->session->get(PublicAuth::DAV_AUTHENTICATED) === $share->getId()) {
return true;
} else {
if (in_array('XMLHttpRequest', explode(',', $this->request->getHeader('X-Requested-With')))) {
// do not re-authenticate over ajax, use dummy auth name to prevent browser popup
http_response_code(401);
header('WWW-Authenticate: DummyBasic realm="' . $this->realm . '"');
throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls');
}
$this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress());
return false;
}
} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
return true;
} else {
$this->throttler->registerAttempt(self::BRUTEFORCE_ACTION, $this->request->getRemoteAddress());
return false;
}
}
return true;
}
public function getShare(): IShare {
assert($this->share !== null);
return $this->share;View on GitHub (pinned to ecdeb153ff)
Solutions
- Supply the correct share password (basic auth username is ignored for link shares; only the password matters)
- Authenticate once through the web UI share page so the session stores DAV_AUTHENTICATED for that share, then retry the DAV call
- In custom JS, complete the share password flow before issuing DAV requests and treat the DummyBasic 401 as terminal instead of re-prompting in a loop
- If calls are not intentionally AJAX, remove/adjust the X-Requested-With header to get the regular Basic challenge path
Defensive patterns
Strategy: try-catch
Validate before calling
// JS: verify share password through the web UI share endpoint BEFORE any DAV call
const check = await fetch(`/s/${token}/authenticate`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'requesttoken': oc_requesttoken },
body: 'password=' + encodeURIComponent(password),
});
if (!check.ok) {
showPasswordPrompt(); // never proceed to DAV with a bad password
} Try / catch
try {
await davFetch(`/public-files/${token}/file.txt`);
} catch (e) {
if (e instanceof Error && e.message.includes('Cannot authenticate over ajax calls')) {
// DummyBasic 401: password wrong/missing and session not authorized for this share.
// Terminal — prompt the user, do NOT retry with the same credentials.
showSharePasswordPrompt(token);
} else {
throw e;
}
} Prevention
- Complete the share-password step once so the session carries DAV_AUTHENTICATED for that share id
- Never retry DAV requests in a loop after a DummyBasic 401; it will not succeed and feeds bruteforce throttling
- Send the correct share password via basic auth (username is ignored for link shares)
- Watch for changed share passwords after page load in long-lived SPAs
When it happens
Trigger: Browser JS (marked X-Requested-With: XMLHttpRequest) hitting remote.php/dav/public-files/<token> for a password-protected share with a wrong or missing password while the session has not authenticated that share. Non-AJAX callers with a bad password instead get a bruteforce-throttled false return and a standard Basic 401 challenge.
Common situations: Share password changed after the page loaded; JS retries with a stale/empty password; custom web apps embedding public share DAV endpoints without first completing the share-password step; password-protected email shares opened via XHR.
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/25f2f9660302a7e4.
Report an issue: GitHub.