{"record":{"id":"53b2378b981bc172","repo":"BookStackApp/BookStack","slug":"errors-api-cookie-auth-only-get","errorCode":null,"errorMessage":"errors.api_cookie_auth_only_get","messagePattern":"errors\\.api_cookie_auth_only_get","errorType":"http","errorClass":"ApiAuthException","httpStatus":403,"severity":"error","filePath":"app/Http/Middleware/ApiAuthenticate.php","lineNumber":43,"sourceCode":"    /**\n     * Ensure the current user can access authenticated API routes, either via existing session\n     * authentication or via API Token authentication.\n     *\n     * @throws ApiAuthException\n     */\n    protected function ensureAuthorizedBySessionOrToken(Request $request): void\n    {\n        // Use the active user session already exists.\n        // This is to make it easy to explore API endpoints via the UI.\n        if (session()->isStarted()) {\n            // Ensure the user has API access permission\n            if (!$this->sessionUserHasApiAccess()) {\n                throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403);\n            }\n\n            // Only allow GET requests for cookie-based API usage\n            if ($request->method() !== 'GET') {\n                throw new ApiAuthException(trans('errors.api_cookie_auth_only_get'), 403);\n            }\n\n            return;\n        }\n\n        // Set our api guard to be the default for this request lifecycle.\n        auth()->shouldUse('api');\n\n        // Validate the token and its users API access\n        auth()->authenticate();\n    }\n\n    /**\n     * Check if the active session user has API access.\n     */\n    protected function sessionUserHasApiAccess(): bool\n    {\n        $hasApiPermission = user()->can(Permission::AccessApi);","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/BookStackApp/BookStack/blob/18f8469a1c72f8cc8497e9372635e6dea5028071/app/Http/Middleware/ApiAuthenticate.php#L25-L61","documentation":"This BookStack API-auth middleware throws ApiAuthException (HTTP 403) when a request is authenticated via the session cookie rather than an API token and the HTTP method is not GET. Cookie-based API access is deliberately limited to read-only GET requests for security (CSRF risk); any POST/PUT/PATCH/DELETE from a cookie session is rejected.","triggerScenarios":"A logged-in browser session (session cookie) calls any non-GET API endpoint, e.g. a fetch() with method POST/PUT/DELETE while relying on the session cookie instead of an API token with header Authorization: Token <id>:<secret>.","commonSituations":"Frontend scripts reusing the logged-in session cookie to mutate data; API clients that never obtained a token; proxies/SDKs stripping the Authorization header so the request falls back to cookie auth; testing mutations in a browser console while logged in.","solutions":["Obtain an API token (User Profile > API Tokens) and send header 'Authorization: Token <id>:<secret>' on the request","Change the request to a GET if a read was intended (check HTTP method, e.g. fetch defaults)","If calling from your own frontend, route mutations through a server-side proxy that authenticates with a token","Verify no middleware/proxy strips the Authorization header, causing fallback to cookie auth"],"exampleFix":"// before\nfetch('/api/books', { method: 'POST', credentials: 'include', body })\n// after\nfetch('/api/books', { method: 'POST', headers: { 'Authorization': 'Token ' + id + ':' + secret, 'Content-Type': 'application/json' }, body })","handlingStrategy":"try-catch","validationCode":"const isCookieAuth = !headers.get('Authorization')?.startsWith('Token ');\nif (isCookieAuth && method !== 'GET') {\n  throw new Error('Cookie-based API auth only allows GET requests; use a token for mutations.');\n}","typeGuard":"function usesTokenAuth(headers) {\n  return /^Token\\s+[\\w-]+:[\\w-]+$/.test(headers.get('Authorization') ?? '');\n}","tryCatchPattern":"try {\n  const res = await fetch(url, options);\n  if (res.status === 403) {\n    const body = await res.json();\n    if (body?.message?.includes('api_cookie_auth_only_get')) {\n      // switch to token auth or change method to GET\n    }\n  }\n} catch (e) { /* network error handling */ }","preventionTips":["Always use API tokens (Authorization: Token id:secret) for non-GET requests","Never rely on session cookies for programmatic API mutations","Audit client code for methods other than GET when credentials: 'include' is used","Check proxies/SDKs don't strip the Authorization header"],"tags":["http-403","authentication","csrf","api"],"backgroundTag":"cookie-auth-method-not-allowed","analyzedSha":"18f8469a1c72f8cc8497e9372635e6dea5028071","analyzedAt":"2026-09-02T19:49:33.068Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}