FullStack JWT Auth: Diving into SvelteKit - Login and Registration
Tags: typescript, svelte, webdev, javascript
Published: 2022-02-10 11:16:27


## Introduction

Having hit the ground running with SvelteKit by building our project's layout as well as its logout feature in the previous article, we'll continue exploring SvelteKit in this article by implementing login and registration features.

## Source code

The overall source code for this project can be accessed here:

{% github  Sirneij/django_svelte_jwt_auth  %}

## Live version

This project was deployed on heroku (backend) and vercel (frontend) and its live version can be accessed [here](https://django-sveltekit-jwt-auth.vercel.app/).

## Step 1: Implement the login functionality

Let's begin by implementing the login functionality of our app. Open up `routes/accounts/login/index.svelte` in your editor and make the content look like:

```typescript
<script>
	import { notificationData } from '../../../store/notificationStore';
	import { post, browserSet, browserGet } from '$lib/requestUtils';
	import { goto } from '$app/navigation';
	import { BASE_API_URI } from '$lib/constants';
	import { fly } from 'svelte/transition';

	import { onMount } from 'svelte';

	let email = '',
		password = '',
		error;

	const handleLogin = async () => {
		if (browserGet('refreshToken')) {
			localStorage.removeItem('refreshToken');
		}
		const [jsonRes, err] = await post(fetch, `${BASE_API_URI}/login/`, {
			user: {
				email: email,
				password: password
			}
		});
		if (err) {
			error = err;
		} else if (jsonRes.user.tokens) {
			browserSet('refreshToken', jsonRes.user.tokens.refresh);
			notificationData.set('Login successful.');
			await goto('/');
		}
	};
	onMount(() => {
		const notifyEl = document.getElementsByClassName('notification');

		if (notifyEl && $notificationData !== '') {
			setTimeout(() => {
				notifyEl.display = 'none';
				notificationData.set('');
			}, 5000);
		}
	});
</script>

<svelte:head>
	<title>Login | FullStack Django & SvelteKit</title>
</svelte:head>

<section
	class="container"
	in:fly={{ x: -100, duration: 500, delay: 500 }}
	out:fly={{ duration: 500 }}
>
	<h1>Login</h1>
	{#if error}
		<p class="center error">{error}</p>
	{/if}
	<form class="form" on:submit|preventDefault={handleLogin}>
		<input
			bind:value={email}
			name="email"
			type="email"
			aria-label="Email address"
			placeholder="Email address"
		/>
		<input
			bind:value={password}
			name="password"
			type="password"
			aria-label="password"
			placeholder="password"
		/>
		<button class="btn" type="submit">Login</button>
		<p class="center">No account yet? <a href="/accounts/register">Get started</a>.</p>
	</form>
</section>
```

This `.svelte` file contains a couple of new imports and some scripts aside the `notificationData` explained in the previous article. The first notable import is `post`. This abstracts away sending POST requests to the server and has the following definition in `lib/requestUtils.ts`:

```typescript
// lib -> requestUtils.ts
...
export const post = async (fetch, url: string, body: unknown) => {
  try {
    const headers = {};
    if (!(body instanceof FormData)) {
      headers["Content-Type"] = "application/json";
      body = JSON.stringify(body);
      const token = browserGet("refreshToken");
      if (token) {
        headers["Authorization"] = `Bearer ${token}`;
      }
      const res = await fetch(url, {
        method: "POST",
        body,
        headers,
      });
      if (res.status === 400) {
        const data = await res.json();
        const error = data.user.error[0];
        return [{}, error];
        // throw { id: error.id, message: error };
      }
      const response = await res.json();
      return [response, ""];
    }
  } catch (error) {
    console.error(`Error outside: ${error}`);

    // throw { id: '', message: 'An unknown error occurred.' };
    return [{}, `An unknown error occurred. ${error}`];
  }
};
...
```

It is an asynchronous function which expects the global `window.fetch`, the url to send the request, and the data to be sent. Looking into the `try` block, we enforced that only `json` data type will be handled and then went on to make the post request while ensuring proper error handling from the response.

Back to the `.svelte` file, we declared some variables &mdash; `email`, and `password` &mdash; and bound them to their respective form inputs using the `bind:value` directive. A very simple and intuitive way of binding input values without the ceremonial state bindings in `react`. To give feedbacks about possible errors, we also have the `error` variable declared which later on was given the error response from the `post` function.

Entering the `handleLogin` asynchronous function, we first remove any residual `refreshToken` that might lurking around user's browser. If not done, we will be faced with some non-informative error if the user tries to login. Then we called on our `post` function and passed in the required arguments. If no errors was encountered, we save the user's `refeshToken` to localStorage, update the notoficationData and redirects the user to the home page. The `handleLogin` function was called on the form's submission using the `on:submit` directive. Notice that before assigning this directive to the function, we added `|preventDefault`. This is extremely important to prevent full page refresh which defeats `app-like` feel.

Since users are automatically redirected to the login page after logging out of their accounts, we also implemented a simple way of resetting the notificationData and animating the notification via the `setTimeout` function located inside `onMount`. `onMount` is almost equivalent to react's `componentDidMount` lifecycle. The reason `setTimeout` was put inside this lifecycle is too ensure that the page has fully been loaded and we have access to `document.getElementsByClassName('notification');`.

## Step 2: Implement the registration flow:

Now that we've gone through how the login was implemented, let's checkout the registration flow. In the `routes/accounts/register/index.svelte`, we have the snippets below:

```typescript
// outes/accounts/register/index.svelte

<script>
	import { fly } from 'svelte/transition';
	import { goto } from '$app/navigation';
	import { BASE_API_URI } from '$lib/constants';
	import { notificationData } from '../../../store/notificationStore';

	let email = '',
		fullName = '',
		bio = '',
		username = '',
		password = '',
		confirmPassword = '',
		error = '';
	const submitForm = async () => {
		await fetch(`${BASE_API_URI}/register/`, {
			method: 'POST',
			mode: 'cors',
			headers: {
				'Content-Type': 'application/json'
			},
			body: JSON.stringify({
				user: {
					email: email,
					username: username,
					password: password,
					bio: bio,
					full_name: fullName
				}
			})
		})
			.then((response) => {
				if (response.status === 201) {
					notificationData.set('Registration successful. Login now.');
					goto('/accounts/login');
					// console.log('User:', response.json());
				} else if (response.status === 400) {
					console.log(response.json());
				}
			})
			.catch((error) => {
				error = error;
				console.error('Error:', error);
			});
	};
	const passwordConfirm = () => (password !== confirmPassword ? false : true);
</script>

<svelte:head>
	<title>Register | FullStack Django & SvelteKit</title>
</svelte:head>

<section
	class="container"
	in:fly={{ y: 100, duration: 500, delay: 500 }}
	out:fly={{ duration: 500 }}
>
	<h1>Register</h1>
	{#if error}
		<p class="center error">{error}</p>
	{/if}
	<form class="form" on:submit|preventDefault={submitForm}>
		<input
			bind:value={email}
			type="email"
			aria-label="Email address"
			placeholder="Email address"
			required
		/>
		<input
			bind:value={username}
			type="text"
			aria-label="Username"
			placeholder="Username"
			required
		/>
		<input
			bind:value={fullName}
			type="text"
			aria-label="Full name"
			placeholder="Full name"
			required
		/>
		<input
			bind:value={bio}
			type="text"
			aria-label="Brief bio"
			placeholder="Tell us about yourself..."
			required
		/>
		<input
			bind:value={password}
			type="password"
			name="password"
			aria-label="password"
			placeholder="password"
			required
		/>
		<input
			bind:value={confirmPassword}
			type="password"
			name="confirmPassword"
			aria-label="Confirm password"
			placeholder="Confirm password"
			required
		/>
		{#if confirmPassword}
			<button class="btn" type="submit">Register</button>
		{:else}
			<button class="btn" type="submit" disabled>Register</button>
		{/if}
	</form>
</section>

```

We did same thing as what we did with the login flow aside from using different api endpoint, updating notificationData to a different string, sending more data to the server, and redirecting to the login page. Also, we didn't use our `post` function here but using it should produce same output.

That's basically it! We have successfully implemented a robust full stack jwt authentication system! Though we have done some authorizations as well but not intentional enough. We will try to do some intentional authorizations in our bonus article where we'll look into how to update user data and maybe create an endpoint which only users with admin role can assess and manipulate its data! Please, be on a lookup for it!!!

## Outro

Enjoyed this article, consider [contacting me for a job, something worthwhile or buying a coffee ☕](mailto:sirneij@gmail.com). You can also connect with/follow me on [LinkedIn](https://www.linkedin.com/in/idogun-john-nelson/).