Table of Contents
Get this article emailed to you .
Svelte version: 3.58.0
SvelteKit version: 1.15.5
GitHub repository: https://github.com/JustinyAhin/sveltekit-auth-jwt
SvelteKit is a meta-framework on top of Svelte for building web applications. It comes with a handful of features that make it delightful to work with. Things like
functions, API routes, server functions, form actions, and progressive enhancement provide a full-fledged experience for end-to-end web application development.
After a handful of trials and reading various repositories codes and tutorials, I’ve come up with an implementation that seems satisfying for me and that I’m going to share in this article.
How does SvelteKit handle server-side code?
In SvelteKit, you have two main ways to run server-side code: API routes (or endpoints, as they are officially called in the documentation) and server functions. Server functions are created in
files or their JS equivalents.
Server functions can either be
functions or form actions.
functions run both during server-side and client-side rendering, while form actions are triggered as a result of form submission.
This tutorial’ll create a small project with a sign-up, login, and guarded pages. Our authentication and authorization login is going to be implemented in our routes' server-side code, and we will use SvelteKit hooks to authorize users in the guarded routes.
We will use JSON Web Tokens (JWT) to generate and verify encrypted tokens that will be sent along with all authenticated requests. We will also use an SQLite database as our store and Prisma to interact with it.
Creating the project
The final code for this tutorial is available on my GitHub here.
It’s pretty straightforward to create a SvelteKit project by running the following command:
I’m using the skeleton template with TypeScript, Prettier, and ESLint enabled.
Installing necessary dependencies
We will need a few dependencies that will be used along the way. First, we install
as a development dependency:
Now we will install
to hash and compare passwords,
to generate and verify tokens, and @prisma/client to interact with our database:
When using TypeScript, some dependencies require type definitions. We install them with the following command:
Setting up the database
Let’s now use Prisma to initialize an SQLite database and create a User model.
file, we will create a simple User model with email and password fields.
We can generate the Prisma client and apply our schema changes to the database by running
How does JWT work?
JWT is basically a standard to securely transmit information between parties (in our case, a client and a server) as a JSON object . This information can be verified and trusted because it is digitally signed using a secret or a public/private key pair.
In an authentication-authorization flow, after a user successfully logs in, the server generates a JWT that contains some user information and sends it back to the client. The client then stores this token in a cookie or local storage and sends it along with every request to the server, which can then verify the token and grant access to protected resources/information.
Here is a simple diagram of how JWT works:
Authentication pages for our app
Now that we understand how JWT works let’s create our authentication pages.
The signup page HTML markup is pretty straightforward. It’s simply a form with an email, password input, and a submit button.
Here, we add
to our form for posting signup data. But how are we going to handle that?
This is where SvelteKit form actions come in handy. In a
file, we can export some actions, allowing us to post data through a form. Here is what it looks like:
The default action for a form is called
. We could also use named actions in case we have many actions on a page.
What we are doing here is validating the data we get from the form, creating a new user, returning a validation error in case there's an error, and finally, redirecting to the login page if everything is successful.
The code for creating a user can be found here.
action to our form element.
Let's also add a few things to make the experience even better. We can display the validation error we returned in our server code on the frontend.
When we return something from a form action, it comes to the frontend as
. We will use that to conditionally errors messages like this:
Note that we are using
export let form: ActionData
contains the data. For this to work, it needs to be named
Now, we can have this alert when there is an error:
The process for logging in is a bit similar to signing in. We use a form action to authenticate our user and redirect to the guarded page. The main difference resides in setting up the JWT token as a cookie.
Let's see how it goes:
Now on the server side:
We set the
cookie with the JWT token and return a successful response.
Note here a few things:
httpOnlyoption is set to
We set the
trueto ensure that the cookie is only sent over HTTPS.
sameSiteoption is set to
strictto prevent the cookie from being sent in cross-site requests.
You can read the MDN documentation for more information about the
With SvelteKit, there is no need to use third-party cookies packages because the framework handles this natively.
We are also exporting a
function in this file. We're basically redirecting to the guarded page if the user is already logged in. You'll understand more about how this part is handled in the hook section.
You can give a look at the
Implementing authorization hook
If you come from a framework like Express, you might be familiar with the concept of middleware. In SvelteKit, we have something called hooks. These are functions that are called before a request is handled by a route.
Hooks can be defined on the server side or on the client side, or both if necessary. We will define our hooks on the server side for this tutorial, since we are doing authentication logic.
The most used hook is the
hook, which simply takes a request and returns a response.
Per convention, server hooks are defined in a
Here are the things that are happening in the
We check if there is an
In case there is one, we extract the JWT token from it.
We verify the token using the
If there is any error, we simply throw it and return the response from the route.
If the token is valid, we get the user from the database and save it in the
With that in place, for every request that comes to our application, we either save or not the user ID in the
. This gives us enough flexibility to handle authorization and necessary redirects per route.
Protecting guarded routes
For our protected routes, we will make use of SvelteKit
function. Since it runs before the component is rendered, we can use it to get the logged-in user from the
, and either display the component or redirect the user to the login page.
function will be implemented in our guarded route's
When there is no user in the
, we throw a 401 error; else we return the user to be used on the frontend.
In case there is an error, we will then have this when trying to access the guarded route:
This tutorial just shows one of the many ways to implement authentication and authorization in Svelte. There are many other aspects that I haven’t covered here. Things like implementing refreshing the token when it expires, implementing a logout or password reset endpoints. I’ve also used a very basic validation system, both on the client and on the server.
I’ll write more about these in future articles.
Get help with your Svelte or SvelteKit code
Got a Svelte/Sveltekit bug giving you headaches? I’ve been there!
Book a free 15 min consultation call, and I’ll review your code base and give you personalized feedback on your code.
P.S. If you have a bigger problem that may need more than 15 minutes, you can also pick my brain for a small fee. 😊 But that’s up to you!
What is JWT and why is it used for authentication?
JSON Web Token (JWT) is a standard that allows you to securely transmit data between parties as a JSON object. This data can be verified because it is digitally signed using a secret or a public/private key pair.
JWTs are used in authentication protocols to create access tokens that assert some number of claims. When a user successfully logs in, a JWT is generated by the server and sent to the client, which then includes it in future requests to verify the user's identity.
How does SvelteKit handle server-side code?
Server functions can be either load functions, which run both during server-side and client-side rendering, or form actions that are triggered by form submissions.
Why is the httpOnly option set to true for the cookie?
option is set to
This is a security best practice to prevent Cross-Site Scripting (XSS) attacks. With this option enabled, the cookie can only be modified by the server, which significantly reduces the risk of the client-side script accessing the secure cookie.
What is the purpose of the handle hook in SvelteKit?
hook in SvelteKit is similar to middleware in other frameworks like Express. It's a function that runs before a request is handled by a route. This hook can be used to modify the request or response or to perform actions based on the request.
In the context of JWT authentication, it can be used to verify the token and authenticate the user before the request reaches the route.
Read this blog post to learn more about handle hooks in SvelteKit: SvelteKit Internals: the handle hook
What does event.locals represent in the context of SvelteKit?
is a property of the event object in SvelteKit server-side functions. It's an object where you can store data that should be available to all downstream functions and endpoints during the lifecycle of a request.
In this tutorial, it's used to store the authenticated user's information.
What does the secure option do when setting a cookie?
option, when set to
, ensures that the cookie will only be sent over HTTPS. This is a security best practice that helps to prevent the cookie from being exposed to eavesdropping.
Why is the sameSite option set to strict in the cookie settings?
option set to
prevents the cookie from being sent in cross-site requests. This helps to protect against Cross-Site Request Forgery (CSRF) attacks by ensuring the cookie is only sent if the request originated from the same site that set the cookie.
What happens if JWT verification fails in the handle hook?
If JWT verification fails in the
hook, an error will be thrown. This could be due to the token being invalid or expired. In such a case, the user would not be authenticated and the request would be handled as an unauthenticated request.
How is the password stored in the database?
The passwords are stored in the database as hashed strings . This means that the original password is transformed into a unique string of characters using a hashing algorithm.
Hashing is a one-way process, making it nearly impossible to derive the original password from the hashed string. This enhances the security of user data.
How are errors handled in this authentication flow?
Errors are handled by returning appropriate error messages from the server to the client. These messages are then displayed to the user. It's important to handle errors carefully to prevent leaking sensitive information.