Here's how I'm protecting routes. I keep seeing this question a lot, so I'm going to answer with my method which is easy to re-use.
I have a AuthLayout component that I just wrap around the routes I want to protect.
/* src/lib/components/AuthLayout.svelte */
<script lang="ts">
import { goto } from '$app/navigation';
import authStore from '$lib/stores/auth';
import { onDestroy } from 'svelte';
let ok = false;
const unsubscribe = authStore.subscribe((state) => {
if (!state.initializing && state.fbUser !== undefined) {
ok = state.fbUser !== null;
if (!ok) {
goto('/login');
}
}
});
onDestroy(unsubscribe);
</script>
{#if ok}
<slot />
{:else}
<div />
{/if}
A few things are happening here. I have a authStore
that looks like this:
/* src/lib/stores/auth.ts */
import { writable } from 'svelte/store';
import type { User as FbUser } from 'firebase/auth';
export type AuthStore = {
initializing: boolean;
fbUser: FbUser | null | undefined;
};
const authStore = writable<AuthStore>({
initializing: true,
fbUser: undefined
});
export default authStore;
I am using Firebase auth, so in my top level layout.svelte, I've got the following:
/* src/routes/+layout.svelte */
<script lang="ts">
import '../app.css';
import { onMount } from 'svelte';
import authStore from '$lib/stores/auth';
import Footer from './Footer.svelte';
import Header from './Header.svelte';
import { auth } from '../firebase';
onMount(() => {
auth.onAuthStateChanged(async (user) => {
authStore.update((state) => ({
...state,
initializing: false,
fbUser: user
}));
});
});
</script>
<Header />
<main><slot /></main>
<Footer />
I update my authStore whenever the firebase user value changes in my onAuthStateChanged listener.
Now, let's circle back to the AuthLayout component. It grabs the fbUser value from the authStore and if it is null, I redirect to the "/login" route.
The reason there is a ok
boolean is to avoid the brief flicker when the content you're trying to protect might show. By using the ok
check, I can ensure that I just show a empty div when there is no firebase user. Otherwise, I will show the protected content via the <slot />
. There is also an onDestroy callback so I don't have a memory leak with the store subscription.
Finally, here is an example of how I use AuthLayout to protect /welcome:
/* src/routes/welcome/+layout.svelte */
<script>
import AuthLayout from '$lib/components/AuthLayout.svelte';
</script>
<AuthLayout><slot /></AuthLayout>
Voila! The route has been protected.