Skip to content

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 ​

vue
<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.

typescript
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.

vue
<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
typescript
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.

vue
<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.

typescript
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 failureStartupEstablished session
401 (session_expired)Guest; ensureResolved() suppresses the public errorClear user/token; expose error
Network / 5xx (network_error / server_error)Remain unknown; expose errorPreserve identity; expose error
403 (forbidden)Remain unknown; expose errorPreserve 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.

vue
<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.

vue
<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.

typescript
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:

typescript
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:

ParameterTypeDescription
providerstringProvider name ('google', 'auth0', 'github', 'mock'). Defaults to 'google'
redirectTostringOptional relative redirect path after login. Overrides redirect.success for that login attempt

Example:

typescript
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:

ParameterTypeDescription
redirectTostringOptional redirect path after logout

Example:

typescript
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.

typescript
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:

ParameterTypeDescription
targetUserIdstringTarget user ID or email to impersonate
reasonstringOptional reason for impersonation (for audit logs)

Example:

typescript
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:

typescript
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 ​

vue
<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 ​

vue
<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 ​

vue
<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 ​

vue
<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 ​

vue
<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:

typescript
// 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

Next Steps ​

Released under the MIT License.