ARTICLE AD BOX
I am implementing authentication in FastAPI using cookies for session management. I have a logout endpoint that deletes the authentication cookie. The logout request returns 200 OK and the frontend redirects the user to the login page.
However, when I refresh the page after logging out, the user gets logged in again automatically.
This suggests that either the cookie is not being properly removed or the browser is still sending some authentication information.
Backend (FastAPI)
manager = LoginManager(SECRET_KEY, token_url=TOKEN_URL, use_cookie=True) router = APIRouter(prefix="/auth", tags=["AUTHENTICATION"]) def _clear_auth_cookie(response: Response): response.delete_cookie( key=manager.cookie_name, path="/", httponly=True, samesite="none", secure=True, ) def _set_auth_cookie(response: Response, subject_id: str, role: str, is_active: bool): access_token = manager.create_access_token( data={"sub": subject_id, "role": role, "is_active": is_active}, expires=expire_duration, ) response.set_cookie( key=manager.cookie_name, value=access_token, httponly=True, samesite="none", secure=True, path="/", expires=seconds, ) # _append_partitioned_cookie_flag(response) def _clear_google_oauth_cookies(response: Response): response.delete_cookie( key=GOOGLE_OAUTH_STATE_COOKIE, path="/", httponly=True, samesite="none", secure=True, ) response.delete_cookie( key=GOOGLE_OAUTH_MODE_COOKIE, path="/", httponly=True, samesite="none", secure=True, ) @router.post("/tokenVerification", status_code=status.HTTP_200_OK) async def token(request: Request, response: Response, db: Session = Depends(get_db)): user = await manager.optional(request) if user: if user.role in {"member", "trainer"} and getattr(user, "email_verified", True) is False: _clear_auth_cookie(response) return { "valid": False, "role": "user", "is_active": False, "user_id": None, "is_super_admin": False, "email_verified": False, } user.last_login = func.now() db.commit() if user.role == "member": return { "valid": True, "role": user.role, "is_active": user.is_active, "user_id": user.user_id, "is_super_admin": False, } if user.role == "trainer": return { "valid": True, "role": user.role, "is_active": user.is_active, "user_id": user.trainer_id, "is_super_admin": False, } if user.role == "admin": return { "valid": True, "role": user.role, "is_active": user.is_active, "user_id": user.admin_id, "is_super_admin": bool(user.is_super_admin), } _clear_auth_cookie(response) return _clear_auth_cookie(response) return {"valid": False, "role": "user", "is_active": False, "user_id": None, "is_super_admin": False} @router.post("/logout") def logout(response: Response): _clear_auth_cookie(response) _clear_google_oauth_cookies(response) return {"message": "Logged out successfully"}Frontend (React + Axios)
authSlice
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import axios from "axios"; const BASE_URL = import.meta.env.VITE_BACKEND_URL; // Async backend verification export const verifyAuth = createAsyncThunk( "auth/verifyAuth", async (_, thunkAPI) => { try { const res = await axios.post(`${BASE_URL}/auth/tokenVerification`, null, { withCredentials: true, }); return res.data ?? { valid: false, role: 'user', is_active: false, user_id: null, is_super_admin: false }; } catch { return thunkAPI.rejectWithValue(false); } } ); const authSlice = createSlice({ name: "auth", initialState: { loggedIn: false, role:'user', is_active:false, user_id:null, is_super_admin: false, }, reducers: { logout(state) { state.loggedIn = false; state.role = 'user'; state.is_active = false; state.user_id = null; state.is_super_admin = false; }, }, extraReducers: (builder) => { builder .addCase(verifyAuth.fulfilled, (state, action) => { const {valid, role, is_active, user_id, is_super_admin} = action.payload; state.loggedIn = !!valid; state.role = role; state.is_active = is_active; state.user_id = user_id; state.is_super_admin = !!is_super_admin; }) .addCase(verifyAuth.rejected, (state) => { state.loggedIn = false; state.role = 'user'; state.is_active = false; state.user_id = null; state.is_super_admin = false; }); }, }); export const { logout } = authSlice.actions; export default authSlice.reducer;Logout Function
const handleLogout = async () => { try { await axios.post(`${BASE_URL}/auth/logout`, null, { withCredentials: true }) dispatch(logout()) setTimeout(() => { navigate('/login') }, 500); } catch { return; } }Steps to reproduce
Login successfully
Click logout
User is redirected to login page
Refresh the page
User appears logged in again
Expected behavior
After logout:
authentication cookie should be removed
refreshing the page should not restore the session
user should remain logged out
Actual behavior
Refreshing the page restores the logged-in state.
Response Headers After Logout:
set-cookie : access-token=""; expires=Sun, 15 Mar 2026 05:05:25 GMT; HttpOnly; Max-Age=0; Path=/; SameSite=none; Secure
set-cookie : google_oauth_state=""; expires=Sun, 15 Mar 2026 05:05:25 GMT; HttpOnly; Max-Age=0; Path=/; SameSite=none; Secure
set-cookie : google_oauth_mode=""; expires=Sun, 15 Mar 2026 05:05:25 GMT; HttpOnly; Max-Age=0; Path=/; SameSite=none; Secure
