SvelteKit Changes: Cookies and Authentication
Tags: javascript, webdev, svelte, sveltekit
Published: 2022-09-21 20:41:16

## 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.

1. Using `parent()` function in routes.
2. 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.

```Javascript
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. 

```go
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.

```Javascript 
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.

```Javascript 
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.

1. Every user authentication starts after you have a register method. So, first make sure to have a user registered in db.

2. Make a login route which will gonna handle your user authentication. Here we gonna set Cookies named 'session' using set method.

3. We will update our `hooks.server.ts` where we going to access `cookies` and get our `session` cookie using get method. 

4. We will get our user on the basis of session and going to add data to `locals` as user.

5. In `+layout.server.js`, we going to access our `locals` data and going to return user data.

6. 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 your `login form` which `email` and `password` fields. On login it's going to call a `form action` on `+page.server.js` for now it's our default action.

```Javascript 
// +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`.

```go
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.

```Javascript 
// 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 to `login` 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

```Javascript 
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.

```Javascript 
// +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 routes `page.js`'s `load` method to access data from `layout`.
```
// +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`

```Javascript 
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`.

```Javascript
// `+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`.
```Javascript 
// +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](https://github.com/joysofcode/sveltekit-auth-cookies/tree/migration)
- [The Ether Code Example](https://github.com/theetherGit/EtherCare)
- [AlexRMU store based authentication](https://stackblitz.com/edit/sveltejs-kit-template-default-arkk5k)
- [Discussion from Kit for replacing session](https://github.com/sveltejs/kit/discussions/6508)
- [Discussion from Kit Removing session](https://github.com/sveltejs/kit/discussions/5883)

This is me writing for you. If you wanna ask or suggest anything please put it in comment and show some love ❤️.