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

  1. Supply the correct share password (basic auth username is ignored for link shares; only the password matters)
  2. Authenticate once through the web UI share page so the session stores DAV_AUTHENTICATED for that share, then retry the DAV call
  3. 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
  4. 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

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

Related errors


AI-assisted analysis of nextcloud/server@ecdeb153ff (2026-08-17). Data as JSON: /api/errors/25f2f9660302a7e4. Report an issue: GitHub.