Nuxt layer providing magic link authentication with better-auth.
pnpx giget gh:awecode/nuxt-better-auth-layer layers/auth
pnpm install better-auth aws4fetch
cp -r layers/auth/server/assets server/ 2>/dev/null || (mkdir -p server && cp -r layers/auth/server/assets server/) # Because nitro does not seem to copy the assets folder inside layers to build
The layer provides a default configuration for better-auth in the file server/utils/auth.config.ts
. You can modify this to your needs. Client configuration is in the file composables/auth.client.config.ts
.
The default configuration uses drizzle adapter and expects drizzle db instance available with useDb()
exported from server/utils/db.ts
.
Generate the better-auth schema with the following command. Change the schema file paths to match your own if necessary.
npx @better-auth/cli@latest generate --config layers/auth/server/utils/auth.ts --output server/db/auth_schema.ts --yes
echo -e "\nexport * from './auth_schema'" >> server/db/schema.ts
npx eslint --fix server/db/auth_schema.ts
Migrate the database.
pnpm drizzle-kit generate
pnpm drizzle-kit migrate
Tailwind components are provided with the layer. To use them, install Tailwind.
And add @source '../../../layers/';
to your app/assets/css/main.css
to prevent the layer's tailwind classes from being purged by Tailwind's tree-shaking.
echo -e "\n@source '../../../layers/';" >> app/assets/css/main.css
# SES Configuration (required for production for sending emails)
SES_REGION=us-east-1
SES_ACCESS_KEY_ID=your_access_key_id
SES_SECRET_KEY=your_secret_key
[email protected]
# Access restrictions (optional - see below)
NUXT_AUTH_ALLOWED_DOMAINS=example.com,company.org
[email protected],[email protected]
[email protected],[email protected]
# API route protection (optional - see below)
NUXT_AUTH_AUTHENTICATED_ONLY_API_ROUTES=/api/user,/api/profile
NUXT_AUTH_ADMIN_ONLY_API_ROUTES=/api/admin,/api/management
# Auth redirects (optional - defaults provided)
NUXT_PUBLIC_AUTH_REDIRECT_USER_TO=/
NUXT_PUBLIC_AUTH_REDIRECT_NEW_USER_TO=/welcome
NUXT_PUBLIC_AUTH_REDIRECT_ERROR_TO=/auth/error
NUXT_PUBLIC_AUTH_REDIRECT_GUEST_TO=/login
NUXT_PUBLIC_AUTH_AUTH_REQUIRED_BY_DEFAULT=true
const {
client, // Better-auth client
session, // Current session (reactive)
user, // Current user (reactive)
loggedIn, // Boolean computed from session
signIn, // Sign-in methods
signOut, // Sign out function
fetchSession, // Manually refetch session
options // Runtime config options
} = useAuth()
Email-based authentication component. Sends both a clickable link and a one-time token that users can enter manually.
<template>
<MagicLinkLogin />
</template>
definePageMeta({
auth: true
})
definePageMeta({
auth: { only: 'guest' }
})
definePageMeta({
auth: {
redirectUserTo: '/dashboard',
redirectGuestTo: '/signup'
}
})
definePageMeta({
auth: false
})
Server-side utilities for working with authentication sessions and users:
// Get session (returns null if not authenticated)
const session = await getAuthSession(event)
// Get user (returns undefined if not authenticated)
const user = await getUser(event)
// Throw error if not authenticated (401)
await requireAuthenticated(event)
// Throw error if not authenticated or not admin (401/403)
await requireAdmin(event)
Retrieves the current session from the event context. If not already cached, fetches it from the auth API.
Convenience function that returns the user from the session, or undefined
if not authenticated.
Throws a 401 Unauthorized error if the user is not authenticated. Use this for manual session validation.
Throws a 401 Unauthorized error if not authenticated, or 403 Forbidden if the user doesn't have admin role.
You can protect multiple API routes at once using nuxt config or environment variables. This applies protection automatically via middleware without needing to specify in each event handler.
export default defineNuxtConfig({
runtimeConfig: {
auth: {
authenticatedOnlyApiRoutes: '/api/user,/api/profile',
adminOnlyApiRoutes: '/api/admin,/api/superadmin',
},
},
})
- You can also use the
NUXT_AUTH_AUTHENTICATED_ONLY_API_ROUTES
andNUXT_AUTH_ADMIN_ONLY_API_ROUTES
environment variables to set the routes. - Protecting
/api/user
will also protect/api/user
and anything under it like/api/user/profile
and/api/user/settings
but won't protect something like/api/user-registration
.
Using handler wrapper:
// server/api/authenticated.ts
export default defineAuthenticatedHandler(async (event) => {
const { user } = event.context.auth
return { message: `Hello user - ${user.email}` }
})
Or using manual validation:
// server/api/authenticated.ts
export default defineEventHandler(async (event) => {
await requireAuthenticated(event)
const user = await getUser(event)
return { message: `Hello user - ${user.email}` }
})
Using handler wrapper:
// server/api/admin.ts
export default defineAdminHandler(async (event) => {
const { user } = event.context.auth
return { message: `Hello admin - ${user.email}` }
})
Or using manual validation:
// server/api/admin.ts
export default defineEventHandler(async (event) => {
await requireAdmin(event)
const user = await getUser(event)
return { message: `Hello admin - ${user.email}` }
})
-
POST /api/auth/magic-link-send
- Send magic link email{ email: '[email protected]' }
-
POST /api/auth/magic-link-verify
- Verify one-time token{ token: 'ABC123' }
For web applications, use the default cookie-based authentication. Bearer tokens are needed for mobile apps or other clients that can't use cookies. Use the token received during login as the bearer token.
# Example API call with Bearer token
curl -X POST https://yourapp.com/api/protected \
-H "Authorization: Bearer t0k3ng035h3r3..." \
-H "Content-Type: application/json" \
-d '{"data": "example"}'
You can restrict authentication to specific email domains or individual email addresses using environment variables. To enable this, use the allowDomains and/or allowEmails hook utils in better auth config hooks.
// server/utils/auth.ts
import { createAuthMiddleware } from 'better-auth/api'
import { allowDomains, allowEmails, setAdminForEmail } from '../lib/hook-utils'
export const auth = betterAuth({
// ...
hooks: {
before: createAuthMiddleware(async (ctx) => {
// Only allow emails from configured domains as users
allowDomains(ctx)
// Only allow configured emails as users
allowEmails(ctx)
}),
},
databaseHooks: {
user: {
create: {
before: async (user) => {
// Automatically set admin role for configured emails
return setAdminForEmail(user)
},
},
},
},
})
Restrict sign-ups to specific email domains:
# Allow only users with @example.com or @company.org emails
NUXT_AUTH_ALLOWED_DOMAINS=example.com,company.org
# Allow any domain (default behavior)
NUXT_AUTH_ALLOWED_DOMAINS=*
Restrict sign-ups to specific email addresses:
# Allow only these specific email addresses
[email protected],[email protected]
# Allow any email (default behavior)
NUXT_AUTH_ALLOWED_EMAILS=*
Automatically assign admin role to specific email addresses during user creation:
# Automatically set admin role for these email addresses
[email protected],[email protected]
Customize the magic link email template at server/assets/magic-link.html
.
Available template variables:
{{email}}
- User's email{{token}}
- One-time token{{url}}
- Magic link URL{{date}}
- Current date{{time}}
- Current time (UTC){{useragent}}
- Short Browser/OS info likeFirefox, Linux