4.5 KiB
4.5 KiB
Authentication Setup - Token Persistence
✅ Fixed: Token persists across page refreshes
The Problem
After login, refreshing the page redirected to /login because the auth state wasn't being restored from the saved token.
The Solution
No plugin needed! The token is already saved in localStorage. I updated the router guard to check for the token on page load.
How it works
1. Login Flow
User submits login form
↓
POST /api/auth/login
↓
Backend returns: { success: true, data: { user, token } }
↓
Token saved to localStorage.setItem("auth_token", token)
↓
User saved to Pinia store
↓
Redirect to dashboard
2. Page Refresh Flow
User refreshes page
↓
Router guard runs (beforeEach)
↓
Check: auth.checked? No
↓
Check: localStorage.getItem("auth_token")? Yes
↓
Set auth.token = token (from localStorage)
↓
Fetch user: GET /api/auth/user (with Bearer token)
↓
Backend returns user data
↓
auth.user = userData
auth.checked = true
↓
User stays on protected route
3. Logout Flow
User clicks logout
↓
POST /api/auth/logout
↓
localStorage.removeItem("auth_token")
↓
auth.user = null
auth.token = null
↓
Redirect to /login
Files Modified
src/services/auth.ts
login()saves token to localStoragelogout()removes token from localStorageme()fetches user from/api/auth/usergetToken()retrieves token from localStorage
src/services/http.ts
- Automatically adds
Authorization: Bearer {token}header to all requests - Reads token from localStorage on every request
src/stores/auth.ts
- Stores both
userandtokenin state isAuthenticatedchecks for both user AND tokenlogin()extracts user and token from API response
src/router/index.js
- On every navigation, checks if token exists in localStorage
- If token exists and auth not checked yet:
- Sets
auth.token = token - Fetches user data with
auth.fetchMe() - If successful, user stays authenticated
- If fails (invalid token), clears token and redirects to login
- Sets
src/views/pages/Login.vue
- Form submits to
authStore.login() - Shows loading state and error messages
- Redirects to dashboard on success
API Endpoints Used
| Endpoint | Method | Purpose | Auth Required |
|---|---|---|---|
/api/auth/login |
POST | Login with email/password | No |
/api/auth/user |
GET | Get current user | Yes (Bearer token) |
/api/auth/logout |
POST | Logout | Yes (Bearer token) |
Testing
Test 1: Fresh Login
- Go to
/login - Enter credentials:
admin@admin.com - Click "Se connecter"
- Should redirect to
/dashboards/dashboard-default - Check localStorage:
auth_tokenshould be present
Test 2: Page Refresh
- After login, refresh the page
- Should stay on dashboard (not redirect to login)
- Check console: should see "Fetching user from /api/auth/user"
Test 3: Invalid Token
- Manually edit token in localStorage to garbage value
- Refresh page
- Should clear token and redirect to login
- Check console: "Invalid token, clearing auth"
Test 4: Logout
- After login, click logout
- Should redirect to
/login - Check localStorage:
auth_tokenshould be removed
Troubleshooting
"Still redirects to /login after refresh"
- Open browser DevTools → Network tab
- Refresh page
- Check if
GET /api/auth/useris called withAuthorization: Bearer ...header - Check response:
- 200 OK: Token is valid, check if user data is correct format
- 401 Unauthorized: Token is invalid or backend isn't verifying correctly
"Token is saved but still logged out"
- Check
auth.isAuthenticatedin Vue DevTools - Verify both
auth.userandauth.tokenare set - If only token is set, check if
/api/auth/userendpoint returns user data
"Backend doesn't accept token"
- Verify backend expects:
Authorization: Bearer {token} - Check if backend route has auth middleware
- Test token manually:
curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8000/api/auth/user
No Plugin Required!
You mentioned Pinia Persisted or Vuex Persisted - you don't need them because:
- Token is already saved in
localStoragedirectly - Router guard reads from
localStorageon app load - Token is automatically added to all API requests
- This is simpler and more explicit than a plugin
The only state that needs to persist is the token (string), and we're handling that manually.