DEV Community

Alex Spinov
Alex Spinov

Posted on

Lucia Has a Free Auth Library That You Actually Understand — No Magic, No Lock-In, Just Sessions

The Auth Problem

Auth0: $23/month for 1,000 users. Clerk: $25/month. NextAuth: works until you need custom logic, then you fight the abstraction.

Lucia is a session management library. Not a framework. Not a service. You control every part of the auth flow.

What Lucia Gives You

Session Management

import { Lucia } from 'lucia';
import { DrizzleSQLiteAdapter } from '@lucia-auth/adapter-drizzle';

const adapter = new DrizzleSQLiteAdapter(db, sessionTable, userTable);

export const lucia = new Lucia(adapter, {
  sessionCookie: {
    attributes: {
      secure: process.env.NODE_ENV === 'production',
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Sign Up

import { generateId } from 'lucia';
import { Argon2id } from 'oslo/password';

const hashedPassword = await new Argon2id().hash(password);
const userId = generateId(15);

await db.insert(userTable).values({
  id: userId,
  email: email,
  hashedPassword: hashedPassword,
});

const session = await lucia.createSession(userId, {});
const sessionCookie = lucia.createSessionCookie(session.id);
// Set cookie in response
Enter fullscreen mode Exit fullscreen mode

Sign In

const user = await db.query.userTable.findFirst({
  where: eq(userTable.email, email),
});

const validPassword = await new Argon2id().verify(
  user.hashedPassword,
  password
);

if (validPassword) {
  const session = await lucia.createSession(user.id, {});
  const cookie = lucia.createSessionCookie(session.id);
  // Set cookie
}
Enter fullscreen mode Exit fullscreen mode

Validate Sessions

const sessionId = cookies.get(lucia.sessionCookieName);
const { session, user } = await lucia.validateSession(sessionId);

if (!session) {
  // Not authenticated
  return redirect('/login');
}
Enter fullscreen mode Exit fullscreen mode

OAuth

import { GitHub } from 'arctic';

const github = new GitHub(clientId, clientSecret);
const url = await github.createAuthorizationURL(state, { scopes: ['user:email'] });

// After callback:
const tokens = await github.validateAuthorizationCode(code);
const githubUser = await fetch('https://api.github.com/user', {
  headers: { Authorization: `Bearer ${tokens.accessToken}` },
}).then(r => r.json());
Enter fullscreen mode Exit fullscreen mode

Works With Any Framework

Next.js, SvelteKit, Astro, Express, Hono, Nuxt, Remix — Lucia is framework-agnostic.

Works With Any Database

Drizzle, Prisma, Mongoose, raw SQL, Turso, PlanetScale, Supabase — via adapters.

Why This Matters

Auth services charge per user. Auth frameworks hide the logic. Lucia gives you the tools to build auth exactly how you want it — and you understand every line.


Building authenticated apps? Check out my web scraping actors on Apify Store — data APIs for your user-facing features. For custom solutions, email spinov001@gmail.com.

Top comments (0)