Qwik Lucia is a library that helps you to integrate Lucia with your Qwik project.
- No more configuration
npm i qwik-lucia
pnpm add qwik-lucia
bun add qwik-lucia
yarn add qwik-lucia
In this example, we will use the @lucia-auth/adapter-drizzle
adapter to connect to a PostgreSQL database.
For simplicity, we won't show the imports that are not related to the
qwik-lucia
orlucia
package.
// src/server/lucia.ts
import { Lucia } from 'lucia';
import { DrizzlePostgreSQLAdapter } from '@lucia-auth/adapter-drizzle';
import { qwikLuciaConfig } from 'qwik-lucia';
const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, userTable);
export const lucia = new Lucia(adapter, {
sessionCookie: {
attributes: {
// set to `true` when using HTTPS
secure: process.env.NODE_ENV === 'production',
},
},
getUserAttributes: (attributes) => {
return {
username: attributes.username,
};
},
});
/*
IMPORTANT!
Here we need to use `qwikLuciaConfig` to correctly configure the `handleRequest` function
*/
export const { handleRequest } = qwikLuciaConfig(lucia);
declare module 'lucia' {
interface Register {
Lucia: typeof lucia;
DatabaseUserAttributes: Omit<SelectUser, 'id'>;
}
}
// src/routes/layout.tsx
import type { RequestHandler } from '@builder.io/qwik-city';
import { handleRequest } from '~/server/lucia';
// Run on every request
export const onRequest: RequestHandler = async ({ cookie, sharedMap }) => {
const authRequest = handleRequest({ cookie });
const { user, session } = await authRequest.validateUser();
// share the user and session with the rest of the app
sharedMap.set('user', user);
sharedMap.set('session', session);
};
// src/routes/signup/index.tsx
import { handleRequest, lucia } from "~/lib/lucia";
// This is just a wrapper around the oslo/password library
import { hashPassword } from 'qwik-lucia';
export const useSignupUser = routeAction$(
async (values, { redirect }) => {
try {
const passwordHash = await hashPassword(values.password);
await db.insert(userTable).values({
username: values.username,
password: passwordHash,
});
} catch (e) {
... // handle error
}
// make sure you don't throw inside a try/catch block!
throw redirect(303, '/');
},
// validate the input
zod$({
username: z.string().min(2),
password: z.string().min(5),
})
);
// src/routes/login/index.tsx
import { handleRequest, lucia } from "~/server/lucia";
// This is just a wrapper around the oslo/password library
import { verifyPassword } from 'qwik-lucia';
export const useLoginAction = routeAction$(
async (values, { cookie, fail }) => {
// Important! Use `handleRequest` to handle the authentication request
const authRequest = handleRequest({ cookie });
try {
//1. search for user
const [user] = await db
.select({
id: userTable.id,
passwordHash: userTable.password,
username: userTable.username,
})
.from(userTable)
.where(eq(userTable.username, values.username));
//2. if user is not found, throw error
if (!user) {
return fail(400, {
message: 'Incorrect username or password',
});
}
// 3. validate password
const isValidPassword = await verifyPassword(
user.passwordHash,
values.password
);
if (!isValidPassword) {
return fail(400, {
message: 'Incorrect username or password',
});
}
// 4. create session
const session = await lucia.createSession(user.id, {});
authRequest.setSession(session); // set session cookie
} catch (e) {
... // handle error
}
// make sure you don't throw inside a try/catch block!
throw event.redirect(303, '/');
},
// validate the input
zod$({
username: z.string(),
password: z.string(),
})
);
// src/routes/index.tsx
import { handleRequest } from '~/server/lucia';
// Create a user logout action
export const useLogoutUserAction = routeAction$(
async (values, { cookie, sharedMap, redirect }) => {
const authRequest = handleRequest({ cookie });
const session = sharedMap.get('session');
if (!session) throw redirect(302, '/login');
// Remove the session from the database and from the cookie - Logout
await authRequest.invalidateSessionCookie(session);
throw redirect(302, '/login');
}
);
const user = sharedMap.get('user');
const session = sharedMap.get('session');
Example
export const isUserLoggedIn = routeLoader$(async ({ sharedMap, redirect }) => {
const user = sharedMap.get('user');
const session = sharedMap.get('session');
if (!user || !session) {
throw redirect(302, '/login');
}
return {};
});