Introduction
In this article, We going to talk about cookies in sveltekit and learn how to handle authentication with Cookies after sveltekit removed getSession handler from hooks. We are now left with new route system and new cookies method. Here, we going to look into two different possible methods to authenticate a user in sveltekit.
- Using
parent()function in routes. - Using svelte stores to handle authentication.
Cookies
Cookies is a new method introduced in sveltekit for cookie header. You can use this method to work with cookies in your webapp with least efforts. There are some more methods in cookies those are set, get and delete.
- Set Method
This methods sets a cookie. This will add a set-cookie header to the response, but also make the cookie available via cookies.get during the current request.
The httpOnly and secure options are true by default, and must be explicitly disabled if you want cookies to be readable by client-side JavaScript and/or transmitted over HTTP. The sameSite option default to lax.
By default, the path of a cookie is the 'directory' of the current pathname. In most cases you should explicitly set path: '/' to make the cookie available throughout your app.
Set method takes three parameters name, value and options, options are optional so it's up to you what you gonna set.
cookies.set('session', user.entityId, {
path: '/',
httpOnly: true,
sameSite: 'strict',
secure: !dev,
maxAge: 60 * 60 * 24 * 30
});
Here, I'm using set method to set my session cookie in Set-Cookie header and defining options as per need.
1. name : session (string)
2. value : user.entityId (string)
3. options: {
path: '/',
httpOnly: true,
sameSite: 'strict',
secure: !dev,
maxAge: 60 * 60 * 24 * 30
}
- Get Method
This method is use to get a cookie that was previously set with cookies.set, or from the request headers.
Get method takes two parameters name and options, options are optional.
const session = cookies.get('session');
Here, I'm using get method to access my session value from cookies that we set above using set method.
- Delete Method
This method is use to delete a cookie by setting its value to an empty string and setting the expiry date in the past.
Delete method takes two parameters name and options, options are optional.
await cookies.delete('session');
That's all we need to remove a cookie.
- Serialize Method
It's same as set method which serialize a cookie name-value pair into a Set-Cookie header string.
Authentication Flow in sveltekit
Here, I'll explain steps to authenticate a user using cookies and session.
Every user authentication starts after you have a register method. So, first make sure to have a user registered in db.
Make a login route which will gonna handle your user authentication. Here we gonna set Cookies named 'session' using set method.
We will update our
hooks.server.tswhere we going to accesscookiesand get oursessioncookie using get method.We will get our user on the basis of session and going to add data to
localsas user.In
+layout.server.js, we going to access ourlocalsdata and going to return user data.We can use that user data to set a store for authentication or use it by
parentmethod.
Local interface that defines event.locals, which can be accessed in hooks (handle, and handleError), server-only load functions, and +server.js files.
Authentication using parent() method
Now we will follow our flow and going to code our authentication flow.
- Create a route which will going to handle your login. It should have
+page.svelteand+page.server.js. In+page.svelteyou going to add yourlogin formwhichemailandpasswordfields. On login it's going to call aform actionon+page.server.jsfor now it's our default action.
// +page.server.ts (inside login route)
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import { customResponse } from '$lib/utils';
import { userRepository } from '$lib/Redis/dbRepository';
import * as bcrypt from 'bcrypt';
import { dev } from '$app/environment';
export const actions = {
default: async ({ request, cookies }) => {
const form = await request.formData();
const email = form.get('email');
const password = form.get('password');
if (!email || !password) return customResponse(400, false, 'Email and Password are required');
if (typeof email !== 'string' || typeof password !== 'string')
return customResponse(400, false, 'Enter a valid email and password.');
const user = await userRepository.search().where('email').equals(email).return.first();
const passwordMatch = user && (await bcrypt.compare(password, user.password));
if (!user || !passwordMatch)
return customResponse(400, false, 'You entered the wrong credentials.');
cookies.set('session', user.entityId, {
path: '/',
httpOnly: true,
sameSite: 'strict',
secure: !dev,
maxAge: 60 * 60 * 24 * 30
});
// return customResponse(200, true, 'User loggedIn successfully');
throw redirect(307, '/dashboard');
}
};
// customResponse function from lib/utils
import { invalid } from '@sveltejs/kit';
export const customResponse = (status: number, success: boolean, message: string, data?: any) => {
if (success) {
return {
success: success,
message: message,
info: data
};
}
return invalid(status, {
success: success,
message: message,
info: data
});
};
Here, I'm validating the user and after validating successfully we are using our set method to set session cookie.
cookies.set('session', user.entityId, {
path: '/',
httpOnly: true,
sameSite: 'strict',
secure: !dev,
maxAge: 60 * 60 * 24 * 30
});
- Now we will going to use our
hook.server.tsto provide user data to every route whenever we need it.
// hook.server.ts
import type { Handle } from '@sveltejs/kit';
import { userRepository } from './lib/Redis/dbRepository';
// custom redirect from joy of code `https://github.com/JoysOfCode/sveltekit-auth-cookies/blob/migration/src/hooks.ts`
function redirect(location: string, body?: string) {
return new Response(body, {
status: 303,
headers: { location }
});
}
const unProtectedRoutes: string[] = [
'/',
'/login',
'/createAdmin',
'/features',
'/docs',
'/deployment'
];
export const handle: Handle = async ({ event, resolve }) => {
const session = event.cookies.get('session');
if (!session && !unProtectedRoutes.includes(event.url.pathname))
return redirect('/login', 'No authenticated user.');
const currentUser = await userRepository.fetch(session as string);
if (currentUser) {
event.locals.user = {
isAuthenticated: true,
name: currentUser.name,
email: currentUser.email,
type: currentUser.user_type,
active: currentUser.active,
phone: currentUser.phone
};
} else {
if (!unProtectedRoutes.includes(event.url.pathname)) return redirect('/', 'Not a valid user');
}
return resolve(event);
};
Here, We going to access our session cookie using get method const session = event.cookies.get('session');.
After that we need to check if session cookie exist or not and we are adding extra condition for route check because we are going to redirect if user is not loggedIn.
Why we are adding this extra route check conditions? It's because when user is on
loginroute and if user is not authorized we going to redirect him tologinpage which will create an infinite loop of redirect and our browser don’t like that.
After validation we are going to get our user from database and update our locals
event.locals.user = {
isAuthenticated: true,
name: currentUser.name,
email: currentUser.email,
type: currentUser.user_type,
active: currentUser.active,
phone: currentUser.phone
};
- Now, we going to access our locals in
+layout.server.jsand going to return our user data from locals to our web app.
// +layout.server.js
export const load = async ({ request, locals, cookies }) => {
return {
user: locals.user
};
};
Here, we are accessing our locals and then returning it to all the pages because it's layout, all the data will be accessible to every page which shared the same layout.
- Accessing data in routes is easy because of
parentmethod. We going to use routespage.js'sloadmethod to access data fromlayout.
// +page.js
export const load = async ({ parent }) => {
const { user } = await parent();
if (user) {
user: user
}
};
This will make your user data accessible to the route. For first time it's going to look complex but after doing once you are going to love it.
Authentication using stores
In stores we going to need a auth.js file to add a store to our project.
Everything will be same as above part except here we are not going to use parent() method. We will set a store and then we are going to access our +layout.server.js data in +layout.js which will going to return our data to +layout.svelte and then we going to set our user data to store.
So follow all the steps till
+layout.server.js, and return user data to all pages.Then we are going to create a store in
lib/auth.js
import { writable } from 'svelte/store';
export const user = writable();
This will do our job for store.
- Now in
+layout.jsfile we will access the data came from+layout.server.js.
// `+layout.js`
export const load = async function ({ data }) {
return {
user: data.user
};
};
This will return data to our +layout.svelte in our export let data.
// +layout.svelte
<script lang="ts">
import { user } from "$lib/auth";
export let data;
$: $user.set(data.user);
</script>
<slot />
Now we have our store with user info and we can access it anywhere in our client side code.
This is all we need to handle authentication in sveltekit with cookies. We haven’t covered how to manage a protected route. So answer is already somewhere in this article(not accurate but idea to handle it). Do a bit brainstorming for that. I might have missed some part or anything you can ask me to add that in comments.
Resources
Here I'm leaving with you with some resources, I used some of them to write this article
- Joy of code authentication example
- The Ether Code Example
- AlexRMU store based authentication
- Discussion from Kit for replacing session
- Discussion from Kit Removing session
This is me writing for you. If you wanna ask or suggest anything please put it in comment and show some love ❤️.
Top comments (14)
Never, Never NEVER !!!!!11111one one one
Don't set raw userId as auth data:
cookies.set('session', user.entityId,- It's an easy way to hijack your administrator account.Important data should be signed, eg use
cookie-signatureor put data in JWT / unique UUID for sessionThanks for this important hint.
Can you explain it a little further, or give me a link?
You must remember that any value can be modified in such a way as to harm your application. In this case, after logging in, your id is provided and your permissions are retrieved based on this.
The attacker can craft the message and gain administrator privileges.
Yeah that's true. Have a look at project.
I'm not sure this will help you or not
Github - EtherCare
I'll use token or jwt for this kind of thing. This project is for testing redis as primary db so i did that.
Ok, you won't that, but don't show bad behavior. Some _script-kid _will copy it mindlessly, and then there will be crying and gnashing of teeth ...
Don't worry I'll be pushing my whole project this weekend which might be helpful and i would love if you suggest something after those changes. But json tokens are easy to crack.
Thank you. Couldn't figure out how to use the
Cookies. I was about to lose my mindHappy to help🤩
Ayo uhhhhh definitely don’t do that with your stores. You’re setting a store value to a global variable. If that gets called/ran by the server side you’re toast lol. Extremely dangerous. Everybody now has everyone else’s info.
I know what you saying, for e.g. i'm assigning locals data to users store after data is validated from sever and cleared even if it's server side rendered it won't going to cause problem but there are scenario's where it's way more dangerous.
Do you have a example repo for this tut?
github.com/theetherGit/EtherCare