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)