Intoduction
Having introduced SvelteKit and our project's structure in the previous article of this series, it's time we built something.
Source code
The overall source code for this project can be accessed here:
Sirneij / django_svelte_jwt_auth
A robust and secure Authentication and Authorization System built with Django and SvelteKit
django_svelte_jwt_auth
This is the codebase that follows the series of tutorials on building a FullStack JWT Authentication and Authorization System with Django and SvelteKit.
This project was deployed on heroku (backend) and vercel (frontend) and its live version can be accessed here.
To run this application locally, you need to run both the backend
and frontend
projects. While the latter has some instructions already for spinning it up, the former can be spinned up following the instructions below.
Run locally
To run locally
-
Clone this repo:
git clone https://github.com/Sirneij/django_svelte_jwt_auth.git
-
Change directory into the
backend
folder:cd backend
-
Create a virtual environment:
pipenv shell
You might opt for other dependencies management tools such as
virtualenv
,poetry
, orvenv
. It's up to you. -
Install the dependencies:
pipenv install
-
Make migrations and migrate the database:
python manage.py makemigrations python manage.py migrate
-
Finally, run the application:
python manage.py runserver
Live version
This project was deployed on heroku (backend) and vercel (frontend) and its live version can be accessed here.
Step 1: Make the layout
Since our entire app will have some uniformity in terms of navigation and footer, let's populate our routes' __layout.svelte
with:
<script lang="ts">
import { notificationData } from '../store/notificationStore';
import { fly } from 'svelte/transition';
import Header from '../components/Header/Header.svelte';
import '../dist/css/style.min.css';
</script>
<Header />
{#if $notificationData}
<div class="notification-container">
<p
class="notification"
in:fly={{ x: 200, duration: 500, delay: 500 }}
out:fly={{ x: 200, duration: 500 }}
>
{$notificationData}
</p>
</div>
{/if}
<main>
<slot />
</main>
<footer>
<p>
Visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit. Coded by
<a href="https://github.com/Sirneij/">John O. Idogun</a>
</p>
</footer>
It's a basic structure which has Header
component, footer
, display of notifications, and a slot
tag to take in other pages' contents. Auto-subscription of notificationData
was done by appending $
at it's beginning. notificationData
is a writable store with the following definition in stores/notificationStore.ts
:
import { writable } from "svelte/store";
export const notificationData = writable("");
It expects a string value. Header
is a component that houses the app's navigation and has the following content in components/Header/Header.svelte
:
<script lang="ts">
import { page } from '$app/stores';
import logo from './svelte-logo.svg';
import john from './john.svg';
import { userData } from '../../store/userStore';
import { logOutUser } from '$lib/requestUtils';
</script>
<header>
<div class="corner">
<a href="https://kit.svelte.dev">
<img src={logo} alt="SvelteKit" />
</a>
</div>
<nav>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
</svg>
<ul>
<li class:active={$page.url.pathname === '/'}>
<a sveltekit:prefetch href="/">Home</a>
</li>
{#if !$userData.username}
<li class:active={$page.url.pathname === '/accounts/login'}>
<a sveltekit:prefetch href="/accounts/login">Login</a>
</li>
<li class:active={$page.url.pathname === '/accounts/register'}>
<a sveltekit:prefetch href="/accounts/register">Register</a>
</li>
{:else}
<li>
Welcome, <a sveltekit:prefetch href="/accounts/user/">{$userData.username}</a>
</li>
<li>
<a href={null} on:click={logOutUser} style="cursor: pointer;">Logout</a>
</li>
{/if}
</ul>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
</svg>
</nav>
<div class="corner">
<a href="https://github.com/Sirneij/">
<img src={john} alt="John O. Idogun" />
</a>
</div>
</header>
This component introduces a couple important imports:
page
: To keep track of the current page, we imported the built-in page and utilizing itsurl
object, we dynamically addedactive
classes to the navigation items.page
store contains an object with the currenturl
,params
,stuff
,status
anderror
.logo
andjohn
are just images which are in the same directory as theHeader.svelte
file.userData
: Just likenotificationData
,userData
is a custom writable store exported fromstores/userStore.ts
to make available current user's data. It has the following definition:
import { writable } from "svelte/store";
export const userData = writable({});
These data are updated/set during login and logout operations.
-
logOutUser
is one of the many functions domiciled in thelib/requestUtils.ts
file. It's purpose is to log the current user out and subsequently reset theuserData
to an empty object. The implementation is shown below:
//lib -> requestUtils.ts
...
export const logOutUser = async () => {
const res = await fetch(`${BASE_API_URI}/token/refresh/`, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
refresh: `${browserGet('refreshToken')}`
})
});
const accessRefresh = await res.json();
const jres = await fetch(`${BASE_API_URI}/logout/`, {
method: 'POST',
mode: 'cors',
headers: {
Authorization: `Bearer ${accessRefresh.access}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
refresh: `${browserGet('refreshToken')}`
})
});
if (jres.status !== 204) {
const data = await jres.json();
const error = data.user.error[0];
throw { id: error.id, message: error };
}
localStorage.removeItem('refreshToken');
userData.set({});
notificationData.set('You have successfully logged out.')
await goto('/accounts/login');
};
From the snippet, we made the first POST request to BASE_API_URI//token/refresh/
sending the current user's refresh
token. This request returns the user's access
token which was used as Authorization
header for the /logout/
endpoint. This process is required as only authenticated users can logout. If the response is successful, we remove refreshToken
from the localStorage, reset userData
, set notificationData
to something informative, and then redirect the user to accounts/login
page. That's basically it! Some notable helper functions are the browserSet
and browserGet
which help set/save and get from the localStorage. Their implementations ain't hard to decipher:
import { browser } from '$app/env';
...
export const browserGet = (key: string):string | undefined => {
if (browser) {
const item = localStorage.getItem(key);
if (item) {
return item;
}
}
return null;
};
export const browserSet = (key:string, value:string) : void => {
if (browser) {
localStorage.setItem(key, value);
}
};
We utilized the built-in browser
to ensure we are in the browser environment before setting and getting items from the localStorage.
That is it for this part. Up next is how we handled registrations and user logins. Stay with me...
Outro
Enjoyed this article, consider contacting me for a job, something worthwhile or buying a coffee ☕. You can also connect with/follow me on LinkedIn.
Top comments (0)