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)