sveltekit hooks can be used to protect all routes in your app by redirecting to a login page as in this simple example:
hooks.server.ts
import { redirect, type Handle } from '@sveltejs/kit'
import { AUTH_COOKIE } from '$env/static/private'
export const handle: Handle = async ({ event, resolve }) => {
    if (event.url.pathname.startsWith('/login')) {
        return await resolve(event)
    }
    if (event.cookies.get('AUTH_COOKIE') !== AUTH_COOKIE) {
        redirect(303, '/login')
    }
    return await resolve(event)
}
This works well, but but the redirect makes the login page part of the browser's history, meaning the user can navigate back to it. Maybe you don't want that - it's a pretty ugly experience especially swiping back to it on mobile.
I spent way too long trying to solve this with ChatGPT sending me on a wild goose chase. In the end the solution was simple: use svelte's goto with replaceState after successful login:
login.svelte
<script lang="ts">
    import { enhance } from '$app/forms'
    import { goto } from '$app/navigation'
    import type { PageProps } from './$types'
    let { form }: PageProps = $props()
    $effect(() => {
        if (form?.success) {
            goto('/', { replaceState: true })
        }
    })
</script>
<main class="h-full">
    <div class="flex h-full flex-col items-center justify-center gap-4">
        <form class="grid place-content-center gap-4" method="POST" action="?/login" use:enhance>
            <input class="input" name="username" type="text" placeholder="Username" autofocus required />
            <input class="input" name="password" type="password" placeholder="Password" required />
            <button class="btn preset-filled-primary-500">Log in</button>
        </form>
        <p class="text-error-500 {form?.error ? 'opacity-100' : 'opacity-0'}">{form?.error ?? '*'}</p>
    </div>
</main>
The login form is backed by a form action in +page.server.ts. The important thing here is to not redirect after login, that is done using the goto in login.svelte
export const actions = {
    login: async ({ cookies, request }) => {
        const data = await request.formData()
        const username = data.get('username') as string
        const password = data.get('password') as string
        const hashedPassword = md5Hash(password)
        const token = await login(username, hashedPassword)
        if (!token) {
            return { error: 'Invalid username or password' }
        }
        cookies.set('AUTH_COOKIE', AUTH_COOKIE, {
            path: '/',
            httpOnly: true,
            secure: true,
            sameSite: 'strict',
            maxAge: 60 * 60 * 24 * 365 * 10, // 10 years
        })
        return { success: true }
        // redirect(303, '/')
    },
} satisfies Actions
Once you go goto, you can't go back!
 

 
    
Top comments (0)