2025-10-06 18:38:16 +03:00

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 localStorage
  • logout() removes token from localStorage
  • me() fetches user from /api/auth/user
  • getToken() 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 user and token in state
  • isAuthenticated checks for both user AND token
  • login() 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:
    1. Sets auth.token = token
    2. Fetches user data with auth.fetchMe()
    3. If successful, user stays authenticated
    4. If fails (invalid token), clears token and redirects to login

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

  1. Go to /login
  2. Enter credentials: admin@admin.com
  3. Click "Se connecter"
  4. Should redirect to /dashboards/dashboard-default
  5. Check localStorage: auth_token should be present

Test 2: Page Refresh

  1. After login, refresh the page
  2. Should stay on dashboard (not redirect to login)
  3. Check console: should see "Fetching user from /api/auth/user"

Test 3: Invalid Token

  1. Manually edit token in localStorage to garbage value
  2. Refresh page
  3. Should clear token and redirect to login
  4. Check console: "Invalid token, clearing auth"

Test 4: Logout

  1. After login, click logout
  2. Should redirect to /login
  3. Check localStorage: auth_token should be removed

Troubleshooting

"Still redirects to /login after refresh"

  • Open browser DevTools → Network tab
  • Refresh page
  • Check if GET /api/auth/user is called with Authorization: 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.isAuthenticated in Vue DevTools
  • Verify both auth.user and auth.token are set
  • If only token is set, check if /api/auth/user endpoint 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:

  1. Token is already saved in localStorage directly
  2. Router guard reads from localStorage on app load
  3. Token is automatically added to all API requests
  4. 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.