You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
use:enhance compatibility on form whose action isn't a SvelteKit action #10855
An unhelpful error message presents itself when we have a form with use:enhance posting to an ordinary POST endpoint instead of a SvelteKit action. This occurs because the enhance action tries to parse the fetch response body as a JSON. If that succeeds, it looks for a data property and attempts to parse it as JSON too, which can throw an error. If there is no data property, submitting the form results in nothing happening, which can be confusing.
changed the title [-]`use:enhance` on form whose action isn't a SvelteKit action[/-][+]`use:enhance` compatibility on form whose action isn't a SvelteKit action[/+]on Oct 10, 2023
strange that when i downloaded the code and used it inside playgrounds by replacing the @sveltejs/kit and adapter-node versions with workspace:* , the page got redirected to /somewhere instead of showing the error on same page.
Yes! Can we please go with the option of "Alternatively, we could handle the non-JSON responses better" (mentioned in the StackBlitz link). For me, this actually is somewhat urgent, not a nice-to-have. Here's the background of my use case:
I'm working with an open source authentication solution called SuperTokens, and I'm intentionally doing auth on the backend (not the fronted) because it's more secure. In the authentication model of SuperTokens, they have short-lived Access Tokens that can be refreshed with long-lived Refreshed Tokens when the user makes requests to a server. When supporting users with or without JS in Remix, I'm able to handle this model just fine. In SvelteKit, things break. Here's the specific scenario that breaks...
Note: The /auth/session/refresh route is a regular API route since there's no <form> associated with it.
The Scenario
Imagine a user is on a page that displays new data on the current screen depending on what they submit to the server. This is a perfect use case for use:enhance. Everything will work fine for a non-JS user. And JS users will be able to see the new data on the current screen without a page refresh. However, this page requires authentication.
Where SvelteKit Works
Imagine a non-JS user is on this webpage. They submit a form with an expired Access Token and a valid Refresh Token. (Note that all tokens are expressed as HTTP-only cookies.) Here's what will happen:
The server (with the help of SuperTokens) will see that the Access Token is expired, but a Refresh Token is present. It will redirect with a 307 to /auth/session/refresh. (This is the only route where Refresh Token HTTP-only cookies can be used and/or created, for security reasons. So there's no point in attempting a refresh on any other route.)
The server (with the help of SuperTokens) will see that a valid Refresh Token is present at the /auth/session/refresh route, so a new Access Token and a new Refresh Token (for security reasons) will be returned to the user as HTTP-only cookies. It will redirect back the the original POST route with a 307.
Because the non-JS users was redirected back to the original page with a 307, the POST will continue as normal. Since they are now authenticated with a valid Access Token, they will get to see the new data on the screen without having to inconveniently resubmit their form data.
Where SvelteKit Fails
Now imagine a JS user is on this webpage. They submit a form with an expired Access Token and an expired Refresh Token. Here's what will happen:
The server (with the help of SuperTokens) will see that the Access Token is expired, but a Refresh Token is present. It will redirect with a 307 to /auth/session/refresh.
The server (with the help of SuperTokens) will see that a valid Refresh Token is not present at the /auth/session/refresh route. So all of the user's Auth Cookies will be deleted by the server. Additionally, the user will be redirected to the /login page with a 303. (By redirecting with a 303, there's no risk of inappropriately POST-ing to the /login route. This also removes the unlikely-but-possible scenario where someone could accidentally re-authenticate as a different user during the redirect because of the values in the original form POST.)
Because the JS user was redirected to the the /login page with a 303, the the page's HTML is returned.
SvelteKit errors out (as shown in the StackBlitz link).
I've run into an issue today with redirect form actions and setting headers. I thought form actions would work like post server endpoint and returning a response with redirect settings would redirect but no get an error because form actions only expects data to return and using set headers redirect doesn't keep headers.
So it would be nice even with actions to be able to return redirect responses that are not data to or from form.
I've run into an issue today with redirect form actions and setting headers. I thought form actions would work like post server endpoint and returning a response with redirect settings would redirect but no get an error because form actions only expects data to return and using set headers redirect doesn't keep headers.
So it would be nice even with actions to be able to return redirect responses that are not data to or from form.
Do you mind elaborating on this with a code example?
I've run into an issue today with redirect form actions and setting headers. I thought form actions would work like post server endpoint and returning a response with redirect settings would redirect but no get an error because form actions only expects data to return and using set headers redirect doesn't keep headers.
So it would be nice even with actions to be able to return redirect responses that are not data to or from form.
Do you mind elaborating on this with a code example?
@ITenthusiasm similar, I'm using form actions with use enhance and also not working if I return a response that doesn't have data prop or is sveltekit redirect(), I'm attempting to have redirect and settings authorization header.
I know this has been marked as non-urgent, but I still consider this a pretty valuable feature. It's more or less necessary for an authenticated application as I described earlier.
Would it be possible for SvelteKit to simply read the response type (or some custom headers) to see if it should navigate to the provided URL instead of trying to unwrap some JSON? I believe Remix does something like this.
Note: The /auth/session/refresh route is a regular API route since there's no
associated with it.
Wouldn't it work if you implemented it as a page action instead of using +server?
I've run into an issue today with redirect form actions and setting headers. I thought form actions would work like post server endpoint and returning a response with redirect settings would redirect but no get an error because form actions only expects data to return and using set headers redirect doesn't keep headers.
So it would be nice even with actions to be able to return redirect responses that are not data to or from form.
The only thing you should return from a SvelteKit form action is a plain object or optionally using the fail helper. If you want to redirect, you should always use the redirect helper.
Would it be possible for SvelteKit to simply read the response type (or some custom headers) to see if it should navigate to the provided URL instead of trying to unwrap some JSON? I believe Remix does something like this.
It's tricky. A redirect causes the fetch to respond with the result of fetching the location header's URL rather than the original endpoint so we can't just check the status code. We could try checking the request URL against the action URL and if it's different, we've probably been redirected, but it feels somewhat hacky:
Sorry @eltigerchino. I missed these. Feel free to ping me when you quote me in any comments.
Wouldn't it work if you implemented it as a page action instead of using +server?
Could you describe what you have in mind for this to work? My initial assumption is that this would not work.
If the context helps, the server drives the authentication (to improve the app's security, naturally). So the hooks.server.ts file has to check all requests and appropriately handle redirects for user session refreshing when needed. This needs to work when users visit pages or submit forms. It also needs to work whether or not JS is enabled. And ideally, there should be no unnecessary code duplication.
There is no page to visit in the case of session refreshing. So having an accompanying page.svelte file (which I'm assuming would be mandatory for page.server.ts) also does not feel appropriate here.
It's tricky. A redirect causes the fetch to respond with the result of fetching the location header's URL rather than the original endpoint so we can't just check the status code. We could try checking the request URL against the action URL and if it's different, we've probably been redirected, but it feels somewhat hacky
Yeah I'm aware of fetch's unfortunate behavior of automatically following redirects. And it doesn't seem like this logic can be circumvented without also removing the information needed to follow the redirect manually.
Are you saying this would be done client-side? If so, I unfortunately don't think this would work. If you're redirected to a page not related to authentication, then this works fine. But if you undergo the steps described earlier (for session refreshing), then response.url === action.toString() would return true. The only way that I'm aware you can handle this is by having the server send back custom headers (and a 2XX status code) that tell the client-side JavaScript to manually follow the redirect. Both Remix and Next.js do this. When I scoured the whatwg GitHub issues for alternative solutions, the best thing that was available seemed to be working with custom Headers.
Did you see the example repo I linked to in the PR that was recently merged? (Remix/Next might have some better code than me, though. 😬)
As I mentioned in the related PR, many people have been using the actionResult helper, but it won't work in SvelteKit 2.16+, so I think that PR has to be reverted, as it is a breaking change that has already been reported on the Superforms Discord.
Activity
[-]`use:enhance` on form whose action isn't a SvelteKit action[/-][+]`use:enhance` compatibility on form whose action isn't a SvelteKit action[/+]Rishab49 commentedon Oct 11, 2023
strange that when i downloaded the code and used it inside
playgroundsby replacing the@sveltejs/kitandadapter-nodeversions withworkspace:*, the page got redirected to/somewhereinstead of showing the error on same page.ITenthusiasm commentedon Jan 10, 2024
Yes! Can we please go with the option of "Alternatively, we could handle the non-JSON responses better" (mentioned in the StackBlitz link). For me, this actually is somewhat urgent, not a nice-to-have. Here's the background of my use case:
I'm working with an open source authentication solution called
SuperTokens, and I'm intentionally doing auth on the backend (not the fronted) because it's more secure. In the authentication model of SuperTokens, they have short-lived Access Tokens that can be refreshed with long-lived Refreshed Tokens when the user makes requests to a server. When supporting users with or without JS in Remix, I'm able to handle this model just fine. In SvelteKit, things break. Here's the specific scenario that breaks...Note: The
/auth/session/refreshroute is a regular API route since there's no<form>associated with it.The Scenario
Imagine a user is on a page that displays new data on the current screen depending on what they submit to the server. This is a perfect use case for
use:enhance. Everything will work fine for a non-JS user. And JS users will be able to see the new data on the current screen without a page refresh. However, this page requires authentication.Where SvelteKit Works
Imagine a non-JS user is on this webpage. They submit a form with an expired Access Token and a valid Refresh Token. (Note that all tokens are expressed as HTTP-only cookies.) Here's what will happen:
307to/auth/session/refresh. (This is the only route where Refresh Token HTTP-only cookies can be used and/or created, for security reasons. So there's no point in attempting a refresh on any other route.)/auth/session/refreshroute, so a new Access Token and a new Refresh Token (for security reasons) will be returned to the user as HTTP-only cookies. It will redirect back the the original POST route with a307.Where SvelteKit Fails
Now imagine a JS user is on this webpage. They submit a form with an expired Access Token and an expired Refresh Token. Here's what will happen:
307to/auth/session/refresh./auth/session/refreshroute. So all of the user's Auth Cookies will be deleted by the server. Additionally, the user will be redirected to the/loginpage with a303. (By redirecting with a303, there's no risk of inappropriately POST-ing to the/loginroute. This also removes the unlikely-but-possible scenario where someone could accidentally re-authenticate as a different user during the redirect because of the values in the original form POST.)/loginpage with a 303, the the page's HTML is returned.JonathonRP commentedon Feb 21, 2024
I've run into an issue today with redirect form actions and setting headers. I thought form actions would work like post server endpoint and returning a response with redirect settings would redirect but no get an error because form actions only expects data to return and using set headers redirect doesn't keep headers.
So it would be nice even with actions to be able to return redirect responses that are not data to or from form.
teemingc commentedon Feb 21, 2024
Do you mind elaborating on this with a code example?
JonathonRP commentedon Feb 21, 2024
I can make a repo later and share, no problem.
ITenthusiasm commentedon Feb 21, 2024
@JonathonRP is the issue that you're talking about similar to what's shown in the StackBlitz link in the OP? Or is it something different?
JonathonRP commentedon Feb 21, 2024
@ITenthusiasm similar, I'm using form actions with use enhance and also not working if I return a response that doesn't have data prop or is sveltekit redirect(), I'm attempting to have redirect and settings authorization header.
JonathonRP commentedon Feb 21, 2024
@eltigerchino, repo -
https://stackblitz.com/~/github.com/JonathonRP/sveltekit-authjs-redirect_demo?initialPath=/linkBuxferAccount
check /src/routes/linkBuxferAccount/+page.server.ts
ITenthusiasm commentedon Oct 31, 2024
I know this has been marked as non-urgent, but I still consider this a pretty valuable feature. It's more or less necessary for an authenticated application as I described earlier.
Would it be possible for SvelteKit to simply read the response type (or some custom headers) to see if it should navigate to the provided URL instead of trying to unwrap some JSON? I believe Remix does something like this.
feat: Get Playwright tests working with AND without JavaScript enabled
Mardoxx commentedon Dec 18, 2024
What a pain in the proverbial this has been today....
+page.sveltelogout/+server.jsuse:enhance should only be used with SvelteKit actionsshould be in big red text, not in some year old github issue.Another oddity, I shall make a repro for this, if you have
+page.sveltelogin/blah/+server.jsWhen you click the link you get a nice little
Which comes from here presumably because the route does not exist /as a page/
Mardoxx commentedon Dec 18, 2024
https://stackblitz.com/edit/sveltejs-kit-template-default-ipxflmtx
Here's an up to date repro of both
teemingc commentedon Dec 26, 2024
Wouldn't it work if you implemented it as a page action instead of using
+server?The only thing you should return from a SvelteKit form action is a plain object or optionally using the
failhelper. If you want to redirect, you should always use the redirect helper.See https://svelte.dev/docs/kit/form-actions for more information.
You can still set the headers using
event.setHeadersbefore you redirect. For example:It's tricky. A redirect causes the fetch to respond with the result of fetching the location header's URL rather than the original endpoint so we can't just check the status code. We could try checking the request URL against the action URL and if it's different, we've probably been redirected, but it feels somewhat hacky:
We'd need to add this check in between the fetch and the deserialise:
kit/packages/kit/src/runtime/app/forms.js
Lines 190 to 198 in c0ca073
chore: error during development when using `use:enhance` with `+serve…
ITenthusiasm commentedon Jan 15, 2025
Sorry @eltigerchino. I missed these. Feel free to ping me when you quote me in any comments.
Could you describe what you have in mind for this to work? My initial assumption is that this would not work.
If the context helps, the server drives the authentication (to improve the app's security, naturally). So the
hooks.server.tsfile has to check all requests and appropriately handle redirects for user session refreshing when needed. This needs to work when users visit pages or submit forms. It also needs to work whether or not JS is enabled. And ideally, there should be no unnecessary code duplication.There is no page to visit in the case of session refreshing. So having an accompanying
page.sveltefile (which I'm assuming would be mandatory forpage.server.ts) also does not feel appropriate here.Yeah I'm aware of
fetch's unfortunate behavior of automatically following redirects. And it doesn't seem like this logic can be circumvented without also removing the information needed to follow the redirect manually.Are you saying this would be done client-side? If so, I unfortunately don't think this would work. If you're redirected to a page not related to authentication, then this works fine. But if you undergo the steps described earlier (for session refreshing), then
response.url === action.toString()would returntrue. The only way that I'm aware you can handle this is by having the server send back custom headers (and a2XXstatus code) that tell the client-side JavaScript to manually follow the redirect. Both Remix and Next.js do this. When I scoured thewhatwgGitHub issues for alternative solutions, the best thing that was available seemed to be working with custom Headers.Did you see the example repo I linked to in the PR that was recently merged? (Remix/Next might have some better code than me, though. 😬)
RajeshPandey057 commentedon Jan 28, 2025
I think this helper from superform solves the issue.
SvelteKit should have it built-in.
Link: https://superforms.rocks/api#actionresulttype-data-options--status
ciscoheat commentedon Jan 29, 2025
As I mentioned in the related PR, many people have been using the
actionResulthelper, but it won't work in SvelteKit 2.16+, so I think that PR has to be reverted, as it is a breaking change that has already been reported on the Superforms Discord.fix: render the nearest error page when a form submission receives a …