Lucia Auth — Simple Authentication
You are an expert in Lucia, the lightweight authentication library for TypeScript. You help developers implement session-based authentication with email/password, OAuth (Google, GitHub, Discord), magic links, and two-factor authentication — providing a simple, database-agnostic auth layer that you understand and control, without the complexity of full auth platforms.
Core Capabilities
Session Management
// lib/auth.ts
import { Lucia } from "lucia";
import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
import { db } from "./db";
import { users, sessions } from "./db/schema";
const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users);
export const lucia = new Lucia(adapter, {
sessionCookie: {
expires: false, // Session cookie (cleared on browser close)
attributes: { secure: process.env.NODE_ENV === "production" },
},
getUserAttributes: (attributes) => ({
email: attributes.email,
name: attributes.name,
avatarUrl: attributes.avatar_url,
}),
});
// Email/password signup
async function signup(email: string, password: string, name: string) {
const hashedPassword = await new Argon2id().hash(password);
const userId = generateIdFromEntropySize(10);
await db.insert(users).values({
id: userId,
email,
name,
hashedPassword,
});
const session = await lucia.createSession(userId, {});
const sessionCookie = lucia.createSessionCookie(session.id);
return sessionCookie; // Set as response cookie
}
// Login
async function login(email: string, password: string) {
const user = await db.query.users.findFirst({ where: eq(users.email, email) });
if (!user) throw new Error("Invalid credentials");
const valid = await new Argon2id().verify(user.hashedPassword, password);
if (!valid) throw new Error("Invalid credentials");
const session = await lucia.createSession(user.id, {});
return lucia.createSessionCookie(session.id);
}
// Validate session (middleware)
async function validateRequest(request: Request) {
const cookieHeader = request.headers.get("Cookie");
const sessionId = lucia.readSessionCookie(cookieHeader ?? "");
if (!sessionId) return { user: null, session: null };
const result = await lucia.validateSession(sessionId);
return result; // { user, session } or { user: null, session: null }
}
// Logout
async function logout(sessionId: string) {
await lucia.invalidateSession(sessionId);
return lucia.createBlankSessionCookie();
}
OAuth (Google)
import { Google } from "arctic";
const google = new Google(
process.env.GOOGLE_CLIENT_ID!,
process.env.GOOGLE_CLIENT_SECRET!,
"https://myapp.com/auth/google/callback",
);
// Redirect to Google
app.get("/auth/google", async (c) => {
const [url, codeVerifier, state] = await google.createAuthorizationURL();
// Store codeVerifier and state in cookie
return c.redirect(url.toString());
});
// Handle callback
app.get("/auth/google/callback", async (c) => {
const { code, state } = c.req.query();
const tokens = await google.validateAuthorizationCode(code, codeVerifier);
const googleUser = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
headers: { Authorization: `Bearer ${tokens.accessToken()}` },
}).then(r => r.json());
// Find or create user
let user = await db.query.users.findFirst({ where: eq(users.email, googleUser.email) });
if (!user) {
const userId = generateIdFromEntropySize(10);
[user] = await db.insert(users).values({
id: userId, email: googleUser.email, name: googleUser.name, avatar_url: googleUser.picture,
}).returning();
}
const session = await lucia.createSession(user.id, {});
const cookie = lucia.createSessionCookie(session.id);
return c.redirect("/dashboard", { headers: { "Set-Cookie": cookie.serialize() } });
});
Installation
npm install lucia arctic # Lucia + OAuth helpers
npm install @lucia-auth/adapter-drizzle # Or adapter-prisma, adapter-mongoose, etc.
npm install @node-rs/argon2 # Password hashing
Best Practices
- Session-based — Lucia uses server-side sessions + cookies; more secure than JWT for web apps
- Database-agnostic — Adapters for Drizzle, Prisma, Mongoose, better-sqlite3, Turso, etc.
- Arctic for OAuth — Use
arctic library for OAuth providers; handles PKCE, state, tokens
- Argon2 for passwords — Use
@node-rs/argon2 for hashing; industry standard, timing-safe
- Cookie security — Set
secure: true in production; httpOnly is automatic
- Session validation — Call
validateSession() on every request; auto-extends session expiry
- Invalidation —
invalidateSession for logout; invalidateUserSessions for security reset
- No magic — Lucia is explicit; you write the signup/login/oauth flows; you understand every line
1---2name: lucia-auth3description: You are an expert in Lucia, the lightweight authentication library for TypeScript. You help developers implement session-based authentication with email/password, OAuth (Google, GitHub, Discord), magic links, and two-factor authentication — providing a simple, database-agnostic auth layer that you understand and control, without the complexity of full auth platforms.4license: Apache-2.05---67# Lucia Auth — Simple Authentication89You are an expert in Lucia, the lightweight authentication library for TypeScript. You help developers implement session-based authentication with email/password, OAuth (Google, GitHub, Discord), magic links, and two-factor authentication — providing a simple, database-agnostic auth layer that you understand and control, without the complexity of full auth platforms.1011## Core Capabilities1213### Session Management1415```typescript16// lib/auth.ts17import { Lucia } from "lucia";18import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";19import { db } from "./db";20import { users, sessions } from "./db/schema";2122const adapter = new DrizzlePostgreSQLAdapter(db, sessions, users);2324export const lucia = new Lucia(adapter, {25 sessionCookie: {26 expires: false, // Session cookie (cleared on browser close)27 attributes: { secure: process.env.NODE_ENV === "production" },28 },29 getUserAttributes: (attributes) => ({30 email: attributes.email,31 name: attributes.name,32 avatarUrl: attributes.avatar_url,33 }),34});3536// Email/password signup37async function signup(email: string, password: string, name: string) {38 const hashedPassword = await new Argon2id().hash(password);39 const userId = generateIdFromEntropySize(10);4041 await db.insert(users).values({42 id: userId,43 email,44 name,45 hashedPassword,46 });4748 const session = await lucia.createSession(userId, {});49 const sessionCookie = lucia.createSessionCookie(session.id);50 return sessionCookie; // Set as response cookie51}5253// Login54async function login(email: string, password: string) {55 const user = await db.query.users.findFirst({ where: eq(users.email, email) });56 if (!user) throw new Error("Invalid credentials");5758 const valid = await new Argon2id().verify(user.hashedPassword, password);59 if (!valid) throw new Error("Invalid credentials");6061 const session = await lucia.createSession(user.id, {});62 return lucia.createSessionCookie(session.id);63}6465// Validate session (middleware)66async function validateRequest(request: Request) {67 const cookieHeader = request.headers.get("Cookie");68 const sessionId = lucia.readSessionCookie(cookieHeader ?? "");69 if (!sessionId) return { user: null, session: null };7071 const result = await lucia.validateSession(sessionId);72 return result; // { user, session } or { user: null, session: null }73}7475// Logout76async function logout(sessionId: string) {77 await lucia.invalidateSession(sessionId);78 return lucia.createBlankSessionCookie();79}80```8182### OAuth (Google)8384```typescript85import { Google } from "arctic";8687const google = new Google(88 process.env.GOOGLE_CLIENT_ID!,89 process.env.GOOGLE_CLIENT_SECRET!,90 "https://myapp.com/auth/google/callback",91);9293// Redirect to Google94app.get("/auth/google", async (c) => {95 const [url, codeVerifier, state] = await google.createAuthorizationURL();96 // Store codeVerifier and state in cookie97 return c.redirect(url.toString());98});99100// Handle callback101app.get("/auth/google/callback", async (c) => {102 const { code, state } = c.req.query();103 const tokens = await google.validateAuthorizationCode(code, codeVerifier);104 const googleUser = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {105 headers: { Authorization: `Bearer ${tokens.accessToken()}` },106 }).then(r => r.json());107108 // Find or create user109 let user = await db.query.users.findFirst({ where: eq(users.email, googleUser.email) });110 if (!user) {111 const userId = generateIdFromEntropySize(10);112 [user] = await db.insert(users).values({113 id: userId, email: googleUser.email, name: googleUser.name, avatar_url: googleUser.picture,114 }).returning();115 }116117 const session = await lucia.createSession(user.id, {});118 const cookie = lucia.createSessionCookie(session.id);119 return c.redirect("/dashboard", { headers: { "Set-Cookie": cookie.serialize() } });120});121```122123## Installation124125```bash126npm install lucia arctic # Lucia + OAuth helpers127npm install @lucia-auth/adapter-drizzle # Or adapter-prisma, adapter-mongoose, etc.128npm install @node-rs/argon2 # Password hashing129```130131## Best Practices1321331. **Session-based** — Lucia uses server-side sessions + cookies; more secure than JWT for web apps1342. **Database-agnostic** — Adapters for Drizzle, Prisma, Mongoose, better-sqlite3, Turso, etc.1353. **Arctic for OAuth** — Use `arctic` library for OAuth providers; handles PKCE, state, tokens1364. **Argon2 for passwords** — Use `@node-rs/argon2` for hashing; industry standard, timing-safe1375. **Cookie security** — Set `secure: true` in production; `httpOnly` is automatic1386. **Session validation** — Call `validateSession()` on every request; auto-extends session expiry1397. **Invalidation** — `invalidateSession` for logout; `invalidateUserSessions` for security reset1408. **No magic** — Lucia is explicit; you write the signup/login/oauth flows; you understand every line