iron-session icon indicating copy to clipboard operation
iron-session copied to clipboard

Implementing iron-session with TRPC

Open Sealis04 opened this issue 1 year ago • 9 comments

So I'm having issues trying to slam iron-session with TRPC. I'm using create-t3-app to start with and before v8, I just used to define (or redefine) IronSession when creating my TRPC context. I assumed V8 was pretty much the same but for some reason, running session.save() inside my procedures doesn't save anything. I tried creating a separate route from trpc to try and see if I can save runs properly, and yes it works. And yes, I can access the session values inside my procedures (but save doesn't work). Any ideas why?

My TRPC Context

export const createTRPCContext = async (opts: { headers: Headers }) => {
  const session = await getIronSession<{ user: string }>(cookies(), sessionOptions);
  return {
    db,
    ...opts,
    session,
  };
};

My procedure (from create-t3 boilerplate)

create: publicProcedure
    .input(z.object({ name: z.string().min(1) }))
    .mutation(async ({ ctx, input }) => {
      // simulate a slow db call
      await new Promise((resolve) => setTimeout(resolve, 1000));
      //This prints out the session.user variable I have set in my custom route
      console.log('old session',ctx.session);
      //this also works
      ctx.session.user = 'new user';
      // This doesn't work
      await ctx.session.save();
      return true;
    })

Edit* To add, I already tried calling getIronSession inside the procedure instead. Still didn't work.

Sealis04 avatar Nov 24 '23 04:11 Sealis04

@Sealis04 Thank you, would you be able to create a very simple GitHub repository where I can npm install && npm run dev and see the issue myself?

Are you running this locally? HTTP or HTTPS? What the sessionOptions in your case (hide the password 👍 )

vvo avatar Nov 24 '23 08:11 vvo

I tried creating a separate route from trpc to try and see if I can save runs properly, and yes it works.

This is reassuring at least, let's see why in some other cases it doesn't.

vvo avatar Nov 24 '23 08:11 vvo

@Sealis04 Thank you, would you be able to create a very simple GitHub repository where I can npm install && npm run dev and see the issue myself?

Are you running this locally? HTTP or HTTPS? What the sessionOptions in your case (hide the password 👍 )

https://github.com/Sealis04/testironsession

Made two ways to change the session here, one is just the submit in home which goes to the procedure made with TRPC, the one that doesn't save the session. The other is with a separate route from TRPC, just visit /setSession. Also, made /getSession to check if the session values change (which doesn't).

This was with HTTP, as for sessionOptions it's just these ones

export const sessionOptions = {
    password: my_super_secret_cookie_password,
    cookieName: "Random Cookie Name",
    cookieOptions: {
      maxAge: undefined,
      secure: false,
    },
}

Sealis04 avatar Nov 25 '23 01:11 Sealis04

I am having the same problem, following

yifever avatar Nov 29 '23 14:11 yifever

Was there any solution to this? I am attempting to run save on the session in a tRPC mutation, and when I read the session from another tRPC mutation after I call save, the session object remains unchanged.

marinofranz avatar Dec 31 '23 00:12 marinofranz

I have a working example of this. I have a special loginProcedure that is used on procedures that write cookies (login, signup, otp verify etc.)

export const loginProcedure = t.procedure.use(
  t.middleware(async ({ ctx, next }) => {
    const result = await next({ ctx });
    const resultCtx = "ctx" in result ? (result.ctx as Context) : undefined;
    if (resultCtx?.sessionUser) {
      // resultCtx is not the ctx passed to `responseMeta` where we need
      // `_session` to set the cookie. We would do it all in `responseMeta` but
      // it is not and awaitable unfortunately.
      ctx._session = await sealData(
        {
          email: resultCtx.sessionUser.email,
          id: resultCtx.sessionUser.id,
        } satisfies SessionUser,
        { password: ctx.env.SECRET, ttl: 60 * 60 * 24 * 365 }, // 1 year
      );
    }
    return result;
  }),
);

Then my fetchRequestHandler has this responseMeta:

responseMeta({ ctx, paths, errors }) {
  const allOk = errors.length === 0;
  if (
    allOk &&
    ctx?._session &&
    paths?.find((path) =>
      ["auth.setPassword", "auth.login", "auth.verifyOtpCode"].includes(
        path,
      ),
    )
  ) {
    return {
      headers: {
        "set-cookie": `__session=${
          ctx._session
        }; Max-Age=2592000; SameSite=Strict; Path=/; ${
          ENV === "development" ? "" : "Secure; "
        }HttpOnly`,
      },
    };
  }
  return {};
}

Not the most elegant way to set cookies, but it works.

jokull avatar Mar 11 '24 08:03 jokull

So I'm having issues trying to slam iron-session with TRPC. I'm using create-t3-app to start with and before v8, I just used to define (or redefine) IronSession when creating my TRPC context. I assumed V8 was pretty much the same but for some reason, running session.save() inside my procedures doesn't save anything. I tried creating a separate route from trpc to try and see if I can save runs properly, and yes it works. And yes, I can access the session values inside my procedures (but save doesn't work). Any ideas why?

My TRPC Context

export const createTRPCContext = async (opts: { headers: Headers }) => {
  const session = await getIronSession<{ user: string }>(cookies(), sessionOptions);
  return {
    db,
    ...opts,
    session,
  };
};

My procedure (from create-t3 boilerplate)

create: publicProcedure
    .input(z.object({ name: z.string().min(1) }))
    .mutation(async ({ ctx, input }) => {
      // simulate a slow db call
      await new Promise((resolve) => setTimeout(resolve, 1000));
      //This prints out the session.user variable I have set in my custom route
      console.log('old session',ctx.session);
      //this also works
      ctx.session.user = 'new user';
      // This doesn't work
      await ctx.session.save();
      return true;
    })

Edit* To add, I already tried calling getIronSession inside the procedure instead. Still didn't work.

Hey to whoever is having this problem, simply add this to headers in httpLink or httpBatchLink and you should be good

Gist here

austinwoon avatar Apr 12 '24 03:04 austinwoon

I am facing the exact same issue.

iStorry avatar Apr 17 '24 04:04 iStorry

for anyone that still have the problem, maybe you can try my solution.

after trying so many ways, and of course bcoz i'm too lazy to understand the TRPC concept, at the end i choose to use Server Action for doing mutation the session (save and destroy)

it is very easy to implement rather than trying to understand the whole TRPC concept and React Query (if you use t3 like me).

the cons is you have to manually setup all the necessary React Query stuff to get the benefit of using react query itself like onSuccess, onError, isPending, etc.

or if you're beginner in React Query and you're in hurry just manually setup hooks for handing state or callback after success and error.

you can watch this video from Lama Dev, if you don't know how to Server Action. https://www.youtube.com/watch?v=p_FiVGxyksI&t=967s

volfadar avatar Apr 21 '24 21:04 volfadar