Claude-skill-registry-data lucia
Implements session-based authentication with Lucia Auth library for server-side session management and cookie handling. Use when building custom authentication, session management, or when user mentions Lucia, server-side auth, or session cookies.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry-data
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry-data "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/lucia" ~/.claude/skills/majiayu000-claude-skill-registry-data-lucia && rm -rf "$T"
manifest:
data/lucia/SKILL.mdsource content
Lucia Auth
Server-side session-based authentication library that abstracts session management complexity.
Note: Lucia v3 will be deprecated by March 2025. The patterns here remain valid for session-based auth.
Quick Start
npm install lucia npm install @lucia-auth/adapter-prisma # Or your database adapter
Setup
Basic Configuration
// lib/auth.ts import { Lucia, TimeSpan } from 'lucia'; import { PrismaAdapter } from '@lucia-auth/adapter-prisma'; import { prisma } from './prisma'; const adapter = new PrismaAdapter(prisma.session, prisma.user); export const lucia = new Lucia(adapter, { sessionExpiresIn: new TimeSpan(30, 'd'), // 30 days sessionCookie: { name: 'session', expires: false, // Session cookie attributes: { secure: process.env.NODE_ENV === 'production', sameSite: 'lax', }, }, getUserAttributes: (attributes) => ({ email: attributes.email, name: attributes.name, role: attributes.role, }), }); // Type declarations declare module 'lucia' { interface Register { Lucia: typeof lucia; DatabaseUserAttributes: { email: string; name: string; role: 'user' | 'admin'; }; } }
Database Schema (Prisma)
// prisma/schema.prisma model User { id String @id @default(cuid()) email String @unique name String role String @default("user") passwordHash String sessions Session[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Session { id String @id userId String expiresAt DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) }
Other Adapters
// Drizzle import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle'; const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, userTable); // SQLite with better-sqlite3 import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite'; const adapter = new BetterSqlite3Adapter(db, { user: 'users', session: 'sessions', }); // MongoDB import { MongodbAdapter } from '@lucia-auth/adapter-mongodb'; const adapter = new MongodbAdapter( db.collection('sessions'), db.collection('users') );
Authentication Flow
Registration
import { generateId } from 'lucia'; import { hash } from '@node-rs/argon2'; async function register(email: string, password: string, name: string) { // Validate input if (!email || !password || password.length < 8) { throw new Error('Invalid input'); } // Check if user exists const existingUser = await prisma.user.findUnique({ where: { email }, }); if (existingUser) { throw new Error('Email already registered'); } // Hash password const passwordHash = await hash(password, { memoryCost: 19456, timeCost: 2, outputLen: 32, parallelism: 1, }); // Create user const userId = generateId(15); const user = await prisma.user.create({ data: { id: userId, email, name, passwordHash, }, }); // Create session const session = await lucia.createSession(userId, {}); const sessionCookie = lucia.createSessionCookie(session.id); return { user, sessionCookie }; }
Login
import { verify } from '@node-rs/argon2'; async function login(email: string, password: string) { // Find user const user = await prisma.user.findUnique({ where: { email }, }); if (!user) { throw new Error('Invalid email or password'); } // Verify password const validPassword = await verify(user.passwordHash, password); if (!validPassword) { throw new Error('Invalid email or password'); } // Create session const session = await lucia.createSession(user.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); return { user, sessionCookie }; }
Logout
async function logout(sessionId: string) { await lucia.invalidateSession(sessionId); const blankCookie = lucia.createBlankSessionCookie(); return blankCookie; } // Logout from all devices async function logoutAll(userId: string) { await lucia.invalidateUserSessions(userId); const blankCookie = lucia.createBlankSessionCookie(); return blankCookie; }
Session Validation
Reading Session Cookie
import { cookies } from 'next/headers'; // Next.js example function getSessionCookie(): string | null { return cookies().get(lucia.sessionCookieName)?.value ?? null; } // Or from raw header function getSessionFromHeader(cookieHeader: string): string | null { return lucia.readSessionCookie(cookieHeader); }
Validating Sessions
async function validateRequest() { const sessionId = getSessionCookie(); if (!sessionId) { return { user: null, session: null }; } const { user, session } = await lucia.validateSession(sessionId); // Session is fresh - set new cookie if (session?.fresh) { const sessionCookie = lucia.createSessionCookie(session.id); cookies().set( sessionCookie.name, sessionCookie.value, sessionCookie.attributes ); } // Session is invalid - clear cookie if (!session) { const blankCookie = lucia.createBlankSessionCookie(); cookies().set( blankCookie.name, blankCookie.value, blankCookie.attributes ); } return { user, session }; }
Framework Integration
Next.js (App Router)
// lib/auth.ts import { cookies } from 'next/headers'; import { cache } from 'react'; export const validateRequest = cache(async () => { const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null; if (!sessionId) { return { user: null, session: null }; } const result = await lucia.validateSession(sessionId); try { if (result.session?.fresh) { const sessionCookie = lucia.createSessionCookie(result.session.id); cookies().set( sessionCookie.name, sessionCookie.value, sessionCookie.attributes ); } if (!result.session) { const blankCookie = lucia.createBlankSessionCookie(); cookies().set( blankCookie.name, blankCookie.value, blankCookie.attributes ); } } catch { // Next.js throws when setting cookies in Server Components } return result; });
// app/api/auth/login/route.ts import { NextResponse } from 'next/server'; export async function POST(request: Request) { const { email, password } = await request.json(); try { const { sessionCookie } = await login(email, password); return new NextResponse(JSON.stringify({ success: true }), { status: 200, headers: { 'Set-Cookie': sessionCookie.serialize(), }, }); } catch (error) { return NextResponse.json( { error: error.message }, { status: 401 } ); } }
// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export async function middleware(request: NextRequest) { // CSRF protection if (request.method !== 'GET') { const origin = request.headers.get('origin'); const host = request.headers.get('host'); if (!origin || !host || new URL(origin).host !== host) { return new NextResponse(null, { status: 403 }); } } return NextResponse.next(); } export const config = { matcher: ['/api/:path*'], };
Express
import express from 'express'; import { lucia } from './lib/auth'; const app = express(); // Session middleware app.use(async (req, res, next) => { const sessionId = lucia.readSessionCookie(req.headers.cookie ?? ''); if (!sessionId) { res.locals.user = null; res.locals.session = null; return next(); } const { session, user } = await lucia.validateSession(sessionId); if (session?.fresh) { res.appendHeader( 'Set-Cookie', lucia.createSessionCookie(session.id).serialize() ); } if (!session) { res.appendHeader( 'Set-Cookie', lucia.createBlankSessionCookie().serialize() ); } res.locals.user = user; res.locals.session = session; next(); }); // CSRF protection app.use((req, res, next) => { if (req.method === 'GET') return next(); const origin = req.headers.origin; const host = req.headers.host; if (!origin || !host || new URL(origin).host !== host) { return res.status(403).send('Forbidden'); } next(); }); // Protected route app.get('/api/me', (req, res) => { if (!res.locals.user) { return res.status(401).json({ error: 'Unauthorized' }); } res.json({ user: res.locals.user }); }); // Login route app.post('/api/login', async (req, res) => { const { email, password } = req.body; try { const { sessionCookie } = await login(email, password); res.appendHeader('Set-Cookie', sessionCookie.serialize()); res.json({ success: true }); } catch (error) { res.status(401).json({ error: error.message }); } }); // Logout route app.post('/api/logout', async (req, res) => { if (!res.locals.session) { return res.status(401).json({ error: 'Not logged in' }); } await lucia.invalidateSession(res.locals.session.id); res.appendHeader( 'Set-Cookie', lucia.createBlankSessionCookie().serialize() ); res.json({ success: true }); });
Hono
import { Hono } from 'hono'; import { getCookie, setCookie } from 'hono/cookie'; const app = new Hono(); // Session middleware app.use('*', async (c, next) => { const sessionId = getCookie(c, lucia.sessionCookieName) ?? null; if (!sessionId) { c.set('user', null); c.set('session', null); return next(); } const { session, user } = await lucia.validateSession(sessionId); if (session?.fresh) { const cookie = lucia.createSessionCookie(session.id); setCookie(c, cookie.name, cookie.value, cookie.attributes); } if (!session) { const cookie = lucia.createBlankSessionCookie(); setCookie(c, cookie.name, cookie.value, cookie.attributes); } c.set('user', user); c.set('session', session); await next(); }); // Protected route helper const authRequired = async (c, next) => { if (!c.get('user')) { return c.json({ error: 'Unauthorized' }, 401); } await next(); }; app.get('/api/me', authRequired, (c) => { return c.json({ user: c.get('user') }); });
OAuth Integration
import { generateState, generateCodeVerifier } from 'arctic'; import { GitHub } from 'arctic'; const github = new GitHub( process.env.GITHUB_CLIENT_ID!, process.env.GITHUB_CLIENT_SECRET! ); // Start OAuth flow async function startGitHubAuth() { const state = generateState(); const codeVerifier = generateCodeVerifier(); const url = await github.createAuthorizationURL(state, { scopes: ['user:email'], }); return { url, state, codeVerifier }; } // Handle callback async function handleGitHubCallback(code: string, state: string) { const tokens = await github.validateAuthorizationCode(code); // Get user info const response = await fetch('https://api.github.com/user', { headers: { Authorization: `Bearer ${tokens.accessToken}`, }, }); const githubUser = await response.json(); // Find or create user let user = await prisma.user.findUnique({ where: { githubId: githubUser.id }, }); if (!user) { user = await prisma.user.create({ data: { id: generateId(15), githubId: githubUser.id, email: githubUser.email, name: githubUser.name || githubUser.login, }, }); } // Create session const session = await lucia.createSession(user.id, {}); const sessionCookie = lucia.createSessionCookie(session.id); return { user, sessionCookie }; }
Password Reset
import { generateId } from 'lucia'; // Request reset async function requestPasswordReset(email: string) { const user = await prisma.user.findUnique({ where: { email } }); if (!user) return; // Don't reveal if user exists // Generate token const token = generateId(40); const expiresAt = new Date(Date.now() + 1000 * 60 * 60); // 1 hour await prisma.passwordReset.create({ data: { token, userId: user.id, expiresAt, }, }); // Send email with reset link await sendEmail({ to: email, subject: 'Password Reset', html: `<a href="${process.env.URL}/reset-password?token=${token}">Reset Password</a>`, }); } // Complete reset async function resetPassword(token: string, newPassword: string) { const reset = await prisma.passwordReset.findUnique({ where: { token }, include: { user: true }, }); if (!reset || reset.expiresAt < new Date()) { throw new Error('Invalid or expired token'); } const passwordHash = await hash(newPassword); await prisma.$transaction([ prisma.user.update({ where: { id: reset.userId }, data: { passwordHash }, }), prisma.passwordReset.delete({ where: { token } }), ]); // Invalidate all sessions await lucia.invalidateUserSessions(reset.userId); }
Reference Files
- oauth-providers.md - OAuth provider setup guides
- security.md - Security best practices