@xenterprises/nuxt-x-auth-better
| 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 |
@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').
-
Session management:
createAuthClientis instantiated once (singleton) and provides a reactiveuseSession()ref that auto-updates. The composable wraps this with convenience methods. -
Route protection: A global middleware (
auth.global.ts) runs on every navigation. It classifies routes as public, guest-only, or protected, then callsgetCurrentUser()to check auth state. -
Auth layout: All
/auth/*pages use theauthlayout which provides a card-style UI with background image, branding, and animations. -
OAuth flow:
loginWithProvider()calls Better Auth'ssignIn.social()which redirects to the provider. The callback lands on/auth/handler/oauth-callbackwhere the Handler component refreshes the session. -
Token access:
getToken()andgetAuthHeaders()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:
- Verifies organizations are enabled.
- Redirects unauthenticated users to the login page.
- Resolves the requested organization by slug and sets it active.
- Redirects non-members to the
notMemberredirect path. - Enforces
requireOwner,requireAdmin, andrequiredPermissionsrules on matchedOrganizationRouteobjects.
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/uimodule, loads the auth CSS, and definesruntimeConfig.public.x.authfor 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 ownapp.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$getAuthTokenfor 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'sUAuthForm.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
Shared Nuxt layer for Symfony + Kinde authentication integration
Nuxt module for authentication with JWT token generation and session management.
Server-side Nuxt consumer for Zolta Identity hosted authentication.
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 | |
| 0.2.6 | ^3.0 | Proprietary | |
| 0.2.5 | ^3.0 | Proprietary | |
| 0.2.4 | ^3.0 | Proprietary | |
| 0.2.2 | ^3.0 | Proprietary | |
| 0.2.1 | ^3.0 | Proprietary | |
| 0.2.0 | ^3.0 | Proprietary | |
| 0.1.1 | ^3.0 | Proprietary |