{"record":{"id":"75ae4380781baa0c","repo":"gotify/server","slug":"no-client-auth-provided","errorCode":null,"errorMessage":"no client auth provided","messagePattern":"no client auth provided","errorType":"http","errorClass":null,"httpStatus":403,"severity":"warning","filePath":"api/session.go","lineNumber":141,"sourceCode":"//\t- clientTokenQuery: []\n//\t- basicAuth: []\n//\tresponses:\n//\t  200:\n//\t    description: Ok\n//\t    headers:\n//\t      Set-Cookie:\n//\t        type: string\n//\t        description: cleared session cookie\n//\t  400:\n//\t    description: Bad Request\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\nfunc (a *SessionAPI) Logout(ctx *gin.Context) {\n\tauth.SetCookie(ctx.Writer, \"\", -1, a.SecureCookie)\n\n\tclient := auth.GetClient(ctx)\n\tif client == nil {\n\t\tctx.AbortWithError(403, errors.New(\"no client auth provided\"))\n\t\treturn\n\t}\n\n\ta.NotifyDeleted(client.UserID, client.Token)\n\tif success := successOrAbort(ctx, 500, a.DB.DeleteClientByID(client.ID)); !success {\n\t\treturn\n\t}\n\n\tctx.Status(200)\n}\n","sourceCodeStart":123,"sourceCodeEnd":152,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/session.go#L123-L152","documentation":"Returned by Logout when there is no authenticated client in the request context. auth.GetClient(ctx) returns nil because no valid session token was presented, so the handler refuses to log out with 403 (after clearing the cookie). Logging out only makes sense with an existing, valid client session.","triggerScenarios":"DELETE/POST to the logout endpoint without a session token; a token that already expired or was deleted server-side; calling logout twice in a row (the second call finds no client); sending a token for a different auth backend that GetClient does not recognize.","commonSituations":"Frontend calls logout after the token already expired; stale clients retrying logout after the server restarted and lost session state; API version changes to how tokens are transported; misconfigured auth middleware not populating the client in the gin context.","solutions":["Only call logout with a currently valid session token","Treat 403 on logout as 'already logged out' and clear local state instead of retrying","Re-login to obtain a fresh token if you need a server-side logout (e.g., to invalidate via NotifyDeleted/DeleteClientByID)","Check the auth middleware/GetClient wiring if valid tokens still yield nil client"],"exampleFix":"// before\nawait api.logout(); // 403 if token already expired\n// after\nif (session.token) { await api.logout(); }\nlocalStorage.removeItem('session');","handlingStrategy":"validation","validationCode":"function canLogout(session) {\n  return Boolean(session && session.token);\n}\nif (!canLogout(currentSession)) {\n  // nothing to invalidate server-side; just clear local state\n  clearLocalSession();\n}","typeGuard":"function isActiveSession(s) {\n  return s != null && typeof s.token === 'string' && s.token.length > 0 && (!s.expiresAt || new Date(s.expiresAt) > new Date());\n}","tryCatchPattern":"try {\n  await api.logout();\n} catch (e) {\n  if (e.status === 403) { /* already logged out */ }\n  else { throw e; }\n} finally {\n  clearLocalSession();\n}","preventionTips":["Guard logout calls with an unexpired-token check","Treat 403 from logout as 'nothing to do', not as a failure","Avoid double-invocation from UI event handlers (disable button after first click)","If server-side invalidation matters, re-authenticate first when the token is already expired"],"tags":["http","session","logout","authorization"],"backgroundTag":"no-active-session","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}