icflorescu / trpc-sveltekit

End-to-end typesafe APIs with tRPC.io for your SvelteKit applications.

Home Page:https://icflorescu.github.io/trpc-sveltekit

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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;
commented

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

I wrote a personal library that just adds SvelteKit's event object to the createContext invocation here

Here's an example of setting headers via the fetch adapter options, and serializing the cookies with SvelteKit's event object

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 !