DEV Community

Brewhouse Digital
Brewhouse Digital

Posted on

Sveltekit Private Routes with Backendless

If you've been following along with the previous articles, you should have a working Login and Register page connected to Backendless. Now we're going to streamline these a bit and add in private routing through SvelteKit.

We haven't done much work on the homepage (index.svelte) so we'll work on that now. Delete the default Svelte code and add in some base content. We'll add in the HTML first, then add in a new page called members.svelte

index.svelte

<script>
    import {user} from "$lib/store";
</script>

<div class="container mx-auto">
    <h1 class="text-5xl font-bold mb-3">Hello World</h1>
    <p>
        <a href={$user.email ? "/members" : "/login"} class="btn btn-outline">
            Click here for the private route
        </a>
    </p>
</div>
Enter fullscreen mode Exit fullscreen mode

You'll see that our link href is wrapped in an if statement. Each of your private routes should be written like this to make sure that the user is logged in. Alternatively, you could wrap the entire link tag in a Svelte {#if}{/if} statement if you wanted to hide the button entirely. Any time you use a Svelte Store in your HTML content, you must use the reactive symbol $. This lets the content know that this variable will change and to watch for updates.

You could also use this to append a target route to the login page, such as /login?target-route=members and then redirect the user after login, but that will be in a different tutorial.

Create the Members Page

Now create a new page in your routes folder called members.svelte, and add in some base html for that as well.

members.svelte

<div class="container mx-auto">
    <h1 class="text-5xl font-bold mb-3">Members Only!</h1>
    <p><a href="/" class="btn btn-outline">Return Home</a></p>
</div>
Enter fullscreen mode Exit fullscreen mode

In this page, we don't need to treat the anchor tags with fancy if statements, since the site will see this as a private route, and hide it entirely to unauthenticated users.

Creating the Private Route System

With Backendless, when you log in, an Object is returned containing some of your user information. In our login.svelte page, we're updating our global Svelte Store user to be this value. Since this is global now, we can use it on our __layout.svelte file to check whenever someone is navigating through site. If they land on a private page, and they aren't logged in, we gracefully redirect them back to the Login screen.

We will accomplish this by creating a list of what URLs should we want checked.

Note: This tutorial will be guiding you with the idea that we will be creating a members-only site. The assumption is that the majority of pages in your future web app will be private, and only a few will be public.

Creating the URL List

Inside your lib/data folder, create a new file called publicRoutes.json. Inside will be an array containing three pages to start. The homepage, the login page, and the register page. If an unauthenticated user lands on a page that is NOT in this list, they will be routed to the Login page.

publicRoutes.json

[
    "/",
    "/login",
    "/register"
]
Enter fullscreen mode Exit fullscreen mode

Any future public routes, like a blog post or Contact Us page, will go here in this list.

Validating the User

Inside your lib/functions folder, create a new file called auth.js. This will hold multiple functions for us in the future. For now, we want to create a new function called validateUserToken. This function will be checking the Backendless token that's saved to your localStorage, and making sure that it has not expired or been tampered with.

We'll need to import a few things before we get started. Your initial file should look something like this (with some notes):

auth.js

import Backendless from "backendless";
import {user} from "$lib/store";
import publicRoutes from "$lib/data/publicRoutes.json";
import {goto} from "$app/navigation";

// Check if user is logged in
export const validateUserToken = async() => {
    // Validate the user token

    // If valid: Update the user store with the latest information

    // If not valid: Unset the user store, and redirect to the login page

    // If the token is corrupted, force logout and redirect user to the login page
}
Enter fullscreen mode Exit fullscreen mode

The last import statement is a new one for the tutorial. The goto function is from the Svelte library, and is an easy way to automatically route a user to another page. This is what we'll be using to send unauthenticated users to our Login page.

Note that we've made this an async call. We need this to finish processing before routing our user throughout the site. To get started, we'll create a Try/Catch and add in our first Backendless method to check for the token:

auth.js

export const validateUserToken = async() => {
    try {
        let response = await Backendless.UserService.getCurrentUser();
    } catch(error) {

    }
}
Enter fullscreen mode Exit fullscreen mode

You don't need to pass anything to the getCurrentUser() method. The Backendless SDK already knows where to look for the token. This could return an object containing user data, a null value if the user is not logged in, or an HTTP error code if the token is expired or corrupted.

auth.js

export const validateUserToken = async() => {
    try {
        let response = await Backendless.UserService.getCurrentUser();

        if(response) {
            // Valid user found
            user.set(response);
        } else {
            // Unset the user store
            user.set({});
        }
    } catch(error) {

    }
}
Enter fullscreen mode Exit fullscreen mode

Now to redirect the user if they are not logged in:

auth.js

export const validateUserToken = async() => {
    try {
        let response = await Backendless.UserService.getCurrentUser();

        if(response) {
            // Valid user found
            user.set(response);
        } else {
            // Unset the user store
            user.set({});

            // Invalid user found. Grab their current location to match against the publicRoutes list
            let currentLocation = window.location.pathname;

            // This will redirect if the unauthenticated user is on a private route
            if(!publicRoutes.includes(currentLocation)) {
                await goto("/login?error=expired-token");
                return false;
            }
        }
    } catch(error) {

    }
}
Enter fullscreen mode Exit fullscreen mode

This uses a simple includes to see if the user is on a private route. If they are, send them elsewhere. In this example, we're appending a query parameter so that we can show an alert bar on the Login page that their token has expired and to log back in. The return false; will prevent anything else from firing in the function.

We'll do something very similar in the catch portion, with an additional Backendless force-logout method just to make sure all the user data gets reset. That will look like this:

auth.js

export const validateUserToken = async() => {
    try {
        let response = await Backendless.UserService.getCurrentUser();

        if(response) {
            // Valid user found
            user.set(response);
        } else {
            // Unset the user store
            user.set({});

            // Invalid user found. Grab their current location to match against the publicRoutes list
            let currentLocation = window.location.pathname;

            // This will redirect if the unauthenticated user is on a private route
            if(!publicRoutes.includes(currentLocation)) {
                await goto("/login?error=expired-token");
                return false;
            }
        }
    } catch(error) {
        // User has invalid token, so log them out
        await Backendless.UserService.logout();
        await goto("/?error=expired-token");
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

For your site's specific needs, the catch section may be useful for logging to an external analytics tool when a user's token is corrupted or expired.

Adding the Validation Check on the Layouts File

Inside the __layouts.svelte file, we need to import the Svelte function onMount, because we'll need to be validating the Backendless token saved to the user's localStorage when they log in. Since Svelte is a compiled language, checking anything browser related should always be inside an onMount or inside a function call.

__layout.svelte

import {onMount} from 'svelte';
Enter fullscreen mode Exit fullscreen mode

Since we want to make sure each route is checked before it's loaded, we want our onMount to be asynchronous, so we can use the await property. Start the onMount call like so:

__layout.svelte

onMount(async() => {
    // Code coming soon
}
Enter fullscreen mode Exit fullscreen mode

Import our new validateUserToken and add it inside the onMount() call:

__layout.svelte

import {validateUserToken} from "$lib/functions/auth";

onMount(async() => {
    await validateUserToken();
})
Enter fullscreen mode Exit fullscreen mode

To see how this works now, make sure you are logged out of your backendless account by clearing your localStorage, and manually going to the /members route. If everything was set up successfully, you should be routed directly to the Login page.

You may have seen a flash of content though, and that's what we'll be fixing next.

Demonstration of private routes being blocked and user being sent to the Login page

Note: This next part is optional. If you don't mind the flashing of content then you can skip this next step

Restricting content

Still in the __layouts.svelte file, create a new variable above the onMount named:

__layout.svelte

let isSiteReadyToLoad = false;
Enter fullscreen mode Exit fullscreen mode

And in our content, we will be wrapping all the content in an if statement to hide it. This also gives us the option to add in a nice Svelte animation to make the content fade in.

You could also add an else here and add a loading icon if you have one.

Your HTML should look something like this now:

__layout.svelte

{#if isSiteReadyToLoad}
    {#if $user.email}
        <h1>Welcome, User</h1>
    {:else}
        <h1>Please login</h1>
    {/if}

    <slot></slot>
{/if}
Enter fullscreen mode Exit fullscreen mode

Optional Animation Effect

And to add the Svelte animation, import the fade function at the top of your <script> tag

__layout.svelte

import { fade } from 'svelte/transition';
Enter fullscreen mode Exit fullscreen mode

To animate an element in Svelte, you need two things:

  • A conditional (such as our isSiteReadyToLoad)
  • A standard HTML tag like a <div>. Svelte animation properties do not work on Svelte Components.

Your HTML should be structured like so:

__layout.svelte

{#if isSiteReadyToLoad}
    <div transition:fade>
        {#if $user.email}
            <h1>Welcome, User</h1>
        {:else}
            <h1>Please login</h1>
        {/if}

        <slot></slot>
    </div>
{/if}
Enter fullscreen mode Exit fullscreen mode

To finish off the Restricted Content section, we can set the value of our isSiteReadyToLoad to true after the validateUserToken() function has completed:

__layout.svelte

// Hide the site content until it is fully loaded
let isSiteReadyToLoad = false;

onMount(async() => {
    await validateUserToken();

    // Load the site
    isSiteReadyToLoad = true;
})
Enter fullscreen mode Exit fullscreen mode

Now if you manually navigate to /members it should fade in the Login page, and you will never see the Members content.

To fully test our new private route, lets log into the site with the username and password that we created back in Tutorial #1. Once you are logged in, you should see your "Welcome, User" at the top left part of your screen. Now manually navigate to the homepage and click on the Members button.

Demonstration of private routing on Sveltekit

Top comments (1)

Collapse
 
rharing profile image
Ronald Haring

is there a repo somewhere that i can clone as i am stuck on the tailwind.config.cjss