Lucia

Upgrade to Lucia v3

Version 3.0 rethinks Lucia and the role it should play in your application. We have stripped out all the annoying bits, and everything else we kept has been refined even more. Everything is more flexible, and just all around easier to understand and work with.

We estimate it will take about an hour or two to upgrade your project, though it depends on how big your application is. If you're having issues with the migration or have any questions, feel free to ask on our Discord server.

Major changes

The biggest change to Lucia is that keys have been removed entirely. We believe it was too limiting and ultimately an unnecessary concept that made many projects more complex than they needed to be. Another big change is that Lucia no longer handles user creation, so createUser() among other APIs has been removed.

For a simple password-based auth, the password can just be stored in the user table.

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

await db.table("user").insert({
	id: userId,
	email,
	hashed_password: hashedPassword
});

Another change is that APIs for request handling have been removed. We now just provide code snippets in the docs that you can copy-paste.

Lucia is now built with Oslo, a library that provides useful auth-related utilities. While not required, we recommend installing it alongside Lucia as all guides in the documentation use it some way or another.

npm install lucia oslo

Initialize Lucia

Here's the base config. Lucia is now initialized using the Lucia class, which takes an adapter and an options object. Make sure to configure the sessionCookie option.

import { Lucia, TimeSpan } from "lucia";
import { astro } from "lucia/middleware";

export const lucia = new Lucia(adapter, {
	sessionCookie: {
		attributes: {
			secure: env === "PRODUCTION" // replaces `env` config
		}
	}
});

Here's the fully updated configuration for reference. middleware and csrfProtection have been removed.

import { Lucia, TimeSpan } from "lucia";
import { astro } from "lucia/middleware";

export const lucia = new Lucia(adapter, {
	getSessionAttributes: (attributes) => {
		return {
			ipCountry: attributes.ip_country
		};
	},
	getUserAttributes: (attributes) => {
		return {
			username: attributes.username
		};
	},
	sessionExpiresIn: new TimeSpan(30, "d"), // no more active/idle
	sessionCookie: {
		name: "session",
		expires: false, // session cookies have very long lifespan (2 years)
		attributes: {
			secure: true,
			sameSite: "strict",
			domain: "example.com"
		}
	}
});

Type declaration

Lucia v3 uses the newer module syntax instead of .d.ts files for declaring types for improved ergonomics and monorepo support. The Lucia type declaration is required.

export const lucia = new Lucia();

declare module "lucia" {
	interface Register {
		Lucia: typeof lucia;
		DatabaseSessionAttributes: DatabaseSessionAttributes;
		DatabaseUserAttributes: DatabaseUserAttributes;
	}
}

interface DatabaseSessionAttributes {
	country: string;
}
interface DatabaseUserAttributes {
	username: string;
}

Polyfill

lucia/polyfill/node has been removed. Manually polyfill the Web Crypto API by importing the crypto module.

import { webcrypto } from "node:crypto";

globalThis.crypto = webcrypto as Crypto;

Update your database

Refer to each database migration guide:

The following packages are deprecated:

  • @lucia-auth/adapter-mongoose (see Mongoose migration guide)
  • @lucia-auth/adapter-session-redis
  • @lucia-auth/adapter-session-unstorage

If you're using a session adapter, we recommend building a custom adapter as the API has been greatly simplified.

Sessions

Session validation

Middleware, Auth.handleRequest(), and AuthRequest have been removed. This means Lucia no longer provides strict CSRF protection. For replacing AuthRequest.validate(), see the Validating session cookies guide or a framework-specific version of it as these need to be re-implemented from scratch (though it's just copy-pasting code from the guides):

Session.sessionId has been renamed to Session.id

const sessionId = session.id;

validateSession() no longer throws an error when the session is invalid, and returns an object of User and Session instead.

// v3
const { session, user } = await auth.validateSession(sessionId);
if (!session) {
	// invalid session
}

Session cookies

createSessionCookie() now takes a session ID instead of a session object, and createBlankSessionCookie() should be used for creating blank session cookies.

const sessionCookie = auth.createSessionCookie(session.id);
const blankSessionCookie = auth.createBlankSessionCookie();

Update authentication

Refer to these guides:

Framework specific configuration

If you installed Oslo, you must prevent oslo from getting bundled. This is only required when using the oslo/password module.

Astro

// astro.config.mjs
export default defineConfig({
	// ...
	vite: {
		optimizeDeps: {
			exclude: ["oslo"]
		}
	}
});

Next.js

oslo/password does NOT work with Turbopack.

// next.config.ts
const nextConfig = {
	webpack: (config) => {
		config.externals.push("@node-rs/argon2", "@node-rs/bcrypt");
		return config;
	}
};

SvelteKit

@sveltejs/adapter-node

Make sure to install oslo as a dependency, not as devDependency to prevent @sveltejs/adapter-node from bundling oslo.

As per SvelteKit documentation :

Development dependencies will be bundled into your app using Rollup. To control whether a given package is bundled or externalised, place it in devDependencies or dependencies respectively in your package.json.

See SvelteKit documentation for details.