Client-Side Authentication
Learn how to manage authentication state and user sessions in your Vue components.
The useAuth Composable
The useAuth() composable provides reactive authentication state and methods for managing user sessions.
Basic Usage
<script setup lang="ts">
const { user, authStatus, isLoading, error, login, logout } = useAuth()
</script>
<template>
<div>
<div v-if="authStatus === 'authenticated'">
<p>Welcome, {{ user?.name }}!</p>
<button @click="logout">Logout</button>
</div>
<div v-else-if="authStatus === 'guest'">
<button @click="login('google')">Login with Google</button>
</div>
<div v-else-if="isLoading">
<p>Checking session...</p>
</div>
<p v-else>{{ error?.message || 'Session not checked' }}</p>
</div>
</template>Returned Properties
user
Current authenticated user object, or null if not authenticated.
const { user } = useAuth()
// User object structure
interface User {
sub: string // Unique user identifier
name: string // Full name
email: string // Email address
picture?: string // Profile picture URL
provider: string // OAuth provider (google, auth0, github)
// ... custom claims
}Reactive State
user is reactive and automatically updates when authentication state changes.
isLoggedIn
Boolean indicating whether the user is currently logged in.
<script setup lang="ts">
const { isLoggedIn } = useAuth()
</script>
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link v-if="isLoggedIn" to="/dashboard">
Dashboard
</router-link>
<router-link v-else to="/login">
Login
</router-link>
</nav>
</template>authStatus
Explicit auth state with three values:
'unknown'before verification, or after a recoverable startup failure'authenticated'when a user is logged in'guest'when auth has been resolved and no session is available
const { authStatus } = useAuth()
if (authStatus.value === 'guest') {
console.log('Show public experience')
}isResolved
Boolean indicating whether the initial auth state has been determined.
Use this to distinguish unresolved sessions from confirmed guests. An unresolved session can have an error without loading.
isLoading
Boolean indicating whether an auth operation is currently running.
<script setup lang="ts">
const { isLoading, authStatus, user, error } = useAuth()
</script>
<template>
<div>
<div v-if="isLoading">
<p>Refreshing session...</p>
</div>
<div v-else-if="authStatus === 'authenticated'">
<p>Welcome, {{ user?.name }}!</p>
</div>
<p v-else-if="authStatus === 'unknown'">{{ error?.message || 'Session not checked' }}</p>
<div v-else>
<p>Please log in</p>
</div>
</div>
</template>Initial Auth Resolution
Use isResolved or await ensureResolved() when you need a deterministic startup auth decision. isLoading is for active auth operations, not for distinguishing unknown from guest.
ensureResolved()
Resolves the initial auth state on demand. This is especially useful in middleware, layouts, or guarded pages that must wait for cold-start session restoration before deciding what to render or where to redirect.
Startup 401 resolves quietly to guest. Network, 5xx, and forbidden failures reject, retain unknown status, expose error, and reset loading. Concurrent callers share one attempt; a later call retries while status remains unknown. Already-resolved sessions are unchanged.
const { ensureResolved, error } = useAuth()
async function retrySession() {
try {
await ensureResolved()
} catch {
console.error(error.value?.message)
}
}error
AuthError | null, exposed as a computed ref. Display error.message; inspect error.category for specific handling.
| Refresh failure | Startup | Established session |
|---|---|---|
401 (session_expired) | Guest; ensureResolved() suppresses the public error | Clear user/token; expose error |
Network / 5xx (network_error / server_error) | Remain unknown; expose error | Preserve identity; expose error |
403 (forbidden) | Remain unknown; expose error | Preserve identity; expose error |
New refresh attempts and successful recovery clear stale errors. Other auth-operation failures use operation_error; a claims-update 401 is not refresh-session rejection. See AuthError.
Preserved identity does not extend token validity or authorize a failed API call. The module provides no error UI or background retry loop.
<script setup lang="ts">
const { error, login } = useAuth()
</script>
<template>
<div>
<p v-if="error" class="error">{{ error.message }}</p>
<button @click="login('google')">Login</button>
</div>
</template>isImpersonating
Boolean indicating whether currently impersonating another user.
<script setup lang="ts">
const { isImpersonating, user, originalUser, stopImpersonation } = useAuth()
</script>
<template>
<div v-if="isImpersonating" class="banner">
<p>
You ({{ originalUser?.originalUserEmail }}) are impersonating {{ user?.email }}
</p>
<button @click="stopImpersonation">Stop Impersonation</button>
</div>
</template>Impersonation Feature
This property is only available when impersonation is enabled. See User Impersonation Guide for details.
originalUser
Original user information when impersonating. Returns null when not impersonating.
const { originalUser, isImpersonating } = useAuth()
if (isImpersonating.value) {
console.log('Original user sub:', originalUser.value?.originalUserSub)
console.log('Original user email:', originalUser.value?.originalUserEmail)
console.log('Original user name:', originalUser.value?.originalUserName)
}OriginalUser Interface:
interface OriginalUser {
originalUserSub: string // Original user's JWT subject
originalUserEmail?: string // Original user's email
originalUserName?: string // Original user's name
}Authentication Methods
login(provider?, redirectTo?)
Initiates the OAuth login flow for the specified provider.
Parameters:
| Parameter | Type | Description |
|---|---|---|
provider | string | Provider name ('google', 'auth0', 'github', 'mock'). Defaults to 'google' |
redirectTo | string | Optional relative redirect path after login. Overrides redirect.success for that login attempt |
Example:
const { login } = useAuth()
// Basic login (defaults to Google)
await login()
// Login with specific provider
await login('google')
await login('github')
await login('auth0')
// Login with a custom post-login destination for this request
await login('google', '/dashboard?from=auth')logout(redirectTo?)
Logs out the current user and clears the session.
Parameters:
| Parameter | Type | Description |
|---|---|---|
redirectTo | string | Optional redirect path after logout |
Example:
const { logout } = useAuth()
// Basic logout
await logout()
// With custom redirect
await logout('/goodbye')refresh(options?)
Manually refreshes the access token using the refresh token. Optionally recomputes custom claims. Failures reject with the underlying error and set useAuth().error. Use refresh() to explicitly recover an established session; ensureResolved() is a no-op once status is resolved.
const { refresh, user } = useAuth()
// Basic refresh
try {
await refresh()
console.log('Token refreshed:', user.value)
} catch (error) {
console.error('Refresh failed:', error)
}
// Refresh with updated claims (after role/permission change)
await refresh({ updateClaims: true })Automatic Refresh
With automaticRefresh: true, the client attempts restoration at eligible startup and refreshes after an API 401. It replays the request once only after success. Network, 5xx, and forbidden failures do not redirect; definitive refresh-session rejection does. There is no pre-expiry timer or background retry loop.
impersonate(targetUserId, reason?)
Start impersonating another user. Requires admin privileges.
Parameters:
| Parameter | Type | Description |
|---|---|---|
targetUserId | string | Target user ID or email to impersonate |
reason | string | Optional reason for impersonation (for audit logs) |
Example:
const { impersonate } = useAuth()
// Basic impersonation
await impersonate('user-123')
// With reason for audit trail
await impersonate('user@example.com', 'Debugging issue #456')Behavior:
- Generates a new short-lived access token (15 minutes default)
- Does NOT generate a refresh token (impersonated sessions cannot be refreshed)
- Updates authentication state to the impersonated user
- Stores original user information for restoration
Authorization Required
Implement the nuxt-aegis:impersonate:check and nuxt-aegis:impersonate:fetchTarget hooks. See User Impersonation Guide.
stopImpersonation()
Stop impersonating and restore the original user session.
Example:
const { stopImpersonation } = useAuth()
try {
await stopImpersonation()
console.log('Restored to original user')
} catch (error) {
console.error('Failed to stop impersonation:', error)
}Behavior:
- Validates current session is impersonated
- Restores original user's session with full privileges
- Generates new access token and refresh token
- Updates authentication state to the original user
Conditional Rendering
Show/Hide Based on Authentication
<script setup lang="ts">
const { authStatus, user, error } = useAuth()
</script>
<template>
<div v-if="authStatus === 'authenticated'">
<h1>Welcome back, {{ user?.name }}!</h1>
<p>{{ user?.email }}</p>
</div>
<div v-else-if="authStatus === 'guest'">
<h1>Welcome, Guest!</h1>
<p>Please log in to access your account.</p>
</div>
<p v-else>{{ error?.message || 'Session not checked' }}</p>
</template>Show During Loading
<script setup lang="ts">
const { isLoading, authStatus, user, error, login } = useAuth()
</script>
<template>
<div>
<div v-if="isLoading">
<p>Loading...</p>
</div>
<div v-else-if="authStatus === 'authenticated'">
<img :src="user?.picture" :alt="user?.name" />
<p>{{ user?.name }}</p>
</div>
<p v-else-if="authStatus === 'unknown'">{{ error?.message || 'Session not checked' }}</p>
<div v-else>
<button @click="login('google')">Login</button>
</div>
</div>
</template>Accessing User Data
Basic User Properties
<script setup lang="ts">
const { user } = useAuth()
</script>
<template>
<div v-if="user">
<img :src="user.picture" :alt="user.name" />
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
<span>Provider: {{ user.provider }}</span>
</div>
</template>Custom Claims
<script setup lang="ts">
const { user } = useAuth()
// Access custom claims
const userRole = computed(() => user.value?.role || 'user')
const isPremium = computed(() => user.value?.premium === true)
</script>
<template>
<div v-if="user">
<p>Role: {{ userRole }}</p>
<span v-if="isPremium" class="badge">Premium</span>
</div>
</template>Complete Example
<script setup lang="ts">
const { user, authStatus, isLoading, error, ensureResolved, login, logout } = useAuth()
async function retrySession() {
try {
await ensureResolved()
} catch {
console.error(error.value?.message)
}
}
const providers = [
{ name: 'google', label: 'Google' },
{ name: 'github', label: 'GitHub' },
{ name: 'auth0', label: 'Auth0' },
]
</script>
<template>
<div>
<div v-if="isLoading">
<p>Loading...</p>
</div>
<div v-else-if="authStatus === 'unknown'">
<p>{{ error?.message || 'Session not checked' }}</p>
<button @click="retrySession">Retry</button>
</div>
<div v-else-if="authStatus === 'authenticated'">
<img :src="user?.picture" :alt="user?.name" />
<h2>{{ user?.name }}</h2>
<p>{{ user?.email }}</p>
<p>Provider: {{ user?.provider }}</p>
<button @click="logout('/')">Logout</button>
</div>
<div v-else>
<h2>Welcome!</h2>
<p>Choose a provider to log in:</p>
<button
v-for="provider in providers"
:key="provider.name"
@click="login(provider.name)"
>
{{ provider.label }}
</button>
</div>
</div>
</template>TypeScript Support
Define custom user types for better type safety:
// types/auth.ts
export type AppTokenClaims = CustomTokenClaims<{
role: 'admin' | 'user' | 'guest'
premium: boolean
customData?: Record<string, any>
}>
// Component
const { user } = useAuth<AppTokenClaims>()
// Now user has full type safety
const role = user.value?.role // Type: 'admin' | 'user' | 'guest'
const isPremium = user.value?.premium // Type: boolean