X Enterprises
Composables

useXAuth

Self-hosted JWT authentication composable with useState shared state, automatic token refresh on 401, and full auth method coverage.

useXAuth

JWT authentication composable for the nuxt-x-auth-local layer. Connects to your own backend API — any server that accepts email/password and returns a JWT works. State (user, isLoading, emailSent) lives in Nuxt's useState so all component instances on the same page share the same reactive state without a Pinia store. On any 401 response, the composable automatically calls the refresh endpoint and retries the original request once.

Usage

const {
  // State
  user,
  isLoading,
  isAuthenticated,
  emailSent,

  // Core
  login,
  signup,
  logout,

  // Password management
  forgotPassword,
  resetPassword,
  changePassword,

  // Token / session
  refreshToken,
  getCurrentUser,
  getToken,
  getAuthHeaders,

  resetState,
  config,
} = useXAuth()

Returns

State

KeyTypeDescription
userRef<AuthUser | null>The currently authenticated user. Shared via useState — all instances on the same page are in sync.
isLoadingRef<boolean>true while any auth operation or token refresh is in progress. Shared via useState.
isAuthenticatedComputedRef<boolean>true when user is not null.
emailSentRef<boolean>true after forgotPassword succeeds. Always shows success to prevent email enumeration.

Auth Methods

KeyTypeDescription
login(email: string, password: string) => Promise<AuthUser | null>POST to loginEndpoint. Stores tokens in cookies and sets user.
signup(email: string, password: string) => Promise<AuthUser | null>POST to signupEndpoint. Returns the new user on success.
logout() => Promise<true>POST to logoutEndpoint. Clears cookies and resets user. Redirects to redirects.afterLogout.
forgotPassword(email: string) => Promise<boolean>POST to forgotPasswordEndpoint. Sets emailSent = true regardless of whether the email exists.
resetPassword(token: string, newPassword: string) => Promise<true | { error: string }>POST to resetPasswordEndpoint with the token from the email link.
changePassword(currentPassword: string, newPassword: string) => Promise<true | { error: string }>POST to changePasswordEndpoint. Requires an active session.

Token / Session

KeyTypeDescription
refreshToken() => Promise<AuthTokens | null>POST to refreshEndpoint with the stored refresh cookie. Returns new tokens or null if refresh fails (cookies are then cleared).
getCurrentUser() => Promise<AuthUser | null>GET to userEndpoint with Bearer header. Updates user. Automatically called after login.
getToken() => string | nullSynchronously reads the access token from the cookie. Returns null if not present.
getAuthHeaders() => Record<string, string>Synchronously returns { Authorization: 'Bearer <token>' }. Returns {} if no token.

Utilities

KeyTypeDescription
resetState() => voidResets emailSent to false.
configComputedRef<AuthConfig>Reactive reference to the full xAuth app config from app.config.ts.

AuthUser Shape

{
  id: string
  email: string
  name: string
  avatar?: string
  emailVerified: boolean
  metadata?: Record<string, any>
}

User data is normalized from your API response via fieldMapper, which tries common field variants (id/sub/user_id, name/displayName/full_name, etc.) automatically.

Expected API Response Shapes

Login / Signup:

{ "accessToken": "eyJ...", "refreshToken": "eyJ..." }

Also accepts token as an alias for accessToken.

Current user (/auth/me):

{ "user": { "id": "1", "email": "user@example.com", "name": "Jane" } }

Or a flat object: { "id": "1", "email": "...", "name": "..." }.

Token refresh (/auth/refresh):

{ "accessToken": "eyJ...", "refreshToken": "eyJ..." }

Automatic Token Refresh

On any 401 response, fetchApi (the internal request helper) calls refreshEndpoint with the stored refresh cookie and retries the original request once. If refresh fails, all auth cookies are cleared. Disable this behavior by setting tokens.hasRefresh: false in app.config.ts.

Security

All redirect targets are validated to be relative paths starting with /. Protocol-relative (//) and absolute URLs are silently replaced with / to prevent open redirect attacks.

Examples

// Login
const user = await login('user@example.com', 'password')

// Protect an API call (synchronous — no await needed)
const headers = getAuthHeaders()
// { Authorization: 'Bearer eyJ...' }

const data = await $fetch('/api/protected', { headers })

// Change password
const result = await changePassword('oldPass', 'newPass')
if (result !== true) console.error(result.error)

// Reset password from email link
const result = await resetPassword(route.query.token as string, newPassword)
<script setup>
const { user, isAuthenticated, logout } = useXAuth()
</script>

<template>
  <div v-if="isAuthenticated">
    Welcome, {{ user?.name }}
    <UButton @click="logout">Sign Out</UButton>
  </div>
  <XAuthLogin v-else />
</template>

AI Context

composable: useXAuth
package: "@xenterprises/nuxt-x-auth-local"
use-when: >
  All authentication operations in a Nuxt app using a self-hosted JWT backend.
  Works with any API that accepts email/password and returns a JWT. State is
  shared via useState — no Pinia required. getToken and getAuthHeaders are
  synchronous for simple API call protection. Automatic 401 token refresh means
  callers never need to handle token expiry manually. Configure all endpoints
  via runtimeConfig.public.localAuth.
Copyright © 2026