DEV Community

Cover image for FullStack JWT Auth: Diving into SvelteKit - Layout and Logout
John Owolabi Idogun
John Owolabi Idogun

Posted on

FullStack JWT Auth: Diving into SvelteKit - Layout and Logout

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:

GitHub logo 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, or venv. 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>

Enter fullscreen mode Exit fullscreen mode

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("");
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

This component introduces a couple important imports:

  • page: To keep track of the current page, we imported the built-in page and utilizing its url object, we dynamically added active classes to the navigation items. page store contains an object with the current url, params, stuff, status and error.

  • logo and john are just images which are in the same directory as the Header.svelte file.

  • userData: Just like notificationData, userData is a custom writable store exported from stores/userStore.ts to make available current user's data. It has the following definition:

  import { writable } from "svelte/store";

  export const userData = writable({});
Enter fullscreen mode Exit fullscreen mode

These data are updated/set during login and logout operations.

  • logOutUser is one of the many functions domiciled in the lib/requestUtils.ts file. It's purpose is to log the current user out and subsequently reset the userData 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');
  };
Enter fullscreen mode Exit fullscreen mode

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);
    }
  };
Enter fullscreen mode Exit fullscreen mode

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)