Unable to delete cookie through client
L-Mario564 opened this issue · comments
Describe the bug
I'm unable to delete a cookie if a procedure is called through the tRPC client (works fine in callers though).
To Reproduce
// lib/trpc/router.ts
import type { RequestEvent } from '@sveltejs/kit';
import type { inferAsyncReturnType } from '@trpc/server';
export async function createContext({ cookies }: RequestEvent) {
return { cookies };
}
export type Context = inferAsyncReturnType<typeof createContext>;
// lib/trpc/index.ts
import { initTRPC } from '@trpc/server';
import type { Context } from '$trpc/context';
export const t = initTRPC.context<Context>().create();
// lib/trpc/routes/index.ts
import { t } from '$trpc';
const cookiesOptions = {
path: '/'
};
export const authRouter = t.router({
logout: t.procedure.query(({ ctx }) => {
ctx.cookies.delete('session', cookiesOptions);
}),
});
// lib/trpc/router.ts
import { authRouter } from './routes';
import { t } from '$trpc';
export const router = t.router({
auth: authRouter
});
export type Router = typeof router;
// hooks.server.ts
import { createContext } from '$trpc/context';
import { router } from '$trpc/router';
import { createTRPCHandle } from 'trpc-sveltekit';
export const handle = createTRPCHandle({ router, createContext });
// routes/+layout.svelte
<script lang='ts'>
import { trpc } from '$trpc/client';
import { page } from '$app/stores';
async function onLogout() {
await trpc($page).auth.logout.query();
}
</script>
<button on:click={onLogout}>
Logout
</button>
Expected behavior
The cookie gets deleted upon clicking the button in the layout file.
Desktop
- OS: Windows 10
- Browser: Opera GX
- Version: Latest
I remember facing similar issues when I first tried to implement a tRPC-only auth workflow at some point last year.
By that time, there were some cookie-API related-issues in SvelteKit (i.e. sveltejs/kit#6650), but I trust they are solved by now.
Suggestion: see if calling invalidateAll
from $app/navigation
after deleting the cookies helps. Have a look at this file in the bookstall example for reference: https://github.com/icflorescu/trpc-sveltekit/blob/main/examples/bookstall/src/lib/components/Header.svelte
Please drop a line here if the above suggestion solves the issue for you or if you find other possible workaround, so other people can benefit from it. Thanks!
The invalidateAll
function didn't seem to do anything, for now what I've done is to define an API route to logout the user using a caller.
// routes/+layout.svelte
<script lang='ts'>
function onLogout() {
goto('/auth/logout');
}
</script>
<button on:click={onLogout}>
Logout
</button>
// routes/auth/logout/+server.ts
import { getCaller } from '$trpc/caller'; // Helper function to create caller
import { redirect } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET = (async (event) => {
const caller = await getCaller(event);
await caller.auth.logout();
throw redirect(302, '/');
}) satisfies RequestHandler;
It's because event.cookies
only works in +hooks.server
files when you eventually call resolve(event)
, i.e. using SvelteKit's intended method of request resolution. When creating custom responses, i.e. here, you have to set your own cookie headers.
One thing I did in the past was save all cookies into a event.locals.cookies
array inside of tRPC handlers. Then when creating the response (at the linked location above), I'd iterate over event.locals.cookies
and manually append a new set-cookie
header for each one.
It kinda just feels weird to me tho; the reason I designed the strategy like that was because I couldn't find a way to access response headers while inside of a handler during resolveHTTPResponse
. I also tried alternatives like responseMeta
, but I wasn't able to make any progress there. If there's a non-trivial way to do set headers like this, definitely consider that over using event.locals
.
Alternatively, it seems that using the fetchRequestHandler
provides access to the headers via opts.resHeaders
.
Here's documentation on the default options available to handlers via context
And just to tie up any loose ends, this is the actual createContext
function that I pass to my library (it receives both the opts and event, then puts it into a single ctx
object for all the handlers)
I hope this helped in any way !