Authentication
Email/password and social login flows
Authentication
What's Included
- Email/password registration and login
- Google OAuth via Firebase
- Forgot password / reset password flow
- Email verification confirmation page
- Auth middleware for protected routes
- Guest middleware for auth-only pages
- Automatic token refresh on 401 errors
Auth Composable (useAuth)
The useAuth composable handles all authentication flows:
const {
register, // (name, email, password) → registers user
login, // (email, password) → logs in user
socialLogin, // (idToken) → Firebase social auth
logout, // () → clears session
forgotPassword, // (email) → sends reset email
resetPassword, // (token, password) → resets password
refreshToken, // () → refreshes JWT
isLoading, // Ref<boolean>
error // Ref<string | null>
} = useAuth()
All methods update the auth store on success and return readonly refs for reactivity.
Auth Store (useAuthStore)
Central state for authentication:
const authStore = useAuthStore()
authStore.token // JWT access token
authStore.refreshToken // Refresh token
authStore.user // UserProfile | null
authStore.isAuthenticated // Computed boolean
Auth state is persisted to localStorage and rehydrated on mount via initAuth().
Pages
| Page | Route | Middleware |
|---|---|---|
| Login | /auth/login | guest |
| Register | /auth/register | guest |
| Forgot Password | /auth/forgot-password | guest |
| Reset Password | /auth/reset-password | guest |
| Email Verified | /auth/email-verified | — |
Login Page
The login form accepts email and password. On success, it fetches the user profile and redirects to /profile. Includes a link to "Forgot password?" and a social login button.
Register Page
The registration form accepts name, email, and password. On success, the user is logged in immediately and redirected to /profile.
Social Login
The SocialLoginButton component handles the Firebase OAuth flow:
- User clicks "Sign in with Google"
- Firebase popup opens for Google OAuth
- On success, the Firebase ID token is sent to the backend
- Backend verifies the token and returns JWT credentials
- User is logged in and redirected
Firebase is optional. If no Firebase config is provided, the social login button is hidden.
Middleware
auth Middleware
Applied to protected pages (/profile, /settings, etc.). Checks authStore.isAuthenticated and redirects to /auth/login if not authenticated.
// In a page component:
definePageMeta({ middleware: 'auth' })
guest Middleware
Applied to auth pages (/auth/login, /auth/register, etc.). Redirects authenticated users to /profile.
Token Refresh
The useApi composable automatically handles token refresh:
- An API call returns
401 Unauthorized useApiattempts a single token refresh using the stored refresh token- If refresh succeeds, the original request is retried with the new token
- If refresh fails, the user is logged out
End-to-End Flows
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, anduserfrom localStorage - If a token exists, sets
isAuthenticated: true - Profile is fetched to validate the token is still valid
Firebase Setup
Firebase is used for Google OAuth (social login). It's optional — if no Firebase config is provided, the social login button is hidden automatically.
1. Get Firebase Configuration
- Go to Firebase Console and select your project
- Click the Gear icon → Project settings
- Scroll to Your apps section (add a web app if you don't have one)
- Select the Config radio button and copy these values:
| Firebase Config Key | Environment Variable |
|---|---|
apiKey | NUXT_PUBLIC_FIREBASE_API_KEY |
authDomain | NUXT_PUBLIC_FIREBASE_AUTH_DOMAIN |
projectId | NUXT_PUBLIC_FIREBASE_PROJECT_ID |
These values are safe to expose client-side — they are restricted by your authorized domains.
2. Enable Google Sign-In
In Firebase Console → Build → Authentication → Sign-in method, ensure Google is enabled.
3. Add Authorized Domains (Critical)
Without this step, Firebase authentication will fail with auth/unauthorized-domain.
- In Firebase Console → Build → Authentication → Settings → Authorized domains
- Click Add domain
- Enter your production domain (e.g.,
yourdomain.com) — withouthttps://or trailing slashes - Add any additional domains (staging, www subdomain, etc.)
localhostis included by default for local development.
Troubleshooting
auth/unauthorized-domain — Add your domain to Firebase authorized domains (see step 3 above).
auth/configuration-not-found — Verify all three NUXT_PUBLIC_FIREBASE_* environment variables are set and restart the application.
Google Sign-In not working — Ensure Google is enabled as a sign-in provider in Firebase Console → Build → Authentication → Sign-in method.