nextcloud/server · error · Sabre\DAV\Exception\NotAuthenticated

CSRF check not passed.

Error message

CSRF check not passed.

What it means

Raised in Sabre\Auth::auth() for DAV requests that rely on the browser session: passesCSRFCheck() failed and requiresCSRFCheck() is true, and the method is not POST (POST instead takes the forcedLogout path and re-checks credentials). The server sets 401 and throws NotAuthenticated. In practice: a cookie-authenticated DAV request (PROPFIND, GET, PUT, ...) reached the server without a valid request token.

Source

Thrown at apps/dav/lib/Connector/Sabre/Auth.php:178

		return true;
	}

	/**
	 * @return array{bool, string}
	 * @throws NotAuthenticated
	 */
	private function auth(RequestInterface $request, ResponseInterface $response): array {
		$forcedLogout = false;

		if (!$this->request->passesCSRFCheck()
			&& $this->requiresCSRFCheck()) {
			// In case of a fail with POST we need to recheck the credentials
			if ($this->request->getMethod() === 'POST') {
				$forcedLogout = true;
			} else {
				$response->setStatus(Http::STATUS_UNAUTHORIZED);
				throw new \Sabre\DAV\Exception\NotAuthenticated('CSRF check not passed.');
			}
		}

		if ($forcedLogout) {
			$this->userSession->logout();
		} else {
			if ($this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) {
				throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.');
			}
			if (
				//Fix for broken webdav clients
				($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED)))
				//Well behaved clients that only send the cookie are allowed
				|| ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && empty($request->getHeader('Authorization')))
				|| \OC_User::handleApacheAuth()
			) {
				$user = $this->userSession->getUser()->getUID();
				$this->currentUser = $user;

View on GitHub (pinned to ecdeb153ff)

Solutions

  1. Send the CSRF token: header 'X-Request-Token' (or 'requesttoken' form/query parameter) using the token embedded in the page (oc_requesttoken / data-requesttoken)
  2. Prefer app-password basic auth for scripted/daemon access — it bypasses CSRF and 2FA entirely
  3. For POST requests the code force-logs-out and retries credential checks; ensure your client re-authenticates rather than looping
  4. Regenerate the page/session if the token is stale (log out and back in)

Example fix

# before (cookie only -> CSRF check not passed)
curl -b nc_cookies.txt -X PROPFIND https://cloud.example/remote.php/dav/files/alice/ -H 'Depth: 0'

# after (app password: no session, no CSRF involved)
curl -u alice:xxxx-xxxx-xxxx-xxxx -X PROPFIND https://cloud.example/remote.php/dav/files/alice/ -H 'Depth: 0'
Defensive patterns

Strategy: validation

Validate before calling

// JS: every cookie-authenticated DAV call must carry the request token
const token = document.querySelector('[data-requesttoken]')?.dataset.requesttoken ?? oc_requesttoken;
await fetch('/remote.php/dav/files/alice/test.txt', {
  method: 'PUT',
  headers: {
    'X-Request-Token': token,
    'Content-Type': 'text/plain',
  },
  body: data,
});

Try / catch

try {
    await davPut(url, body);
} catch (e) {
    if (e.status === 401 && /CSRF check not passed/.test(e.body ?? '')) {
        // cookie session without valid request token: refresh token from the page or switch to app-password auth
        await refreshRequestTokenAndRetryOnce();
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: JS or curl calling remote.php/dav/... with only the session cookie, no X-Request-Token header / requesttoken parameter, and no Authorization header — e.g. a fetch() missing the requesttoken header, or a copied curl command reused after the token expired with the session still alive.

Common situations: Custom front-end code calling DAV endpoints and forgetting the request token; long-lived sessions whose requesttoken was rendered into an old page; tools that mix cookie auth with WebDAV semantics; requests proxied through middleware stripping custom headers.

Related errors


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