Authentication Flow

End-to-end authentication from registration to token refresh

Authentication Flow

Registration Flow

1. User fills RegisterForm
   │
2. useAuth().register(name, email, password)
   │
3. POST /api/v1/auth/register
   │
4. Backend creates user + dispatches UserCreated event
   │
5. Welcome email sent (Mailpit in dev, Brevo in prod)
   │
6. Backend returns { token, refreshToken, userId }
   │
7. authStore.setAuth(token, refreshToken)
   │
8. useProfile().fetchProfile() → updates authStore.user
   │
9. Router navigates to /profile

Login Flow

1. User fills LoginForm
   │
2. useAuth().login(email, password)
   │
3. POST /api/v1/auth/login
   │
4. Backend verifies credentials
   │
5. Returns { token, refreshToken }
   │
6. authStore.setAuth(token, refreshToken)
   │
7. fetchProfile() → updates user state
   │
8. Router navigates to /profile

Social Login Flow (Firebase/Google)

1. User clicks SocialLoginButton
   │
2. Firebase popup opens (Google OAuth)
   │
3. User authenticates with Google
   │
4. Firebase returns idToken
   │
5. useAuth().socialLogin(idToken)
   │
6. POST /api/v1/auth/social { idToken, provider: 'google' }
   │
7. Backend verifies token with Firebase Admin SDK
   │   ├── New user → creates account (emailValidated: true)
   │   └── Existing user → issues new tokens
   │
8. Returns { token, refreshToken, isNewUser }
   │
9. authStore.setAuth() + fetchProfile()
   │
10. Router navigates to /profile

Token Refresh Flow

1. API call returns 401 Unauthorized
   │
2. useApi detects 401 response
   │
3. POST /api/v1/auth/token/refresh { refreshToken }
   │
4. Backend validates refresh token
   │   ├── Valid → returns new { token, refreshToken }
   │   └── Invalid/expired → returns 401
   │
5a. Success:
   │   ├── authStore.setAuth(newToken, newRefreshToken)
   │   └── Retry original request with new token
   │
5b. Failure:
       ├── authStore.clearAuth()
       └── Redirect to /auth/login

Password Reset Flow

1. User fills ForgotPasswordForm with email
   │
2. POST /api/v1/auth/forgot-password { email }
   │
3. Backend always returns 202 (prevents email enumeration)
   │
4. If email exists → sends reset email with tokenized link
   │
5. User clicks link in email
   │
6. GET /api/v1/auth/reset-password/{token}
   │
7. Backend validates token, redirects to frontend:
   │  {FRONTEND_RESET_PASSWORD_URL}?token={token}
   │
8. User fills ResetPasswordForm with new password
   │
9. POST /api/v1/auth/reset-password { token, password }
   │
10. Password is changed, PasswordChanged email sent

Email Verification Flow

1. User registers → welcome email sent
   │
2. Email contains validation link:
   │  /api/v1/auth/validate-email/{token}
   │
3. User clicks link
   │
4. Backend validates token, sets emailValidated: true
   │
5. Redirects to EMAIL_VALIDATION_REDIRECT_URL
   │  (typically /auth/email-verified)
   │
6. Frontend shows confirmation page

Session Persistence

Auth state is persisted to localStorage:

  • authStore.initAuth() runs on app mount (app.vue)
  • Reads token, refreshToken, and user from localStorage
  • If a token exists, sets isAuthenticated: true
  • Profile is fetched to validate the token is still valid