Migrate to v2

Breaking changes#

New features#

Installation#

Remove lucia-auth from your package.json. Install the new version of lucia:

npm i lucia@latest
pnpm add lucia@latest
yarn add lucia@latest

If you’re using the OAuth integration, install the new version of it as well:

npm i @lucia-auth/oauth@latest
pnpm add @lucia-auth/oauth@latest
yarn add @lucia-auth/oauth@latest

Database and adapters#

See each database adapter package’s migration guide:

Lucia namespace#

/// <reference types="lucia" />
declare namespace Lucia {
	type Auth = import("./lucia.js").Auth; // no change
	type DatabaseUserAttributes = {}; // formerly `UserAttributes`
	type DatabaseSessionAttributes = {}; // new
}

Imports#

Lucia core and adapters no longer use default exports.

// v1
import lucia from "lucia-auth";

// v2
import { lucia } from "lucia";

You should find and replace all instances of “lucia-auth” (or ‘lucia-auth’) with “lucia”.

Initialize Lucia#

The configuration for lucia() has been overhauled. See Configuration for details.

// v1
const auth = lucia({
	adapter: adapter(),
	env,
	middleware: framework(),

	transformDatabaseUser = (data) => {
		return {
			userId: data.id,
			username: data.username
		};
	},

	autoDatabaseCleanup: false,
	csrfProtection: true,
	generateCustomUserId: () => generateRandomString(16),
	hash,
	origin: ["https://foo.example.com"],
	sessionCookie: {
		sameSite: "strict"
	},
	sessionExpiresIn
});
// v2
const auth = lucia({
	adapter: adapter(), // no change
	env, // no change
	middleware: framework(), // no change

	// previously `transformDatabaseUser`
	getUserAttributes: (data) => {
		return {
			// IMPORTANT!!!!
			// `userId` included by default!!
			username: data.username
		};
	},

	// autoDatabaseCleanup: false, <= removed for now
	csrfProtection: {
		allowedSubdomains: ["foo"] // allow https://foo.example.com
	} // can be boolean
	// generateCustomUserId, <= removed, see `csrfProtection`
	passwordHash, // previously `hash`
	// origin, <= removed
	sessionCookie: {
		name: "user_session", // session cookie name
		attributes: {
			// moved previous `sessionCookie` value here
			sameSite: "strict"
		}
	},
	sessionExpiresIn // no change
});

Use custom user id#

While generateCustomUserId() configuration has been removed, you can now pass a custom user id to Auth.createUser().

await auth.createUser({
	userId: generateCustomUserId(),
	attributes: {}
});

generateRandomString()#

generateRandomString() only uses lowercase letters and numbers by default (no uppercase). This applies to user and session ids as well. To use the old id generation, pass a custom alphabet when using generateRandomString():

import { generateRandomString } from "lucia/utils";

const alphabet =
	"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

await auth.createUser({
	userId: generateRandomString(15, alphabet)
	// ...
});

Creating sessions and keys#

Auth.createSession() and Auth.createKey() now takes a single parameter.

// v1
await auth.createSession(userId);
await auth.createKey(userId, {
	// ...
});

// v2
await auth.createSession({
	userId,
	attributes: {} // must be defined!
});
await auth.createKey({
	userId
	// ...
});

Middleware#

With v2, Lucia no longer needs to set new session cookies when validating sessions if sessionCookie.expires configuration is set to false.

lucia({
	sessionCookie: {
		expires: false
	}
});

This should only be enabled when necessary:

nextjs()#

Auth.handleRequest() no longer accepts Response and Headers when using the Next.js middleware. Passing only IncomingMessage or Request will disable AuthRequest.setSession(). We recommend setting cookies manually when creating a new session.

// removed
auth.handleRequest({
	req: req as IncomingMessage,
	headers: headers as Headers
});
auth.handleRequest({
	req: req as IncomingMessage,
	response: response as Response
});
auth.handleRequest({
	request: request as Request
});

// new - `AuthRequest.setSession()` disabled
auth.handleRequest(req as IncomingMessage);
auth.handleRequest(request as Request);

request must be defined as well:

// v1
auth.handleRequest({
	cookies: cookies as Cookies,
	request: request as Request
});

// v2
auth.handleRequest({
	cookies: cookies as Cookies,
	request: request as Request | null
});

web()#

Auth.handleRequest() no longer accepts Response and Headers when using the web standard middleware. This means AuthRequest.setSession() is disabled, and we recommend setting cookies manually.

// v1
auth.handleRequest(request as Request, response as Response);
auth.handleRequest(request as Request, headers as Headers);

// v2
auth.handleRequest(request as Request);

Validating sessions#

Auth.validateSessionUser() and AuthRequest.validateUser() has been removed. The User object can now be accessed via Session.user.

const authRequest = auth.handleRequest();
const session = await auth.validateSession();
const session = await authRequest.validate();

const user = session.user;

Session renewal#

Auth.renewSession() has been removed.

Reading cookies manually#

Auth.parseRequestHeaders() has been removed and replaced with Auth.validateRequestOrigin() and Auth.readSessionCookie().

auth.validateRequestOrigin(request as LuciaRequest); // csrf check
const sessionCookie = auth.readSessionCookie(request.headers.cookie); // does NOT handle csrf check

type LuciaRequest = {
	method: string;
	url: string;
	headers: {
		origin: string | null;
		cookie: string | null;
		authorization: string | null;
	};
	storedSessionCookie?: string | null;
};

Default database values#

Lucia no longer supports database default values for database tables.

// v1
await auth.createUser({
	attributes: {
		// (admin = false) set by database
	}
});

// v2
await auth.createUser({
	attributes: {
		admin: false // must manually pass value
	}
});

This means Lucia.DatabaseUserAttributes (formerly UserAttributes) cannot have optional properties.

Primary keys#

Primary keys have been removed. We recommend storing the provider id of the primary key as a user attributes if you rely on it.

// v1
await auth.createUser({
	primaryKey: {
		// ...
	}
});

// v2
await auth.createUser({
	key: {
		// ...
	}
});

Single use keys#

Single use keys have been removed. We recommend implementing your tokens as they’re more secure. Make sure to update Auth.createKey() even if you weren’t using single use keys.

// v1
await auth.createKey(userId, {
	type: "persistent",
	providerId,
	providerUserId,
	password
});

// v2
await auth.createKey({
	userId,
	providerId,
	providerUserId,
	password
});

lucia/utils#

Added new /utils export, which exports generateRandomString() among other utilities.

import {
	generateRandomString,
	serializeCookie,
	isWithinExpiration
} from "lucia/utils";

OAuth#

The OAuth package also had some changes as well.

Removed provider()#

We now provide providerUserAuth() which is a lower level API for implementing your own provider.

Renamed providerUser and tokens#

providerUser and tokens of the validateCallback() return value is now renamed to githubUser and githubTokens, etc.

const { githubUser, githubTokens } = await githubAuth.validateCallback(code);

Removed LuciaOAuthRequestError#

LuciaOAuthRequestError is replaced with OAuthRequestError.

Update ProviderUserAuth.validateCallback()#

User attributes should be provided as its own property.

const { createUser } = await githubAuth.validateCallback(code);

// v1
await createUser(attributes);

// v2
await createUser({
	attributes
});