I have a problem with redirecting after submitting form in SvelteKit.
I have some routes that needs authentication, before rendering it I’m checking if user is authenticated.
routes
│ ├── +page.svelte
│ ├── login
│ │ ├── +page.server.ts
│ │ └── +page.svelte
│ └── movies
│ ├── +page.server.ts
│ ├── +page.svelte
│ └── [id]
│ ├── +page.server.ts
│ ├── +page.svelte
│ └── +page.ts
For example, when user vistits /movies
route, in my +page.server.ts
I’m checking if user is authenticated, by lookup in cookies. If cookies haven’t auth_token
I’m redirecting him to /login
route with some info about desired redirecting route after succesful login.
So in that scenario, user will be redirected to /login?redirectTo=%2Fmovies
For that part, everything works great.
The idea is that, if he authenticate it should redirect him again to /movies
route.
I wrote some logic in routes/login/+page.server.ts
to gather value of redirectTo
query param but after submitting form I can’t see it.
/routes/login/+page.server.ts
import { redirect } from '@sveltejs/kit';
export const actions = {
login: async ({request, cookies, url}) => {
console.log("url", url)
console.log('request', request)
const formData = await request.formData();
const data = { email: formData.get('email'), password: formData.get('password')}
const response = await fetch("http://localhost:4000/v1/tokens/authentication", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data),
credentials: 'include'
})
if (!response.ok) {
return {success: false};
}
const cookie = response.headers.get('set-cookie');
if (cookie !== null) {
const parts = cookie.split(';');
const token = parts[0].split('=')[1];
const expires = parts.find(p => p.trim().startsWith('Expires'))?.split('=')[1];
cookies.set('auth_token', token, {
path: '/',
httpOnly: true,
sameSite: 'lax',
expires: expires ? new Date(expires) : undefined
});
console.log(url.searchParams)
const redirectTo = url.searchParams.get('redirectTo') || '/'
console.log("redirectTo", redirectTo)
redirect(303, redirectTo);
return {
success: true,
}
} return {
success: false,
}
}
}
/routes/login/+page.svelte
<script lang="ts">
import { enhance } from "$app/forms";
</script>
<h1>Login</h1>
<form method="POST" action="?/login" use:enhance>
<label
>Email
<input name="email" type="email" required />
</label>
<label
>password
<input name="password" type="password" required />
</label>
<button>Log in</button>
</form>
The output of console.log("url", url)
is missing that URL with redirectTo value.
url URL {
href: 'http://localhost:5173/login?/login',
origin: 'http://localhost:5173',
protocol: 'http:',
username: '',
password: '',
host: 'localhost:5173',
hostname: 'localhost',
port: '5173',
pathname: '/login',
search: '?/login',
searchParams: URLSearchParams { '/login' => '' },
hash: ''
}
What Am I doing wrong? I followed logic from docs.
Solution
I managed to solve this problem.
I’ve used use:enhanced
on Login
form, to pass search param redirectTo
from URL into action.
+page.svelte
for login route
<script lang="ts">
import { enhance } from "$app/forms";
import { page } from "$app/stores";
let currentUrl = $page.url.href;
const url: URL = new URL(currentUrl);
const redirectTo: string = url.searchParams.get("redirectTo") ?? "";
</script>
<h1>Login</h1>
<h1>{currentUrl}</h1>
<h2>{redirectTo}</h2>
<form
method="POST"
action="?/login"
use:enhance={({ formData }) => {
formData.append("redirectTo", redirectTo);
}}
>
<label
>Email
<input name="email" type="email" required />
</label>
<label
>password
<input name="password" type="password" required />
</label>
<button>Log in</button>
</form>
+page.server.ts
for login route actions
import { redirect } from '@sveltejs/kit';
export const actions = {
login: async ({request, cookies, url}) => {
const formData = await request.formData();
const data = { email: formData.get('email'), password: formData.get('password'), redirectTo: formData.get("redirectTo")}
console.log("data", data)
const response = await fetch("http://localhost:4000/v1/tokens/authentication", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({email: data.email, password: data.password}),
credentials: 'include'
})
if (!response.ok) {
console.log('err', response)
return {success: false};
}
const cookie = response.headers.get('set-cookie');
if (cookie !== null) {
const parts = cookie.split(';');
const token = parts[0].split('=')[1];
const expires = parts.find(p => p.trim().startsWith('Expires'))?.split('=')[1];
cookies.set('auth_token', token, {
path: '/',
httpOnly: true,
sameSite: 'lax',
expires: expires ? new Date(expires) : undefined
});
console.log(url.searchParams)
const redirectTo = data.redirectTo != null ? data.redirectTo.toString() : "/"
console.log("redirectTo", redirectTo)
redirect(303, redirectTo);
} return {
success: false,
}
},
register: async (event) => {
console.log('elo')
}
}
2
Answers
On the
/routes/login/+page.svelte
page, if a query string parameter namedredirectTo
exists, then you can access it with:In the same file, you need to check if that query string parameter has a value, and then add it as a query string parameter to the
action
attribute of the<form>
. So then when the<form>
is submitted and the server receives that POST request, you can read out the value of that query string parameter usingurl.searchParams.get('redirectTo')
in the/routes/login/+page.server.ts
file.If you want to make things easier, use the default action on the server, and then you can use
action=""
or skip it entirely (both means the POST request will be sent to the same URL the current page is one, including the query string if one is present).You can solve this more cleanly by appending the URL search parameters (which includes your
redirectTo
parameter) in the form action URL.Or, if you want it in formdata instead of the URL:
Regardless of how you solve it, please ensure that you validate the
redirectTo
parameter on the backend to prevent redirecting to phishing sites.https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html#unvalidated-redirects-and-forwards-cheat-sheet