Secure Authentication: Integrating Lucia with Astro for Robust User Management
- Ctrl Man
- Web Development , Security
- 24 Sep, 2024
Integrating Lucia Authentication with Astro
To integrate the Lucia authentication system for login functionality in your Astro project, follow these steps. This guide will help you structure your components and implement authentication effectively.
- Install Required Packages
First, install Lucia and the necessary adapter for your chosen database. Follow the instructions for either SQLite or MongoDB, depending on your preference.
Option A: SQLite
If you’re using SQLite, install the following packages:
npm install lucia @lucia-auth/adapter-sqlite better-sqlite3
Option B: MongoDB
If you’re using MongoDB, install the following packages:
npm install lucia @lucia-auth/adapter-mongodb mongodb
Troubleshooting MongoDB Configuration
If you’re facing an issue where MongoDB fails to start with exit code 100 after updating the MongoDB Compass (eg. to version 1.44.4) application on Windows, it is likely because the data directory (C:\data\db\
) does not exist. The error log may look like this:
"DBException in initAndListen, terminating","attr":{"error":"NonExistentPath: Data directory C:\\data\\db\\ not found. Create the missing directory or specify another path using (1) the --dbpath command line option, or (2) by adding the 'storage.dbPath' option in the configuration file."
Solution
To resolve this, you need to create the missing directory. Follow these steps:
- Open a command prompt with administrative privileges.
- Run the following command:
mkdir C:\data\db
After creating the directory, try starting MongoDB again. This should allow the database to initialize correctly.
- Set Up Lucia
After installing the required packages, you need to set up Lucia with your chosen database adapter. Create a file (e.g., auth.ts
) to configure Lucia:
Option A: SQLite Configuration
import { lucia } from "lucia";
import { astro } from "lucia/middleware";
import { betterSqlite3 } from "@lucia-auth/adapter-sqlite";
import sqlite from "better-sqlite3";
const db = sqlite("auth.db");
export const auth = lucia({
adapter: betterSqlite3(db),
env: import.meta.env.DEV ? "DEV" : "PROD",
middleware: astro(),
// ... other options
});
export type Auth = typeof auth;
Option B: MongoDB Configuration
import { lucia } from "lucia";
import { astro } from "lucia/middleware";
import { MongodbAdapter } from "@lucia-auth/adapter-mongodb";
import { Collection, MongoClient } from "mongodb";
const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();
const db = client.db("your_database_name");
const User = db.collection("users") as Collection<UserDocument>;
const Session = db.collection("sessions") as Collection<SessionDocument>;
const adapter = new MongodbAdapter(
Session as Collection<SessionDocument>,
User as Collection<UserDocument>
);
export const auth = lucia({
adapter: adapter,
env: import.meta.env.DEV ? "DEV" : "PROD",
middleware: astro(),
// ... other options
});
interface UserDocument {
_id: string;
// ... other user fields
}
interface SessionDocument {
_id: string;
user_id: string;
expires_at: Date;
}
export type Auth = typeof auth;
Choose the configuration that matches your selected database. This setup will allow you to use Lucia for authentication in your Astro project with either SQLite or MongoDB as the backend.
3. Create Authentication Components
Login Form Component
Create a LoginForm.astro
component:
---
import { auth } from "../auth";
let errorMessage: string | null = null;
if (Astro.request.method === "POST") {
const formData = await Astro.request.formData();
const username = formData.get("username");
const password = formData.get("password");
try {
const key = await auth.useKey("username", username, password);
const session = await auth.createSession(key.userId);
Astro.locals.auth.setSession(session);
return Astro.redirect("/dashboard");
} catch {
errorMessage = "Invalid username or password";
}
}
---
<form method="post">
{errorMessage && <p>{errorMessage}</p>}
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<button type="submit">Log in</button>
</form>
Protected Layout
Create a ProtectedLayout.astro
component:
---
import { auth } from "../auth";
const session = await Astro.locals.auth.validate();
if (!session) {
return Astro.redirect("/login");
}
---
<slot />
4. Implement Login and Protected Pages
Login Page
Create a login.astro
page:
---
import Layout from "../layouts/Layout.astro";
import LoginForm from "../components/LoginForm.astro";
---
<Layout title="Login">
<h1>Login</h1>
<LoginForm />
</Layout>
Protected Page
Create a protected page (e.g., dashboard.astro
):
---
import ProtectedLayout from "../layouts/ProtectedLayout.astro";
import Layout from "../layouts/Layout.astro";
---
<Layout title="Dashboard">
<ProtectedLayout>
<h1>Dashboard</h1>
<p>Welcome to your dashboard!</p>
</ProtectedLayout>
</Layout>
5. Handle Logout
Create a logout API route (e.g., logout.ts
in the pages/api
directory):
import type { APIRoute } from "astro";
import { auth } from "../../auth";
export const post: APIRoute = async ({ locals }) => {
const session = await locals.auth.validate();
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
await auth.invalidateSession(session.sessionId);
locals.auth.setSession(null);
return new Response(null, {
status: 302,
headers: {
Location: "/login"
}
});
};
Then, you can add a logout button to your protected pages:
<form action="/api/logout" method="post">
<button type="submit">Logout</button>
</form>
6. Protect API Routes
For API routes that need authentication, you can use a similar pattern:
import type { APIRoute } from "astro";
export const get: APIRoute = async ({ locals }) => {
const session = await locals.auth.validate();
if (!session) {
return new Response("Unauthorized", { status: 401 });
}
// Your protected API logic here
};
Conclusion
This setup provides a solid foundation for implementing authentication in your Astro project using Lucia. Remember to handle error cases, implement proper password hashing, and follow security best practices. Always test your authentication flow thoroughly to ensure a smooth and secure user experience.