Lucia Auth
Authentication Library
Lucia Auth
Overview
Lucia Auth is a lightweight open-source authentication library gaining attention in 2025. Specializing in session-based authentication, it features a simple yet highly flexible design. With full TypeScript support, database-agnostic, and framework-neutral architecture, it works across diverse frameworks including Next.js, SvelteKit, Astro, Solid, and Nuxt. By eliminating complex configurations and providing a minimal API that allows developers full control over authentication logic, it delivers built-in security best practices.
Details
Lucia Auth is a next-generation authentication library designed under the philosophy that "authentication should not be complex." It provides authentication foundations including password hashing, session management, CSRF protection, and secure cookie generation as core features, enabling flexible data persistence through database adapters (Prisma, Drizzle, MongoDB, Firebase, etc.). Unlike traditional libraries, its design that doesn't impose authentication flows or UI elements allows developers to freely customize authentication systems according to requirements. Session-based authentication avoids JWT management complexity, providing a more intuitive and secure authentication experience.
Key Features
- Lightweight Design: High-performance authentication features with minimal bundle size
- Framework Neutral: Unified API usage across React, Vue, Svelte, etc.
- Database Agnostic: Rich database adapter support
- Full TypeScript Support: End-to-end type safety
- Session-Based: Simple session management without JWT requirements
- Developer Friendly: Rapid deployment with minimal configuration
Advantages and Disadvantages
Advantages
- Reduced learning cost through simple and understandable API
- Framework neutrality enables reusability across diverse environments
- Lightweight design minimizes bundle size and enables high-speed operation
- Excellent development-time type safety with full TypeScript support
- Flexible integration through rich database adapters
- High customizability through complete control of authentication flows
Disadvantages
- Limited track record and community as a relatively new library
- Advanced features (SSO, MFA, etc.) require custom implementation
- Documentation is in development with limited learning resources
- No enterprise support or SLA provided
- Limited OAuth2.0/OpenID Connect support
- Concerns about long-term support in large-scale projects
Reference Pages
Usage Examples
Installation and Setup
# Install Lucia Auth
npm install lucia
# Install database adapter (example: Prisma)
npm install @lucia-auth/adapter-prisma prisma
npm install bcrypt
npm install @types/bcrypt # For TypeScript
# Other adapter examples
npm install @lucia-auth/adapter-drizzle # Drizzle ORM
npm install @lucia-auth/adapter-mongodb # MongoDB
npm install @lucia-auth/adapter-sqlite # SQLite
Basic Configuration with Prisma
// lib/lucia.ts - Lucia Auth Configuration
import { lucia } from "lucia";
import { prisma } from "@lucia-auth/adapter-prisma";
import { PrismaClient } from "@prisma/client";
import { webcrypto } from "crypto";
// WebCrypto polyfill for Node.js environment
globalThis.crypto = webcrypto as Crypto;
const client = new PrismaClient();
export const auth = lucia({
adapter: prisma(client, {
user: "user", // Prisma model name
key: "key", // Prisma model name
session: "session" // Prisma model name
}),
env: process.env.NODE_ENV === "development" ? "DEV" : "PROD",
middleware: "node", // For Next.js
sessionCookie: {
expires: false // Persistent sessions
},
getUserAttributes: (data) => {
return {
// Include user attributes in session
email: data.email,
name: data.name,
role: data.role
};
}
});
export type Auth = typeof auth;
// prisma/schema.prisma - Prisma Schema Definition
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql" // MySQL, SQLite also supported
url = env("DATABASE_URL")
}
model User {
id String @id @unique
email String @unique
name String?
role String @default("user")
auth_session Session[]
key Key[]
@@map("users")
}
model Session {
id String @id @unique
user_id String
active_expires BigInt
idle_expires BigInt
user User @relation(references: [id], fields: [user_id], onDelete: Cascade)
@@index([user_id])
@@map("sessions")
}
model Key {
id String @id @unique
hashed_password String?
user_id String
user User @relation(references: [id], fields: [user_id], onDelete: Cascade)
@@index([user_id])
@@map("keys")
}
User Registration Functionality
// lib/auth.ts - Authentication Helper Functions
import { auth } from "./lucia";
import { generateId } from "lucia";
import bcrypt from "bcrypt";
export const createUser = async (email: string, password: string, name?: string) => {
try {
// Hash password
const hashedPassword = await bcrypt.hash(password, 12);
// Create user
const userId = generateId(15);
const user = await auth.createUser({
userId,
key: {
providerId: "email", // Provider ID
providerUserId: email.toLowerCase(), // Provider user ID
password: hashedPassword
},
attributes: {
email: email.toLowerCase(),
name: name || "",
role: "user"
}
});
return user;
} catch (error) {
throw new Error("User creation failed");
}
};
export const signInUser = async (email: string, password: string) => {
try {
// User authentication
const key = await auth.useKey("email", email.toLowerCase(), password);
const session = await auth.createSession({
userId: key.userId,
attributes: {}
});
return session;
} catch (error) {
throw new Error("Authentication failed");
}
};
export const signOutUser = async (sessionId: string) => {
try {
await auth.invalidateSession(sessionId);
} catch (error) {
throw new Error("Sign out failed");
}
};
Next.js App Router Integration
// app/api/auth/signup/route.ts - User Registration API
import { NextRequest, NextResponse } from "next/server";
import { createUser } from "@/lib/auth";
import { auth } from "@/lib/lucia";
import { cookies } from "next/headers";
export async function POST(request: NextRequest) {
try {
const { email, password, name } = await request.json();
// Validation
if (!email || !password) {
return NextResponse.json(
{ error: "Email and password are required" },
{ status: 400 }
);
}
if (password.length < 8) {
return NextResponse.json(
{ error: "Password must be at least 8 characters" },
{ status: 400 }
);
}
// Create user
const user = await createUser(email, password, name);
// Create session
const session = await auth.createSession({
userId: user.userId,
attributes: {}
});
// Set session cookie
const sessionCookie = auth.createSessionCookie(session);
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
return NextResponse.json({
success: true,
user: {
id: user.userId,
email: user.email,
name: user.name
}
});
} catch (error) {
return NextResponse.json(
{ error: "Account creation failed" },
{ status: 500 }
);
}
}
// app/api/auth/signin/route.ts - Sign In API
import { NextRequest, NextResponse } from "next/server";
import { signInUser } from "@/lib/auth";
import { auth } from "@/lib/lucia";
import { cookies } from "next/headers";
export async function POST(request: NextRequest) {
try {
const { email, password } = await request.json();
if (!email || !password) {
return NextResponse.json(
{ error: "Email and password are required" },
{ status: 400 }
);
}
// User authentication
const session = await signInUser(email, password);
// Set session cookie
const sessionCookie = auth.createSessionCookie(session);
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
return NextResponse.json({
success: true,
session: {
id: session.sessionId,
userId: session.user.userId
}
});
} catch (error) {
return NextResponse.json(
{ error: "Authentication failed" },
{ status: 401 }
);
}
}
// app/api/auth/signout/route.ts - Sign Out API
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/lucia";
import { cookies } from "next/headers";
export async function POST(request: NextRequest) {
try {
const sessionId = cookies().get(auth.sessionCookieName)?.value ?? null;
if (!sessionId) {
return NextResponse.json({ error: "Session not found" }, { status: 401 });
}
// Invalidate session
await auth.invalidateSession(sessionId);
// Clear cookie
const sessionCookie = auth.createBlankSessionCookie();
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json({ error: "Sign out failed" }, { status: 500 });
}
}
Authentication Protection with Middleware
// middleware.ts - Next.js Middleware
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/lucia";
export async function middleware(request: NextRequest) {
const sessionId = request.cookies.get(auth.sessionCookieName)?.value ?? null;
// Define protected routes
const protectedPaths = ["/dashboard", "/profile", "/admin"];
const isProtectedRoute = protectedPaths.some(path =>
request.nextUrl.pathname.startsWith(path)
);
if (isProtectedRoute) {
if (!sessionId) {
// Redirect to login if unauthenticated
return NextResponse.redirect(new URL("/login", request.url));
}
try {
// Validate session
const { session, user } = await auth.validateSession(sessionId);
if (!session) {
// Redirect to login if invalid session
return NextResponse.redirect(new URL("/login", request.url));
}
// Add user info to headers for valid session
const response = NextResponse.next();
response.headers.set("x-user-id", user.userId);
response.headers.set("x-user-email", user.email);
return response;
} catch (error) {
return NextResponse.redirect(new URL("/login", request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/profile/:path*", "/admin/:path*"]
};
Authentication Usage in React Components
// components/LoginForm.tsx - Login Form
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
export default function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError("");
try {
const response = await fetch("/api/auth/signin", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (response.ok) {
router.push("/dashboard");
router.refresh();
} else {
setError(data.error || "Login failed");
}
} catch (error) {
setError("Network error occurred");
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
Email
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
/>
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
Password
</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
/>
</div>
{error && (
<div className="text-red-600 text-sm">{error}</div>
)}
<button
type="submit"
disabled={loading}
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
>
{loading ? "Signing in..." : "Sign In"}
</button>
</form>
);
}
Authentication State Retrieval in Server Components
// app/dashboard/page.tsx - Dashboard Page
import { auth } from "@/lib/lucia";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
export default async function DashboardPage() {
const sessionId = cookies().get(auth.sessionCookieName)?.value ?? null;
if (!sessionId) {
redirect("/login");
}
try {
const { session, user } = await auth.validateSession(sessionId);
if (!session) {
redirect("/login");
}
return (
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="border-4 border-dashed border-gray-200 rounded-lg p-6">
<h1 className="text-3xl font-bold text-gray-900 mb-4">
Dashboard
</h1>
<div className="bg-white shadow overflow-hidden sm:rounded-lg">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
User Information
</h3>
<div className="mt-5 border-t border-gray-200">
<dl className="divide-y divide-gray-200">
<div className="py-3 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4">
<dt className="text-sm font-medium text-gray-500">
User ID
</dt>
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{user.userId}
</dd>
</div>
<div className="py-3 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4">
<dt className="text-sm font-medium text-gray-500">
Email
</dt>
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{user.email}
</dd>
</div>
<div className="py-3 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4">
<dt className="text-sm font-medium text-gray-500">
Name
</dt>
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{user.name || "Not set"}
</dd>
</div>
<div className="py-3 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4">
<dt className="text-sm font-medium text-gray-500">
Role
</dt>
<dd className="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
{user.role}
</dd>
</div>
</dl>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
} catch (error) {
redirect("/login");
}
}