Full Stack SvelteKit, a full and comprehensive video course that will teach you how to build and deploy full stack web applications using SvelteKit. Full Stack SvelteKit

How to do email and password authentication with SvelteKit and Lucia

By Justin Ahinon.

Table of Contents

Authentication might be one of the most common features of modern web applications . It is a critical component of any website, as it allows users to log in and access their personalized content.

While many advocate for using a third-party authentication / user management platform, I believe that it is important to understand the underlying technology and how it can be implemented in SvelteKit .

In this article, we will explore how to implement email and password authentication in SvelteKit using the Lucia authentication library.

What is Lucia?

Lucia  is an open-source, lightweight and highly customizable session-based authentication library for TypeScript applications . It's built and maintained by  Pilcrow . The library provides a set of primitives and utilities for building secure and scalable authentication systems. It's framework and database agnostic, and provides providers and adapters for popular databases like PostgreSQL, MySQL, and SQLite.

For this tutorial, we'll use SQLite as a database alongside the BetterSqlite3 adapter.

To quickly spin up authentication UIs and forms, we'll use the  Shadcn Svelte  component library.

We'll handle interactions with the database using  Drizzle ORM .

Setting up the project

Let's start by creating a new SvelteKit project using the  create-svelte  command:

Loading code block

This will create a new project directory with the name  auth-email-password-lucia . Let's also use the  Node adapter  for our project.

Loading code block

We can now go ahead and replace the adapter in the  svelte.config.js  file with the Node adapter:

Loading code block

Let's also install and setup Tailwind CSS, Shadcn Svelte, as well as a few components for our authentication UI.

Loading code block

Setting up the database

Note : Checkout this article for a full guide on how to set up SQLite with Drizzle in a SvelteKit application .

Let's think a little bit about the database structure we'll need for our authentication system. Obviously, we'll need a table for users. Since Lucia uses sessions, we'll also need a table for storing sessions.

Let's now use Drizzle to create the database setup. You can read more about Drizzle and SQLite  here .

Loading code block

We can now create a  Drizzle config file .

Loading code block

We are using an environment variable,  DB_URL , to store the database URL. Let's not forget to add it to the  .env  file.

Loading code block

We also need to create a “client”, that allows use to interact with the database in a type safe way.

Loading code block

Now, let's create the database schema.

Loading code block

That's it for our schema. The users table has an email and a hashed password columns. The sessions table has an expiresAt column and a userId column which references the users table.

Using  Drizzle Kit generate command , we can generate the SQL migration file for this schema.

It'll look like this:

Loading code block

To apply the migrations (and create the database), we can use the  Drizzle push command .

Let's make sure to git ignore it the database file, to avoid committing our development database.

Loading code block

Finally, let's create a few functions for CRUD operations on the database.

Loading code block

Setup Lucia

We can now set up Lucia in our project. Let's start by installing the necessary dependencies.

Loading code block

The next step is to create a configuration file for Lucia. Let's do it in the  src/lib/server/auth/lucia.ts  file.

Loading code block

This is quite a lot, so let's go through it step by step.

We start by instantiating an adapter for Lucia. Lucia uses adapter to  interact with the database , mainly to create and read sessions . To this adapter, we pass our SQLite database instance, as well as the table names for users and sessions.

Next one, we create a new instance of Lucia. We pass the adapter we created earlier, as well as some configuration options.

By default, Lucia expects the  users  table to have a text  id  column, and the  sessions  table to have the following columns:

  • id : a text column with a primary key

  • expires_at : an integer column with a timestamp

  • user_id : a text column with a foreign key to the  users  table

When handling and checking sessions, we might want to return some additional attributes in the user sessions, or the user object that goes with it. For this, we can use the  getUserAttributes  and  getSessionAttributes  methods and return an object with the attributes we want to include in the session and user objects.

The next step is to set up how Lucia will check for user sessions, validating the sessions, etc…

This is done inside  the handle hook  in SvelteKit. The handle hook in SvelteKit is a function that runs before and after every request made by the application. It's used to handle things like authentication, authorization and more.

Let's go ahead and create a server hook for our application.

Loading code block

The part that interests us here is the  LuciaHandle  handle function.

We first check if there is a session cookie in the request. If there is, we validate the session and get the user object. If the session is valid, we set the session cookie and return the response. If the session is not valid, we create a blank session cookie and set it.

Finally, we set the user object and session object in the  event.locals  object, which will be available in the rest of the request lifecycle, and across all SvelteKit server files in the application.

Authentication UIs

Now, we can go ahead and create the routes for login and signup. As said earlier, we will use Shadcn Svelte for our UI components.

Loading code block

This is a basic form with an email and a password fields. We will use the  enhance  function from SvelteKit to handle form submissions.

Signup form for an email and password authentication system with SvelteKit using Lucia

The login form is mostly similar to the signup form, with a few differences for the labels and the submit button text.

When the form is submitted, SvelteKit will expect a corresponding  form action  to be defined in the associated  +page.server.ts  file to the route. Let's create this file and add the action to the form.

Loading code block

In the form action, we first get the email and the password from the form data. We then proceed with some basic validation to ensure that the email and password are present and not empty.

We also make sure that there is no existing user in the database with the same email address.

After those checks, we create a new user in the DB, and then create a new session for the user. This is where the Lucia adapter is helpful. Behind the scenes, Lucia uses the adapter to create a new session in the database. Once that's done, we set the session cookie and redirect the user to the protected route.

We can also add some logic to this route load function, to redirect the user to the profile page if they are already logged in.

The login form action is very similar to the signup form action.

Loading code block

Instead of creating a new user, we check that there is actually a user in the database with the email address from the form data. We only log in if that user exists and their password is valid.

Frontend feedback for errors

When an error occurs during the sign-up or the login process, we return an action failure with the fail function (see https://kit.svelte.dev/docs/modules#sveltejs-kit-fail ).

But we don't have (yet) on the frontend a way to let the user know that an error occurs.

Let's implement that feature by adding a toast when an error occurs.

We'll use the sonner component from Shadcn for that purpose.

Loading code block

Now, in our main layout file, we'll add the component and configure it:

Loading code block

Once that's done, we can call the toaster function wherever we want to display it:

Loading code block

We'll then get this beautiful error toast whenever there's an error:

We can also do the same thing for sign-up errors.

What's next?

This tutorial was a brief introduction to the basics of SvelteKit and Lucia. There are still a lot more areas that can be covered to improve the current state of the application. Here are some ideas for future work:

  • Implementing more advanced authentication features, such as password reset, email verification, etc…

  • Handling errors and exceptions in the CRUD operations

  • Adding more UI feedbacks to the user when logging in or signing up, for instance

Conclusion

In this tutorial, we dove deep into the world of SvelteKit and Lucia, showcasing how to set up a robust email and password authentication system from scratch. We covered everything from initializing your project and configuring your database to crafting secure login and signup forms with Shadcn Svelte components.

But this is just the beginning! Imagine enhancing your app with advanced features like password resets, email verification, and more user-friendly error handling. The possibilities are endless, and with the foundation you've built today, you're well on your way to creating a secure, scalable, and user-centric web application.

Keep experimenting, keep building, and watch your SvelteKit projects come to life with Lucia!

Stay Updated with the Full Stack SvelteKit course

Want to be the first to know when the Full Stack SvelteKit course launches? Sign up now to receive updates, exclusive content, and early access!