Magic Link Authentication Starter for Nuxt.js - Passwordless login system with email-based authentication
npm install nuxt-magic-auth-starter


Welcome to Nuxt Magic Auth Starter, a production-ready starter template for Nuxt.js applications with passwordless magic link authentication. This starter provides a complete authentication system using email-based magic links, eliminating the need for traditional passwords.
- ๐ Magic Link Authentication - Passwordless login via email
- ๐ฏ JWT Token Management - Secure token-based authentication with automatic refresh
- ๐ง Email Provider Agnostic - Support for Console (dev), Resend, and SMTP/Nodemailer
- ๐ณ Stripe Integration - Built-in payment processing with automatic customer creation
- ๐ Subscription Paywall - Ready-to-use component for premium content protection
- ๐ค Flexible User Updates - Update any user field via REST API (PATCH endpoint)
- ๐ Complete User Data - GET endpoint returns all fields including custom ones
- ๐จ Tailwind CSS - Beautiful, responsive UI out of the box
- ๐ฆ TypeScript - Full type safety and IntelliSense
- ๐ Production Ready - Includes security best practices, rate limiting, and error handling
- ๐ง Zero Config - Works out-of-the-box with sensible defaults
- ๐ฑ Responsive Design - Mobile-first, accessible components
- ๐งช Fully Tested - 233 unit tests with Vitest
- Nuxt 4 - The Intuitive Vue Framework. Build your next Vue.js application with confidence using Nuxt.
- Tailwind CSS - A utility-first CSS framework for rapidly building custom user interfaces.
- Prisma 7 - Next-generation ORM for Node.js and TypeScript with PostgreSQL adapter.
- JWT - JSON Web Tokens for secure authentication.
- TypeScript - Typed JavaScript at any scale.
- VueUse - Collection of essential Vue Composition Utilities.
Get up and running in under 2 minutes:
``bash1. Create a new Nuxt project
npx nuxi init my-app
cd my-app
3. Extend your
nuxt.config.ts:Choose the configuration that matches your email provider:
๐บ Console (Development) - Logs magic links to terminal
`typescript
// nuxt.config.ts - Console Provider (Development)
export default defineNuxtConfig({
extends: ['nuxt-magic-auth-starter'],
modules: ['@nuxtjs/tailwindcss'],
runtimeConfig: {
// Database
databaseUrl: process.env.DATABASE_URL,
// Authentication
jwtSecret: process.env.JWT_SECRET,
// Email Provider
emailProvider: 'console',
emailConfig: {
fromEmail: 'noreply@localhost',
fromName: 'My App (Dev)'
},
public: {
appUrl: process.env.APP_URL || 'http://localhost:3000'
}
}
})
`.env file:
`bash
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
JWT_SECRET="your-super-secret-jwt-key-min-32-characters"
APP_URL="http://localhost:3000"
`
๐ง Resend - Modern email API for production
`typescript
// nuxt.config.ts - Resend Provider (Production)
export default defineNuxtConfig({
extends: ['nuxt-magic-auth-starter'],
modules: ['@nuxtjs/tailwindcss'],
runtimeConfig: {
// Database
databaseUrl: process.env.DATABASE_URL,
// Authentication
jwtSecret: process.env.JWT_SECRET,
// Email Provider
emailProvider: 'resend',
emailConfig: {
fromEmail: process.env.FROM_EMAIL,
fromName: process.env.FROM_NAME,
// Resend specific
resendApiKey: process.env.RESEND_API_KEY
},
public: {
appUrl: process.env.APP_URL
}
}
})
`.env file:
`bash
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
JWT_SECRET="your-super-secret-jwt-key-min-32-characters"
APP_URL="https://myapp.com"Resend Configuration
FROM_EMAIL="noreply@myapp.com"
FROM_NAME="My App"
RESEND_API_KEY="re_your_resend_api_key"
`
โ๏ธ AutoSend - Modern email API for developers
`typescript
// nuxt.config.ts - AutoSend Provider (Production)
export default defineNuxtConfig({
extends: ['nuxt-magic-auth-starter'],
modules: ['@nuxtjs/tailwindcss'],
runtimeConfig: {
// Database
databaseUrl: process.env.DATABASE_URL,
// Authentication
jwtSecret: process.env.JWT_SECRET,
// Email Provider
emailProvider: 'autosend',
emailConfig: {
fromEmail: process.env.FROM_EMAIL,
fromName: process.env.FROM_NAME,
// AutoSend specific
autosendApiKey: process.env.AUTOSEND_API_KEY
},
public: {
appUrl: process.env.APP_URL
}
}
})
`.env file:
`bash
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
JWT_SECRET="your-super-secret-jwt-key-min-32-characters"
APP_URL="https://myapp.com"AutoSend Configuration
FROM_EMAIL="noreply@myapp.com"
FROM_NAME="My App"
AUTOSEND_API_KEY="as_your_autosend_api_key"
`Setup Instructions:
1. Sign up at AutoSend
2. Add and verify your domain in the Domain section
3. Generate API key in Settings > API Keys
4. Use a verified email address as
FROM_EMAIL
๐ฌ Nodemailer (SMTP) - Gmail, Outlook, or custom SMTP
`typescript
// nuxt.config.ts - Nodemailer/SMTP Provider (Production)
export default defineNuxtConfig({
extends: ['nuxt-magic-auth-starter'],
modules: ['@nuxtjs/tailwindcss'],
runtimeConfig: {
// Database
databaseUrl: process.env.DATABASE_URL,
// Authentication
jwtSecret: process.env.JWT_SECRET,
// Email Provider
emailProvider: 'nodemailer',
emailConfig: {
fromEmail: process.env.FROM_EMAIL,
fromName: process.env.FROM_NAME,
// SMTP specific
smtpHost: process.env.SMTP_HOST,
smtpPort: process.env.SMTP_PORT,
smtpSecure: process.env.SMTP_SECURE,
smtpUser: process.env.SMTP_USER,
smtpPass: process.env.SMTP_PASS
},
public: {
appUrl: process.env.APP_URL
}
}
})
`.env file (Gmail example):
`bash
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
JWT_SECRET="your-super-secret-jwt-key-min-32-characters"
APP_URL="https://myapp.com"SMTP Configuration (Gmail)
FROM_EMAIL="noreply@myapp.com"
FROM_NAME="My App"
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_SECURE="false"
SMTP_USER="your-email@gmail.com"
SMTP_PASS="your-app-password"
`.env file (Outlook example):
`bash
SMTP Configuration (Outlook)
SMTP_HOST="smtp-mail.outlook.com"
SMTP_PORT="587"
SMTP_SECURE="false"
SMTP_USER="your-email@outlook.com"
SMTP_PASS="your-password"
`.env file (Custom SMTP example):
`bash
SMTP Configuration (Custom Server)
SMTP_HOST="mail.yourserver.com"
SMTP_PORT="465"
SMTP_SECURE="true"
SMTP_USER="noreply@yourserver.com"
SMTP_PASS="your-smtp-password"
`
โก Quick Config - Minimal setup with environment variables
`typescript
// nuxt.config.ts - Environment-driven configuration
export default defineNuxtConfig({
extends: ['nuxt-magic-auth-starter'],
modules: ['@nuxtjs/tailwindcss'],
runtimeConfig: {
jwtSecret: process.env.JWT_SECRET,
emailProvider: process.env.EMAIL_PROVIDER || 'console',
emailConfig: {
fromEmail: process.env.FROM_EMAIL,
fromName: process.env.FROM_NAME,
// Resend
resendApiKey: process.env.RESEND_API_KEY,
// AutoSend
autosendApiKey: process.env.AUTOSEND_API_KEY,
// SMTP
smtpHost: process.env.SMTP_HOST,
smtpPort: process.env.SMTP_PORT,
smtpSecure: process.env.SMTP_SECURE,
smtpUser: process.env.SMTP_USER,
smtpPass: process.env.SMTP_PASS
},
public: {
appUrl: process.env.APP_URL
}
}
})
`4. Set up environment and database:
`bash
Copy environment template
cp node_modules/nuxt-magic-auth-starter/.env.example .envEdit .env with your DATABASE_URL and JWT_SECRET
Initialize Prisma with the schema from the package
npx prisma init
cp node_modules/nuxt-magic-auth-starter/prisma/schema.prisma prisma/Run migrations
npx prisma migrate dev --name initStart development server
npm run dev
`๐ Done! You now have full magic link authentication. Focus on your app logic!
$3
| Feature | Description |
|---------|-------------|
|
useAuth() composable | Complete auth state management with auto token refresh |
| /api/auth/* endpoints | Ready-to-use authentication API with rate limiting |
| | Complete login form with title, description, messages |
| | Ready-to-use landing page component |
| | User dropdown with logout |
| | Styled login button with variants |
| | Show content only to logged-in users |
| | Loading indicator component |
| | Subscription paywall component |
| auth middleware | Protect routes easily |
| guest middleware | Redirect logged-in users |
| Prisma schema | User & VerificationToken models with Stripe |
| Email templates | Customizable magic link & welcome emails |
| User updates | Flexible PATCH endpoint for profile changes |
| Stripe payments | Complete payment & subscription system |
| useStripe() composable | Subscription status management |$3
When a new version is released, simply run:
`bash
npm update nuxt-magic-auth-starter
`Your customizations stay intact while you get the latest features and security updates!
---
๐ฆ Alternative: Clone as Template
For full control over the codebase or to customize everything:
`bash
Clone the repository
git clone https://github.com/leszekkrol/nuxt-magic-auth-starter.git my-app
cd my-appInstall dependencies
npm installSet up environment variables
cp .env.example .envStart PostgreSQL database
docker-compose up -dRun database migrations
npm run db:migrate(Optional) Seed with demo data
npm run db:seedStart development server
npm run dev
`โ๏ธ Configuration
$3
Create a
.env file in your project root:`bash
==============================================
DATABASE
==============================================
DATABASE_URL="postgresql://user:password@localhost:5432/magic_auth"==============================================
AUTHENTICATION
==============================================
JWT_SECRET="your-super-secret-jwt-key-min-32-characters"==============================================
APPLICATION
==============================================
APP_URL="http://localhost:3000"==============================================
EMAIL CONFIGURATION
==============================================
Provider: console | resend | autosend | nodemailer
EMAIL_PROVIDER="console"
FROM_EMAIL="noreply@yourapp.com"
FROM_NAME="Your App Name"Resend (if EMAIL_PROVIDER=resend)
RESEND_API_KEY="re_your_api_key"AutoSend (if EMAIL_PROVIDER=autosend)
AUTOSEND_API_KEY="as_your_api_key"SMTP (if EMAIL_PROVIDER=nodemailer)
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_SECURE="false"
SMTP_USER="your-email@gmail.com"
SMTP_PASS="your-app-password"Stripe (for payment processing)
STRIPE_SECRET_KEY="sk_test_your_stripe_secret_key"
STRIPE_PUBLISHABLE_KEY="pk_test_your_stripe_publishable_key"
STRIPE_WEBHOOK_SECRET="whsec_your_webhook_secret"
`๐ Authentication Flow
The magic link authentication process is designed to be secure, simple, and passwordless. Here's how it works:
`
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MAGIC LINK AUTHENTICATION FLOW โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ
โ Client โ โ Server โ โ Database โ
โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ
โ โ โ
โโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโ
โ STEP 1: REQUEST MAGIC LINK โ โ
โโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโ
โ โ โ
โ POST /api/auth/send-magic-link โ
โ { email, name? } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ โ
โ โ โ
โ โ Generate secure token โ
โ โ (crypto.randomBytes) โ
โ โ โ
โ โ Store SHA-256 hash โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ โ
โ โ Send email with link โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ ๐ง Magic Link Email โ โ
โ โ โ Click to sign in: โ โ
โ โ โ /verify?token=xxx โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ { success: true } โ โ โ
โ <โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โผ โ
โ โ User's Inbox โ
โ โ โ
โโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโ
โ STEP 2: USER CLICKS LINK โ โ
โโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโ
โ โ โ
โ GET /verify?token=xxx โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ โ
โ โ โ
โ POST /api/auth/verify-token โ โ
โ { token } โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ โ
โ โ โ
โ โ Hash token & lookup โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ โ
โ โ Validate: not expired, โ
โ โ not used, exists โ
โ โ <โโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โ โ Mark token as used โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ โ
โ โ Create/get user โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ โ
โ โ Generate JWT โ
โ โ Set HTTP-only cookie โ
โ โ โ
โ { success, user, isNewUser } โ โ
โ + Set-Cookie: auth_token โ โ
โ <โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโ
โ STEP 3: AUTHENTICATED โ โ
โโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโโโโโโโโโโโโโโโโโโโโโโโโโชโโโโโ
โ โ โ
โ GET /api/auth/me โ โ
โ Cookie: auth_token=jwt โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ>โ โ
โ โ โ
โ โ Verify JWT signature โ
โ โ Extract user ID โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโ>โ
โ โ โ
โ { user: {...} } โ โ
โ <โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโ
`$3
| Feature | Description |
|---------|-------------|
| ๐ Token Hashing | Only SHA-256 hash stored in database, raw token sent via email |
| โฐ Token Expiration | Tokens expire after 15 minutes (configurable) |
| ๐ Single Use | Each token can only be used once |
| ๐ช HTTP-Only Cookies | JWT stored in secure, HTTP-only cookie to prevent XSS |
| ๐ก๏ธ CSRF Protection | SameSite cookie attribute prevents cross-site attacks |
| ๐ง Email Verification | User proves email ownership by clicking the link |
$3
`
Token Created โโโบ Email Sent โโโบ User Clicks โโโบ Token Verified โโโบ Token Marked Used
โ โ
โ 15 min expiration โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Token Invalid After
`๐ API Endpoints
$3
| Method | Endpoint | Description |
|--------|----------|-------------|
|
POST | /api/auth/send-magic-link | Send magic link to email |
| POST | /api/auth/verify-token | Verify token and authenticate |
| GET | /api/auth/me | Get current authenticated user (returns all user fields) |
| PATCH | /api/auth/me | Update current authenticated user (any fields) |
| POST | /api/auth/logout | Clear authentication cookie |$3
| Method | Endpoint | Description |
|--------|----------|-------------|
|
GET | /api/stripe/subscription | Get current user's subscription status |
| POST | /api/stripe/checkout | Create checkout session for products/subscriptions |
| POST | /api/stripe/billing-portal | Create billing portal session for subscription management |
| POST | /api/stripe/webhook | Handle Stripe webhook events |$3
`typescript
// Request
const response = await $fetch('/api/auth/send-magic-link', {
method: 'POST',
body: {
email: 'user@example.com',
name: 'John Doe' // optional
}
})// Response
{ success: true, message: 'Magic link sent to your email' }
`$3
`typescript
// Request
const response = await $fetch('/api/auth/verify-token', {
method: 'POST',
body: { token: 'your-magic-link-token' }
})// Response
{
success: true,
user: { id: '...', email: 'user@example.com', name: 'John Doe' },
isNewUser: false
}
`$3
`typescript
// Request
const response = await $fetch('/api/auth/me')// Response - Returns ALL user fields from database
{
user: {
id: 'clx...',
email: 'user@example.com',
name: 'John Doe',
stripeCustomerId: 'cus_...', // Included if Stripe integration enabled
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z'
// Plus any additional custom fields you've added to the User model
}
}
// Or if not authenticated
{
user: null
}
`> Note: The
/api/auth/me endpoint returns all fields from the User model in your database. This means if you add custom fields to your Prisma schema (e.g., avatar, bio, role, preferences), they will automatically be included in the response without any code changes.$3
`typescript
// Request - Update single field
const response = await $fetch('/api/auth/me', {
method: 'PATCH',
body: { name: 'John Doe' }
})// Request - Update multiple fields
const response = await $fetch('/api/auth/me', {
method: 'PATCH',
body: {
name: 'John Doe',
bio: 'Full-stack developer',
avatar: 'https://example.com/avatar.jpg',
preferences: { theme: 'dark', language: 'en' }
}
})
// Response
{
success: true,
user: {
id: 'clx...',
email: 'user@example.com',
name: 'John Doe',
bio: 'Full-stack developer',
avatar: 'https://example.com/avatar.jpg',
preferences: { theme: 'dark', language: 'en' },
stripeCustomerId: 'cus_...',
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T12:30:00.000Z' // Updated automatically
}
}
`Features:
- โ
Flexible - Update any field from your User model
- โ
Bulk updates - Update multiple fields in one request
- โ
Protected fields - Automatically excludes
id and createdAt
- โ
Email validation - Checks if new email is already in use
- โ
Type-safe - Prisma validates field types automatically
- โ
Authenticated only - Requires valid auth token๐งฉ Components
$3
Complete magic link login form with customizable title, description, and messages.
`vue
title="Sign in to your account"
description="Enter your email and we'll send you a magic link"
button-text="Send Magic Link"
success-text="Check your email!"
error-text="Something went wrong"
show-name
@success="onSuccess"
@failed="onFailed"
/>
`Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
title | string | '' | Form title (h1) |
| description | string | '' | Description text below title |
| showName | boolean | false | Show optional name input field |
| buttonText | string | 'Send Magic Link' | Submit button text |
| successText | string | 'Check your email for the magic link!' | Success message |
| errorText | string | 'Failed to send magic link' | Fallback error message |
| redirectTo | string | '' | Route to redirect after success |Events:
| Event | Payload | Description |
|-------|---------|-------------|
|
@success | { email: string, name?: string } | Emitted when magic link is sent successfully |
| @failed | message: string | Emitted when sending fails |$3
Ready-to-use landing page component with features showcase.
`vue
`$3
Dropdown menu for authenticated users with profile link and logout.
`vue
`$3
Styled login button with variant support.
`vue
Sign In
`Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
to | string | '/login' | Navigation target |
| variant | string | 'primary' | Visual style variant |Variants:
primary, secondary, outline$3
Wrapper that shows content only to authenticated users.
`vue
This content is only visible to logged-in users.
Please sign in to view this content.
`$3
Loading indicator with size and color options.
`vue
`Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
size | string | 'md' | Spinner size |
| color | string | 'primary' | Color theme |
| label | string | - | Optional text label |
| containerClass | string | '' | Additional CSS classes |Sizes:
sm, md, lg | Colors: primary, white, gray$3
Shows content only to users with active Stripe subscription. Perfect for premium content, paywalls, and subscription-gated features.
`vue
Premium Content
This is only visible to subscribers!
Premium Plan Content
Only for premium subscribers!
Pro Features
Pro plan exclusive content
Premium content here
Upgrade to Premium
Get access to exclusive features
Checking subscription...
Premium content
`Props:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
|
priceId | string | - | Stripe price ID to check for (optional) |
| productId | string | - | Stripe product ID to check for (optional) |
| checkoutUrl | string | - | Custom URL for upgrade button |
| autoCheck | boolean | true | Auto-check subscription on mount |Slots:
| Slot | Description |
|------|-------------|
|
default | Content shown to subscribers |
| paywall | Custom paywall UI (has default) |
| loading | Custom loading state (has default) |
| error | Custom error message |Exposed Methods:
`typescript
const component = ref>()// Manually refresh subscription status
component.value?.checkAccess()
`Example - Real-world usage:
`vue
My Dashboard
Basic Features
Available to all users
priceId="price_premium"
checkoutUrl="/pricing?plan=premium"
>
๐ Premium Analytics
๐
Premium Feature
Unlock advanced analytics with Premium plan
- โ Real-time data
- โ Custom reports
- โ Export to CSV
`๐ Composables
$3
Composable for Stripe subscription management with reactive state.
`typescript
const {
// State
subscription, // Ref - Current subscription
loading, // Ref - Loading state
error, // Ref - Error message
hasSubscription, // ComputedRef - Has any subscription
isActive, // ComputedRef - Has active/trialing subscription
// Actions
fetchSubscription, // (options?: { priceId?: string, productId?: string }) => Promise
hasPrice, // (priceId: string) => Promise
hasProduct, // (productId: string) => Promise
clearSubscription // () => void
} = useStripe()
`Example Usage:
`vue
Status: {{ subscription?.status }}
โ ๏ธ Subscription will cancel at period end
`๐ Composables
$3
Main composable for authentication state and actions.
`typescript
const {
// State
user, // Ref - Current user
isLoggedIn, // ComputedRef - Auth status
loading, // Ref - Loading state
error, // Ref - Error message // Actions
sendMagicLink, // (email: string, options?: { name?: string }) => Promise
verifyToken, // (token: string) => Promise
logout, // () => Promise
refreshUser // () => Promise
} = useAuth()
`User Type:
`typescript
interface User {
id: string
email: string
name: string | null
createdAt?: string
}
`Example Usage:
`vue
Welcome, {{ user?.name }}!
{{ user.bio }}
`๐ก๏ธ Middleware
$3
Protects routes for authenticated users only. Redirects to
/login if not authenticated.`vue
`$3
Protects routes for non-authenticated users only. Redirects to
/dashboard if already authenticated.`vue
`๐ Pages
| Page | Path | Description |
|------|------|-------------|
| Landing |
/ | Public homepage with features |
| Login | /login | Magic link request form |
| Verify | /verify?token=... | Token verification handler |
| Dashboard | /dashboard | Protected user dashboard |
| Profile | /profile | Protected user profile |๐๏ธ Database Schema
This starter uses Prisma ORM with PostgreSQL. The schema is minimal but production-ready.
$3
`
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DATABASE SCHEMA โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ users โ โ verification_tokens โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ id visiblePK String โ โ id PK String โ
โ email UK String โโโ โ โโ email String โ
โ name String? โ โ token UK String โ
โ createdAt DateTime โ โ expires DateTime โ
โ updatedAt DateTime โ โ used Boolean โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ createdAt DateTime โ
โ updatedAt DateTime โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
PK = Primary Key UK = Unique Key โโ โ โ = Logical relation (by email)
`$3
`prisma
// =============================================================================
// Prisma Schema - Magic Link Authentication
// =============================================================================generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
}
// =============================================================================
// User Model
// =============================================================================
/// Registered application user
/// Created when user first verifies their email via magic link
model User {
id String @id @default(cuid())
/// User's email address (unique identifier for login)
email String @unique
/// User's display name (optional, can be set during registration)
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
// =============================================================================
// Verification Token Model
// =============================================================================
/// Magic link verification token
/// Stores hashed tokens with expiration and usage tracking
model VerificationToken {
id String @id @default(cuid())
/// SHA-256 hash of the actual token (never store plain tokens)
token String @unique
/// Email address this token was sent to
email String
/// Token expiration timestamp (default: 15 minutes from creation)
expires DateTime
/// Whether token has been consumed (prevents replay attacks)
used Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
/// Index for fast token lookup during verification
@@index([token])
/// Index for finding user's pending tokens
@@index([email])
/// Index for cleanup of expired tokens
@@index([expires])
@@map("verification_tokens")
}
`$3
#### User
| Field | Type | Description |
|-------|------|-------------|
|
id | String | Unique identifier (CUID format) |
| email | String | User's email address (unique, used for login) |
| name | String? | Optional display name |
| createdAt | DateTime | Account creation timestamp |
| updatedAt | DateTime | Last update timestamp |#### VerificationToken
| Field | Type | Description |
|-------|------|-------------|
|
id | String | Unique identifier (CUID format) |
| token | String | SHA-256 hash of the magic link token |
| email | String | Email address the token was sent to |
| expires | DateTime | Token expiration time (15 min default) |
| used | Boolean | Whether token has been consumed |
| createdAt | DateTime | Token creation timestamp |
| updatedAt | DateTime | Last update timestamp |$3
`
verification_tokens_token_key UNIQUE (token) Fast token lookup
verification_tokens_email_idx INDEX (email) Find user's tokens
verification_tokens_expires_idx INDEX (expires) Cleanup expired tokens
users_email_key UNIQUE (email) Prevent duplicates
`$3
`bash
npm run db:generate # Generate Prisma client
npm run db:migrate # Run migrations
npm run db:push # Push schema changes (dev only)
npm run db:seed # Seed demo data
npm run db:studio # Open Prisma Studio GUI
`$3
The
prisma/seed.ts creates a demo user for testing:`typescript
// Demo user created by seed
{
email: 'demo@example.com',
name: 'Demo User'
}
`Run with:
npm run db:seed๐ง Email Providers
$3
Default provider. Magic links are logged to the terminal - no configuration required.
`env
EMAIL_PROVIDER="console"
`$3
Modern email API for production.
`env
EMAIL_PROVIDER="resend"
RESEND_API_KEY="re_your_api_key"
`Links: Website | Documentation
$3
Email platform for developers and marketers. Send transactional and marketing emails.
`env
EMAIL_PROVIDER="autosend"
AUTOSEND_API_KEY="as_your_api_key"
`Links: Website | Documentation
$3
Universal SMTP support for Gmail, Outlook, or custom servers. Package is included in dependencies.
`env
EMAIL_PROVIDER="nodemailer"
SMTP_HOST="smtp.gmail.com"
SMTP_PORT="587"
SMTP_SECURE="false"
SMTP_USER="your-email@gmail.com"
SMTP_PASS="your-app-password"
`$3
Email templates are located in the
templates/ directory:
- magic-link.html - Magic link authentication email
- welcome.html - Welcome email for new usersTemplates support
{{placeholder}} syntax for variable substitution.๐ณ Stripe Integration
This starter includes built-in Stripe integration for payment processing and subscription management. When a user creates an account, a Stripe customer is automatically created and linked to their profile.
$3
If you're upgrading from an earlier version, follow these steps to enable Stripe integration:
1. Update the package:
`bash
npm update nuxt-magic-auth-starter
`2. Update Prisma schema:
The
User model now includes a stripeCustomerId field. Copy the updated schema:`bash
cp node_modules/nuxt-magic-auth-starter/prisma/schema.prisma prisma/
`Or manually add this field to your
prisma/schema.prisma:`prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
stripeCustomerId String? @unique // ๐ Add this line
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt @@map("users")
}
`3. Create and run database migration:
`bash
Generate Prisma client with new schema
npx prisma generateCreate migration
npx prisma migrate dev --name add-stripe-customer-idOr for production (without prompts)
npx prisma migrate deploy
`4. Install Stripe package (if not auto-installed):
`bash
npm install stripe
`5. Add Stripe configuration to
.env:`bash
Add these lines to your .env file
STRIPE_SECRET_KEY="sk_test_your_stripe_secret_key"
STRIPE_PUBLISHABLE_KEY="pk_test_your_stripe_publishable_key"
STRIPE_WEBHOOK_SECRET="whsec_your_webhook_secret"
`6. Update your
nuxt.config.ts:Add Stripe configuration to runtimeConfig:
`typescript
export default defineNuxtConfig({
extends: ['nuxt-magic-auth-starter'],
runtimeConfig: {
// ... existing config
// Add Stripe config
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
public: {
// ... existing public config
stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY
}
}
})
`7. Restart your development server:
`bash
npm run dev
`โ
Done! New users will automatically get a Stripe customer ID upon registration.
Optional - Backfill existing users:
If you have existing users without Stripe customer IDs, create a migration script:
`typescript
// scripts/backfill-stripe-customers.ts
import { PrismaClient } from '@prisma/client'
import Stripe from 'stripe'const prisma = new PrismaClient()
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-12-18.acacia',
})
async function backfillStripeCustomers() {
const usersWithoutStripe = await prisma.user.findMany({
where: { stripeCustomerId: null }
})
console.log(
Found ${usersWithoutStripe.length} users without Stripe customer ID) for (const user of usersWithoutStripe) {
try {
const customer = await stripe.customers.create({
email: user.email,
name: user.name || undefined,
metadata: { userId: user.id }
})
await prisma.user.update({
where: { id: user.id },
data: { stripeCustomerId: customer.id }
})
console.log(
โ Created Stripe customer for ${user.email})
} catch (error) {
console.error(โ Failed for ${user.email}:, error)
}
} console.log('Backfill complete!')
}
backfillStripeCustomers()
.then(() => process.exit(0))
.catch((error) => {
console.error(error)
process.exit(1)
})
`Run it with:
`bash
npx tsx scripts/backfill-stripe-customers.ts
`---
$3
1. Create a Stripe account at https://stripe.com
2. Get your API keys from Stripe Dashboard
3. Add keys to your
.env file:`bash
Get these from: https://dashboard.stripe.com/apikeys
STRIPE_SECRET_KEY="sk_test_your_stripe_secret_key"
STRIPE_PUBLISHABLE_KEY="pk_test_your_stripe_publishable_key"Optional: For webhook signature verification
Get from: https://dashboard.stripe.com/webhooks
STRIPE_WEBHOOK_SECRET="whsec_your_webhook_secret"
`4. Update your
nuxt.config.ts (already configured if you followed the quick start):`typescript
export default defineNuxtConfig({
runtimeConfig: {
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
public: {
stripePublishableKey: process.env.STRIPE_PUBLISHABLE_KEY
}
}
})
`$3
| Feature | Description |
|---------|-------------|
| ๐ซ Auto Customer Creation | Stripe customer automatically created on user registration |
| ๐ฐ Checkout Sessions | Create payment and subscription checkout flows |
| ๐ช Billing Portal | Let customers manage subscriptions and payment methods |
| ๐ Webhook Support | Handle Stripe events (subscriptions, payments, etc.) |
| ๐ Secure | Uses server-side API keys, never exposes secrets to client |
$3
#### Create Checkout Session
`typescript
// In your component or page
async function startCheckout() {
try {
const response = await $fetch('/api/stripe/checkout', {
method: 'POST',
body: {
priceId: 'price_1234567890', // Your Stripe price ID
mode: 'subscription', // or 'payment' for one-time
successUrl: window.location.origin + '/success',
cancelUrl: window.location.origin + '/cancelled'
}
})
// Redirect to Stripe checkout
window.location.href = response.url
} catch (error) {
console.error('Checkout failed:', error)
}
}
`#### Open Billing Portal
`typescript
// Let users manage their subscriptions
async function openBillingPortal() {
try {
const response = await $fetch('/api/stripe/billing-portal', {
method: 'POST',
body: {
returnUrl: window.location.origin + '/profile'
}
})
// Redirect to Stripe billing portal
window.location.href = response.url
} catch (error) {
console.error('Failed to open billing portal:', error)
}
}
`#### Example Vue Component
`vue
Choose Your Plan
`$3
To handle Stripe events (subscriptions, payments, etc.), configure webhooks in your Stripe Dashboard:
1. Add endpoint URL:
https://yourdomain.com/api/stripe/webhook2. Select events to listen to:
-
customer.subscription.created
- customer.subscription.updated
- customer.subscription.deleted
- invoice.payment_succeeded
- invoice.payment_failed
- checkout.session.completed3. Copy webhook signing secret and add to
.env:
`bash
STRIPE_WEBHOOK_SECRET="whsec_..."
`4. Customize webhook handler in
server/api/stripe/webhook.post.ts to update your database based on events.$3
The
User model includes a stripeCustomerId field:`prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
stripeCustomerId String? @unique // Automatically populated on registration
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
`$3
Use Stripe's test mode for development:
- Test card:
4242 4242 4242 4242
- Any future expiry date
- Any 3-digit CVC
- Any postal codeSee Stripe Testing Documentation for more test cards.
$3
- [ ] Replace test API keys with live keys
- [ ] Configure live webhook endpoint
- [ ] Test checkout flow end-to-end
- [ ] Test billing portal functionality
- [ ] Verify webhook events are processed correctly
- [ ] Enable Stripe Radar for fraud prevention
- [ ] Set up email receipts in Stripe Dashboard
๐งช Testing
The project includes 233 unit tests covering all utilities, API logic, composables, components, and Stripe integration.
`bash
Run tests
npm testWatch mode
npm run test:watchCoverage report
npm run test:coverage
`๐งช Development
`bash
Development server
npm run devBuild for production
npm run buildPreview production build
npm run previewGenerate static site
npm run generate
`โ๏ธ Development
It brings immense joy and excitement to know that you're keen on contributing to the projects I'm working on. There's always a world of possibilities that can be explored, and having talented individuals like you onboard can truly make a massive difference. Your interest is deeply appreciated, and it's a reminder of the magic โจ that happens when open-source developers come together and collaborate.
Open-source is the foundation of many groundbreaking innovations, and it's the community of developers like you who fuel this ceaseless evolution. Your ideas ๐ก, your code ๐ป, and your passion โค๏ธ can significantly impact the shape of the projects and contribute to the larger world of technology.
Let's build ๐ ๏ธ, create ๐จ, and revolutionize ๐ together. Let's take these projects to new heights ๐๏ธ and unlock their true potential. Your skills ๐ฏ and ideas ๐ญ are more than welcome here - they're necessary, valued, and have the potential to spark real change.
So, yes, absolutely, your participation is eagerly welcomed! I'm thrilled ๐ at the prospect of working with you, and I can't wait to see the incredible contributions you'll bring to these projects. Thanks again for showing your interest and excitement. It truly means the world! ๐
Let's do this, and let's make amazing things happen together. ๐
๐ Share with Friends!
If you're enjoying the projects and want to send some love back my way, that's music to my ears! Your support is the fuel that keeps this creative machine running, and I am forever grateful for that.Here are some super cool ways you can express your appreciation and help keep this development train chugging:
- ๐ Show some love with a GitHub star on the project! It's like applause, but for coders!
- ๐ฆ Share the love on Twitter! Tweeting about the project helps spread the word and attract more rockstars like you. Don't forget to tag me @leszekkrol and use the hashtag
#leszekkrol!Your voice is powerful, and your support means the world. Thank you, from the bottom of my heart, for your interest in the development of my community. ๐
_PS: Consider sponsoring my work (Leszek W. Krรณl) on Kup mi kawฤ_
โญ๏ธ Author
The author of the project is:
- Leszek W. Krรณl
๐ Changelog
$3
- โจ NEW: Full Stripe integration for payment processing
- โจ NEW: Automatic Stripe customer creation on user registration
- โจ NEW: component for subscription paywalls
- โจ NEW: useStripe() composable for subscription management
- โจ NEW: GET /api/stripe/subscription endpoint for checking subscription status
- โจ NEW: Billing portal endpoint for subscription management
- โจ NEW: Checkout session endpoint for purchases
- โจ NEW: Webhook handler for Stripe events
- โจ NEW: PATCH /api/auth/me endpoint for flexible user profile updates
- โจ NEW: requireUser() helper function in auth utilities
- โจ NEW: GET /api/auth/me` now returns all user fields (including custom fields)Please use the Issue Tracker tool to submit any bug reports and feature requests. When reporting bugs, remember to provide additional information about your hardware configuration and the versions of libraries you are using.
See the website ยท
Leszek W. Krรณl on Twitter