Sveltekit Private Routes with Backendless
Tags: svelte, backendless, javascript
Published: 2022-03-12 05:01:19

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_
```html
<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>
```

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_
```html
<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>
```

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_
```json
[
	"/",
	"/login",
	"/register"
]
```

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_
```javascript
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
}
```

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_
```javascript
export const validateUserToken = async() => {
    try {
        let response = await Backendless.UserService.getCurrentUser();
    } catch(error) {

    }
}
```

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_
```javascript
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) {

    }
}
```

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

_auth.js_
```javascript
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) {

    }
}
```

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_
```javascript
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;
    }
}
```

_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_
```javascript
import {onMount} from 'svelte';
```

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_
```javascript
onMount(async() => {
    // Code coming soon
}
```

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

___layout.svelte_
```javascript
import {validateUserToken} from "$lib/functions/auth";

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

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](https://i.imgur.com/eTtyLLR.gif)
 
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_
```javascript
let isSiteReadyToLoad = false;
```

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_
```html
{#if isSiteReadyToLoad}
    {#if $user.email}
        <h1>Welcome, User</h1>
    {:else}
        <h1>Please login</h1>
    {/if}

    <slot></slot>
{/if}
```

#### Optional Animation Effect
And to add the Svelte animation, import the `fade` function at the top of your `<script>` tag

___layout.svelte_
```javascript
import { fade } from 'svelte/transition';
```

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_
```html
{#if isSiteReadyToLoad}
    <div transition:fade>
        {#if $user.email}
            <h1>Welcome, User</h1>
        {:else}
            <h1>Please login</h1>
        {/if}

        <slot></slot>
    </div>
{/if}
```

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_
```javascript
// Hide the site content until it is fully loaded
let isSiteReadyToLoad = false;

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

	// Load the site
	isSiteReadyToLoad = true;
})
```

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](https://i.imgur.com/AefFplJ.gif)