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.ts
where we going to accesscookies
and get oursession
cookie using get method.We will get our user on the basis of session and going to add data to
locals
as user.In
+layout.server.js
, we going to access ourlocals
data and going to return user data.We can use that user data to set a store for authentication or use it by
parent
method.
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.svelte
and+page.server.js
. In+page.svelte
you going to add yourlogin form
whichemail
andpassword
fields. On login it's going to call aform action
on+page.server.js
for 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.ts
to 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
login
route and if user is not authorized we going to redirect him tologin
page 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.js
and 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
parent
method. We going to use routespage.js
'sload
method 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.js
file 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-signature
or 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