@xenterprises/nuxt-x-auth-better

BetterAuth authentication layer for Nuxt
1,378
Install
npm install @xenterprises/nuxt-x-auth-better
Latest Version:0.5.0
Vue:3
License:Custom
Last Updated:Jul 28, 2026
Links: GitLab  ·  npm
Maintainer: tim-mushen

@xenterprises/nuxt-x-auth-better

BetterAuth authentication layer for Nuxt 4. Pre-built UI components, route protection, OAuth, magic link, and password reset flows — all configurable via app.config.ts.

Install

npm install @xenterprises/nuxt-x-auth-better better-auth

Setup

1. Extend the layer

// nuxt.config.ts
export default defineNuxtConfig({
  extends: ['@xenterprises/nuxt-x-auth-better'],
})

2. Configure

// app.config.ts
export default defineAppConfig({
  xAuth: {
    features: {
      oauth: true,
      magicLink: true,
      forgotPassword: true,
      signup: true,
    },
    redirects: {
      afterLogin: '/dashboard',
      afterSignup: '/dashboard',
    },
    oauthProviders: [
      { id: 'google', label: 'Google', icon: 'i-simple-icons-google' },
      { id: 'github', label: 'GitHub', icon: 'i-simple-icons-github' },
    ],
    ui: {
      showLogo: true,
      logoUrl: '/logo.svg',
      brandName: 'My App',
    },
  },
})

3. Set environment variables

NUXT_PUBLIC_X_AUTH_BASE_URL=https://api.example.com

Usage

<script setup>
const {
  user,
  isAuthenticated,
  isLoading,
  login,
  signup,
  logout,
  loginWithProvider,
  sendMagicLink,
  forgotPassword,
  resetPassword,
  changePassword,
  updateUser,
  deleteAccount,
  getToken,
  getAuthHeaders,
} = useXAuth()

await login('email@example.com', 'password')
await loginWithProvider('google')
await sendMagicLink('email@example.com')
await signup('email@example.com', 'password', 'John Doe')
await forgotPassword('email@example.com')
await changePassword('oldpass', 'newpass')
await updateUser({ name: 'New Name' })
await logout()
</script>

Options (app.config.ts)

xAuth.publicRoutes

Array of extra route paths treated as public by the global auth middleware (merged with the built-in defaults /auth/handler and /auth/logout). Entries match by exact path or path prefix.

xAuth: {
  publicRoutes: ['/docs', '/pricing'],
}

xAuth.plugins

Opt-in Better Auth client plugins, resolved when the auth client singleton is created.

Name Type Default Description
admin boolean false Include Better Auth's adminClient() plugin
stripe boolean false Include stripeClient() when the installed Better Auth exports it; otherwise a no-op with a console warning (see INTEGRATION.md)

xAuth.redirects

Name Type Default Description
login string "/auth/login" Login page path
signup string "/auth/signup" Signup page path
afterLogin string "/" Redirect after successful login
afterSignup string "/" Redirect after successful signup
afterLogout string "/auth/login" Redirect after logout
forgotPassword string "/auth/forgot-password" Forgot password page path

xAuth.features

Name Type Default Description
oauth boolean false Enable OAuth provider buttons
magicLink boolean false Enable magic link authentication
otp boolean false Enable OTP authentication
forgotPassword boolean true Enable forgot password flow
signup boolean true Enable signup form and link

xAuth.oauthProviders

Array of { id, label, icon } objects. Supported provider IDs: google, github, microsoft, facebook, twitter, apple, linkedin, discord, spotify.

xAuth.ui

Name Type Default Description
showLogo boolean true Show logo in auth layout
showBrandName boolean true Show brand name below logo
logoUrl string "" URL to logo image
brandName string "" Brand name text
tagline string "" Tagline below brand name
layout "centered" | "split" "centered" Auth page layout style
background.enabled boolean true Enable background image
background.imageUrl string "" Custom background image URL (random default if empty)
background.overlayOpacity number 55 Overlay opacity (0-100)
background.blur boolean true Blur background image
card.glass boolean false Glass morphism effect on card
card.logoUrl string "" Logo shown inside the auth card
form.showSeparator boolean true Show "or" separator between OAuth and form
legal.copyright string "" Footer copyright text
legal.links Array<{label, to}> [] Footer legal links

Composable: useXAuth()

Reactive State

Property Type Description
user Ref<AuthUser | null> Current authenticated user
isAuthenticated Ref<boolean> Whether user is authenticated
isLoading Ref<boolean> Whether session is loading
emailSent Ref<boolean> Whether magic link / reset email was sent
codeSent Ref<boolean> Whether OTP code was sent
needsEmailVerification Ref<boolean> Set when login fails because the email is unverified
session Ref<SessionData> Raw Better Auth session data
sessionError Ref<Error> Session error if any
config Ref<AuthConfig> Current auth configuration

Methods

Method Returns Description
login(email, password) AuthUser | null Email/password sign in
signup(email, password, name?) AuthUser | null Create account
logout() boolean Sign out and redirect
loginWithProvider(provider) boolean Start OAuth flow
sendMagicLink(email, options?) boolean Send magic link email
forgotPassword(email) boolean Send password reset email
resetPassword(token, newPassword) true | { error } Reset password with token
changePassword(current, new) true | { error } Change password (authenticated)
updateUser({ name?, image? }) true | { error } Update user profile
deleteAccount() boolean Permanently delete account
getCurrentUser() AuthUser | null Get current user (fetches if needed)
getToken() string | null Get access token
getAuthHeaders() Record<string, string> Get { Authorization: 'Bearer ...' }
handleMagicLinkCallback(token) true | { error } Verify magic link token
verifyEmail(token) true | { error } Verify an email verification token
resendVerificationEmail(email) boolean Resend the verification email (non-disclosing)
listSessions() AuthSession[] List the user's active sessions
revokeSession(token) boolean Revoke a session by its token
resetState() void Reset emailSent / codeSent flags

AuthUser Type

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

Components

Component Description
XAuthLogin Email/password login form with OAuth, magic link, and forgot password links
XAuthSignup Registration form with terms/privacy links
XAuthForgotPassword Password reset request form with success state
XAuthMagicLink Magic link email form with success state
XAuthOAuthButton Single OAuth provider button with brand icon
XAuthOAuthButtonGroup All configured OAuth provider buttons
XAuthHandler Callback handler for OAuth, magic link, password reset, email verification
XAuthResendVerification Resend the email verification link (used on login and in the verification handler)
XAuthActiveSessions List active sessions/devices with per-session revoke

Pages (auto-registered)

Path Description Route Type
/auth/login Login page Guest-only
/auth/signup Registration page Guest-only
/auth/forgot-password Password reset request Guest-only
/auth/magic-link Magic link authentication Guest-only
/auth/reset-password Set new password (with token) Guest-only
/auth/logout Logout handler Public
/auth/handler/* OAuth / magic link / password reset callbacks Public

All other routes are protected (require authentication).

Environment Variables

Name Required Description
NUXT_PUBLIC_X_AUTH_BASE_URL Yes Base URL of your Better Auth backend (e.g. https://api.example.com)

The auth path is configurable via runtimeConfig.public.x.auth.authPath (default: /auth).

Error Reference

All errors are displayed as toast notifications. Methods return null, false, or { error: string } on failure.

Context Error When
Login "Please enter both email and password." Empty email or password
Login "Invalid credentials." Wrong email/password (from API)
Signup "Please enter both email and password." Empty email or password
Signup "Could not create account." API error (e.g. email exists)
Forgot Password "Please enter your email address." Empty email
Reset Password "Token and new password are required" Missing token or password
Change Password "Please enter both current and new passwords." Empty input
Magic Link "Please enter your email address." Empty email
OAuth "{provider} Login Failed" OAuth flow error
Logout "Failed to sign out." API error
Update Profile "Could not update profile." API error
Delete Account "Could not delete account." API error

How It Works

This layer uses Better Auth's official Vue client (better-auth/vue) via createAuthClient. The client communicates with your Better Auth backend using cookie-based sessions (credentials: 'include').

  1. Session management: createAuthClient is instantiated once (singleton) and provides a reactive useSession() ref that auto-updates. The composable wraps this with convenience methods.

  2. Route protection: A global middleware (auth.global.ts) runs on every navigation. It classifies routes as public, guest-only, or protected, then calls getCurrentUser() to check auth state.

  3. Auth layout: All /auth/* pages use the auth layout which provides a card-style UI with background image, branding, and animations.

  4. OAuth flow: loginWithProvider() calls Better Auth's signIn.social() which redirects to the provider. The callback lands on /auth/handler/oauth-callback where the Handler component refreshes the session.

  5. Token access: getToken() and getAuthHeaders() extract the access token from the Better Auth session for use with your own API calls.

Organizations & Teams

The layer includes a full BetterAuth Organizations & Teams implementation. Enable it in app.config.ts:

xAuth: {
  features: {
    organization: true,
    teams: true,
  },
  organization: {
    enabled: true,
    teams: true,
  },
}

useOrganization() Composable

<script setup>
const {
  // State
  organizations,
  organizationsLoading,
  activeOrganization,
  activeLoading,
  activeMember,
  activeMemberRole,
  members,
  invitations,
  teams,
  isOrgAdmin,
  isOrgOwner,
  canCreateOrganization,

  // Org CRUD
  createOrganization,
  updateOrganization,
  deleteOrganization,
  setActiveOrganization,
  getFullOrganization,
  checkSlug,

  // Members
  listMembers,
  updateMemberRole,
  removeMember,
  leaveOrganization,

  // Invitations
  inviteMember,
  cancelInvitation,
  acceptInvitation,
  rejectInvitation,
  getInvitation,
  listInvitations,
  listUserInvitations,

  // Teams
  listTeams,
  createTeam,
  updateTeam,
  deleteTeam,
  setActiveTeam,
  listTeamMembers,
  addTeamMember,
  removeTeamMember,
  listUserTeams,

  // Access control
  hasPermission,
  checkRolePermission,
  getDefaultPermissions,
} = useOrganization()
</script>
Reactive State
Property Type Description
organizations Ref<Organization[]> All orgs the user belongs to
organizationsLoading Ref<boolean> Whether the org list is loading
activeOrganization Ref<Organization | null> Currently active organization
activeLoading Ref<boolean> Whether the active org is loading
activeMember Ref<OrganizationMember | null> Current user's membership in active org
activeMemberRole Ref<string | null> Role of current user in active org
members ComputedRef<OrganizationMember[]> Members of the active org
invitations ComputedRef<OrganizationInvitation[]> Pending invitations for the active org
teams ComputedRef<OrganizationTeam[]> Teams in the active org
isOrgAdmin ComputedRef<boolean> User is owner or admin of active org
isOrgOwner ComputedRef<boolean> User is owner of active org
canCreateOrganization ComputedRef<boolean> User can create new organizations
Methods
Method Description
createOrganization(name, slug?, logo?) Create a new organization
updateOrganization(data) Update organization details
deleteOrganization(organizationId?) Delete an organization
setActiveOrganization(organizationId?, organizationSlug?) Set the active organization
getFullOrganization(organizationId?, organizationSlug?) Fetch full org details
checkSlug(slug) Check if a slug is available
listMembers(organizationId?) List organization members
updateMemberRole(memberId, role, organizationId?) Update a member's role
removeMember(memberId, organizationId?) Remove a member
leaveOrganization() Leave the active organization
inviteMember(email, role, organizationId?) Invite a user by email
cancelInvitation(invitationId) Cancel a pending invitation
acceptInvitation(invitationId) Accept an invitation
rejectInvitation(invitationId) Reject an invitation
getInvitation(invitationId) Fetch invitation details
listInvitations(organizationId?) List org invitations
listUserInvitations() List current user's invitations
listTeams(organizationId?) List teams in an org
createTeam(name, organizationId?) Create a team
updateTeam(teamId, data, organizationId?) Update a team
deleteTeam(teamId, organizationId?) Delete a team
setActiveTeam(teamId) Set the active team
listTeamMembers(teamId) List members of a team
addTeamMember(teamId, memberId, organizationId?) Add a member to a team
removeTeamMember(teamId, memberId) Remove a member from a team
listUserTeams() List teams for current user
hasPermission(permissionCheck) Check if active member has a permission
checkRolePermission(role, permissions) Check permissions for a specific role
getDefaultPermissions(role) Get default permissions for a role

Components

Component Description
XAuthOrganizationSwitcher List and switch between organizations
XAuthOrganizationCreate Create a new organization
XAuthOrganizationSettings Edit / delete organization
XAuthOrganizationMembers Manage organization members
XAuthOrganizationInvite Invite a new member
XAuthOrganizationInvitations List pending invitations
XAuthTeamList List teams in an organization
XAuthTeamCreate Create a team
XAuthTeamSettings Edit / delete team
XAuthTeamMembers Manage team members

Pages (auto-registered)

Path Description
/org Organization list / switcher
/org/create Create a new organization
/org/:slug Organization dashboard
/org/:slug/settings Organization settings
/org/:slug/members Member list
/org/:slug/invite Invite member
/org/:slug/invitations Pending invitations
/org/:slug/teams Team list
/org/:slug/teams/create Create team
/org/:slug/teams/:teamId Team settings + members
/auth/handler/invitation?id=... Public invitation accept handler

Middleware

organization.global.ts runs on every navigation. For routes under /org (or any path configured in organization.protectedRoutes), it:

  1. Verifies organizations are enabled.
  2. Redirects unauthenticated users to the login page.
  3. Resolves the requested organization by slug and sets it active.
  4. Redirects non-members to the notMember redirect path.
  5. Enforces requireOwner, requireAdmin, and requiredPermissions rules on matched OrganizationRoute objects.

Server Setup & Custom Roles

Add the organization plugin to your BetterAuth server config and enable teams on both server and client:

// server/auth.ts
import { betterAuth } from 'better-auth'
import { organization } from 'better-auth/plugins'

export const auth = betterAuth({
  plugins: [
    organization({
      allowUserToCreateOrganization: true,
      teams: {
        enabled: true,
      },
    }),
  ],
})

Custom access-control roles must be configured on both server and client. See INTEGRATION.md for details.

Layer Architecture

  • nuxt.config.ts: Registers @nuxt/ui module, loads the auth CSS, and defines runtimeConfig.public.x.auth for base URL and auth path.
  • app/app.config.ts: Defines all configurable options (publicRoutes, plugins, redirects, features, OAuth providers, UI settings) with defaults. Consumer apps override these in their own app.config.ts.
  • app/composables/useXAuth.ts: Core composable wrapping Better Auth's Vue client with typed methods for all auth flows.
  • app/middleware/auth.global.ts: Global route middleware for auth-based route protection.
  • app/plugins/auth-token.ts: Provides $getAuthToken for use in other plugins/composables.
  • app/layouts/auth.vue: Visual auth layout with card design, background image, and branding.
  • app/components/XAuth/*: Pre-built form components using Nuxt UI's UAuthForm.
  • app/pages/auth/*: Auto-registered auth pages.
  • app/utils/: Field normalization utilities.

Testing

npm run test        # Watch mode
npm run test:run    # Single run

Requirements

  • Nuxt 4+
  • Better Auth 1.0+
  • @nuxt/ui v4+ (included as dependency)

License

UNLICENSED

Related Packages

@habityzer/nuxt-symfony-kinde-layer

Shared Nuxt layer for Symfony + Kinde authentication integration

3,634 0
@peterbud/nuxt-aegis

Nuxt module for authentication with JWT token generation and session management.

1,888 0
@zoltasoft/identity-consumer-nuxt

Server-side Nuxt consumer for Zolta Identity hosted authentication.

810 0
@drmhse/authos-vue

Pre-1.0 Vue and Nuxt adapter for AuthOS authentication

4,367 6

Version History

Version Released Vue License
0.5.0 ^3.0 Custom
0.4.0 ^3.0 Custom
0.3.2 ^3.0 Custom
0.3.1 ^3.0 Custom
0.3.0 ^3.0 Proprietary