wasp-auth
Complete Wasp authentication setup and user management. Use when implementing auth, setting up login/signup, or working with user authentication. Includes minimal User model, auth configuration, helper functions, and protected routes.
When & Why to Use This Skill
This Claude skill provides a comprehensive framework for implementing secure authentication and user management within Wasp applications. It streamlines the setup of login/signup flows, OAuth integrations (Google, GitHub), and protected routes, ensuring that developers follow security best practices such as automatic password hashing and proper data modeling to prevent common vulnerabilities.
Use Cases
- Rapidly bootstrapping multi-method authentication systems including email verification, password resets, and social logins.
- Implementing secure client-side navigation and server-side authorization checks to protect sensitive application data.
- Configuring and customizing pre-built authentication UI components to maintain consistent user onboarding experiences.
- Refactoring database schemas to align with Wasp's optimized authentication architecture, ensuring clear separation between auth identities and user profiles.
| name | wasp-auth |
|---|---|
| description | Complete Wasp authentication setup and user management. Use when implementing auth, setting up login/signup, or working with user authentication. Includes minimal User model, auth configuration, helper functions, and protected routes. |
| version | 1.0 |
| last_updated | 2025-10-18 |
| allowed_tools | [Edit, Bash, Read] |
Wasp Authentication Skill
Quick Reference
When to use this skill:
- Setting up authentication
- Implementing login/signup
- Working with user data
- Creating protected routes
- Accessing email/username fields
Critical Rules
CRITICAL: User model typically ONLY needs id field
NEVER add: email, emailVerified, password, username to User model (Wasp manages these)
ALWAYS use: getEmail(user) and getUsername(user) helpers to access auth fields
Complete Auth Setup Workflow
1. User Model (Minimal)
File: app/schema.prisma
model User {
id Int @id @default(autoincrement())
// Auth fields (email, password, etc.) managed by Wasp in separate tables
// Add only NON-AUTH fields here:
// profileImageUrl String?
// timeZone String? @default("UTC")
}
CRITICAL RULES:
- ✅ User model ONLY needs
idfield for auth - ❌ NEVER add:
email,emailVerified,password,username - ✅ These are managed by Wasp in
AuthandAuthIdentitytables - ✅ Exception: Advanced pattern if you need email on User for queries (see below)
2. Auth Configuration
File: app/main.wasp
app myApp {
wasp: { version: "^0.18.0" },
title: "My App",
auth: {
userEntity: User, // Links to your User model
methods: {
// Username/password authentication
usernameAndPassword: {},
// Email/password with verification
email: {
fromField: {
name: "My App",
email: "noreply@myapp.com"
},
emailVerification: {
clientRoute: EmailVerificationRoute
},
passwordReset: {
clientRoute: PasswordResetRoute
}
},
// OAuth providers (requires env vars)
google: {},
github: {},
},
onAuthFailedRedirectTo: "/login",
},
emailSender: {
provider: Dummy // Dev: prints to console
// provider: SMTP // Production: SendGrid, MailGun, etc.
}
}
// Required routes for email auth
route EmailVerificationRoute {
path: "/auth/verify-email",
to: EmailVerificationPage
}
page EmailVerificationPage {
component: import { EmailVerification } from "@src/auth/EmailVerificationPage.tsx"
}
route PasswordResetRoute {
path: "/auth/reset-password",
to: PasswordResetPage
}
page PasswordResetPage {
component: import { PasswordReset } from "@src/auth/PasswordResetPage.tsx"
}
3. Auth Pages
Use Wasp's pre-built components:
// src/auth/LoginPage.tsx
import { LoginForm } from 'wasp/client/auth'
export function LoginPage() {
return (
<div className="container mx-auto max-w-md">
<h1>Login</h1>
<LoginForm />
</div>
)
}
// src/auth/SignupPage.tsx
import { SignupForm } from 'wasp/client/auth'
export function SignupPage() {
return (
<div className="container mx-auto max-w-md">
<h1>Sign Up</h1>
<SignupForm />
</div>
)
}
4. Protected Routes
Client-side protection:
import { useAuth } from 'wasp/client/auth'
import { Redirect } from 'wasp/client/router'
export function ProtectedPage() {
const { data: user, isLoading, error } = useAuth()
if (isLoading) return <div>Loading...</div>
if (error || !user) {
return <Redirect to="/login" />
}
// User is authenticated
return <div>Protected content for {user.id}</div>
}
Server-side protection (operations):
import { HttpError } from "wasp/server";
import type { MyQuery } from "wasp/server/operations";
export const myQuery: MyQuery = async (args, context) => {
// ✅ ALWAYS check auth first
if (!context.user) throw new HttpError(401);
// Safe to proceed
return context.entities.Task.findMany({
where: { userId: context.user.id },
});
};
5. Accessing Auth Fields
CRITICAL: User object structure differs between AuthUser and User entity
Client-Side (AuthUser)
import { useAuth, getEmail, getUsername } from 'wasp/client/auth'
export function ProfileComponent() {
const { data: user } = useAuth()
// ✅ CORRECT - Use helpers
const email = getEmail(user) // string | null
const username = getUsername(user) // string | null
// ❌ WRONG - Direct access FAILS
console.log(user.email) // UNDEFINED!
console.log(user.username) // UNDEFINED!
// ✅ NULL SAFETY (REQUIRED)
if (email) {
return <div>Email: {email}</div>
}
return <div>No email</div>
}
Server-Side (Operations)
import { getEmail, getUsername } from "wasp/auth";
import { HttpError } from "wasp/server";
export const myQuery = async (args, context) => {
if (!context.user) throw new HttpError(401);
// ✅ CORRECT - Use helpers
const email = getEmail(context.user); // string | null
const username = getUsername(context.user); // string | null
// ✅ NULL SAFETY (REQUIRED)
if (!email) {
throw new HttpError(400, "Email required");
}
// Safe to use email
console.log(email);
};
Advanced: Email on User Model
IF you need email field on User entity for queries:
A. Add to schema.prisma
model User {
id Int @id @default(autoincrement())
email String? @unique // Add this
}
B. Populate via userSignupFields
File: app/main.wasp
auth: {
userEntity: User,
methods: {
email: {
userSignupFields: import { getEmailUserFields } from "@src/auth/userSignupFields"
}
}
}
File: src/auth/userSignupFields.ts
import { defineUserSignupFields } from "wasp/auth/providers/types";
import { z } from "zod";
const userDataSchema = z.object({
email: z.string().email(),
});
export const getEmailUserFields = defineUserSignupFields({
email: (data) => {
const userData = userDataSchema.parse(data);
return { email: userData.email };
},
});
C. Run Migration
wasp db migrate-dev "Add email to User"
../scripts/safe-start.sh # RESTART for types (multi-worktree safe)
Password Security
NEVER save passwords as plain text
// ❌ WRONG - Plain text password (INSECURE!)
await context.entities.User.create({
data: {
email: args.email,
password: args.password // SECURITY VULNERABILITY!
}
})
// ✅ CORRECT - Use Wasp auth (handles hashing automatically)
// Configure in main.wasp:
auth: {
methods: {
usernameAndPassword: {}, // Wasp handles password hashing
email: {} // Wasp handles password hashing
}
}
Rule: Use Wasp auth system. NEVER implement password storage manually.
Common Auth Errors
Error: user.email is undefined
Cause: Trying to access email directly instead of using helper
Fix:
// ❌ WRONG
const email = user.email;
// ✅ CORRECT
import { getEmail } from "wasp/client/auth";
const email = getEmail(user);
Error: Cannot read property 'email' of null
Cause: Not checking for null before accessing auth fields
Fix:
const email = getEmail(user)
if (!email) {
// Handle missing email
return <div>Email not available</div>
}
// Safe to use email
Error: Auth not working
Diagnostic steps:
- Verify auth config in main.wasp
- Check userEntity matches User model name in schema.prisma
- Verify .env.server has required variables (for OAuth)
- Check Wasp server logs for auth errors
- Restart
../scripts/safe-start.sh(multi-worktree safe)
Environment Variables (OAuth)
For Google OAuth:
# .env.server
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
For GitHub OAuth:
# .env.server
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"
Critical Rules Checklist
✅ DO:
- Use minimal User model (id only)
- Use helper functions (getEmail, getUsername)
- Check for null before using auth fields
- Use Dummy email provider for development
- Restart after auth config changes
- Always check
if (!context.user)in operations - Use Wasp auth for password hashing
❌ NEVER:
- Add email/password fields to User model (unless using advanced pattern)
- Access user.email directly (use getEmail helper)
- Forget null checks for auth fields
- Assume all users have all auth methods
- Save passwords as plain text
- Skip auth check in operations
Quick Setup Checklist
Initial auth setup:
- Create minimal User model (id only) in schema.prisma
- Configure auth in main.wasp
- Create LoginPage and SignupPage
- Set up email routes (if using email auth)
- Configure email provider (Dummy for dev)
- Run migration:
wasp db migrate-dev "Add auth" - Restart
../scripts/safe-start.sh(multi-worktree safe) - Test login/signup flow
References
- Complete guide:
docs/TROUBLESHOOTING-GUIDE.md(Auth section) - Wasp auth docs: https://wasp.sh/docs/auth/overview