{"record":{"id":"35ecf1cc38f6eb5b","repo":"nextcloud/server","slug":"cannot-authenticate-over-ajax-calls-35ecf1","errorCode":null,"errorMessage":"Cannot authenticate over ajax calls","messagePattern":"Cannot authenticate over ajax calls","errorType":"http","errorClass":"Sabre\\DAV\\Exception\\NotAuthenticated","httpStatus":401,"severity":"error","filePath":"apps/dav/lib/Connector/Sabre/Auth.php","lineNumber":211,"sourceCode":"\t\t\t\t|| \\OC_User::handleApacheAuth()\n\t\t\t) {\n\t\t\t\t$user = $this->userSession->getUser()->getUID();\n\t\t\t\t$this->currentUser = $user;\n\t\t\t\t$this->session->close();\n\t\t\t\treturn [true, $this->principalPrefix . $user];\n\t\t\t}\n\t\t}\n\n\t\t$data = parent::check($request, $response);\n\t\tif ($data[0] === true) {\n\t\t\t$startPos = strrpos($data[1], '/') + 1;\n\t\t\t$user = $this->userSession->getUser()->getUID();\n\t\t\t$data[1] = substr_replace($data[1], $user, $startPos);\n\t\t} elseif (in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With') ?? ''))) {\n\t\t\t// For ajax requests use dummy auth name to prevent browser popup in case of invalid creditials\n\t\t\t$response->addHeader('WWW-Authenticate', 'DummyBasic realm=\"' . $this->realm . '\"');\n\t\t\t$response->setStatus(Http::STATUS_UNAUTHORIZED);\n\t\t\tthrow new \\Sabre\\DAV\\Exception\\NotAuthenticated('Cannot authenticate over ajax calls');\n\t\t}\n\t\treturn $data;\n\t}\n}\n","sourceCodeStart":193,"sourceCodeEnd":216,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/Connector/Sabre/Auth.php#L193-L216","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// JS: verify credentials once before entering DAV-heavy flows\nconst probe = await fetch('/remote.php/dav/avatars/current-user.png', {\n  headers: { Authorization: 'Basic ' + btoa(user + ':' + pass) },\n});\nif (probe.status === 401) {\n  showCredentialError(); // avoid triggering repeated DummyBasic 401s from DAV calls\n}","typeGuard":null,"tryCatchPattern":"try {\n    await davRequest('PUT', url, body, basicAuthHeader);\n} catch (e) {\n    if (e.status === 401 && e.headers['WWW-Authenticate']?.startsWith('DummyBasic')) {\n        // credentials rejected on an AJAX request — the popup was suppressed on purpose.\n        // Stop retrying, surface a re-login UI, and refresh the stored app password.\n        invalidateStoredCredentials();\n    } else {\n        throw e;\n    }\n}","preventionTips":["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"],"tags":["dav","authentication","ajax","invalid-credentials","http-401"],"backgroundTag":"invalid-credentials","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}