lucia-auth / lucia

Authentication, simple and clean
https://lucia-auth.com
BSD Zero Clause License
9.69k stars 500 forks source link

[Bug]: auth.handleRequest() doesn't work with TRPC mutations in NextJS #523

Closed jonathanharg closed 1 year ago

jonathanharg commented 1 year ago

Package

​@lucia-auth/nextjs

Package version

1.2.1

Describe the bug

I have set up the following context with TRPC which seems to be causing issues (using node middleware).

export const createTRPCContext = (opts: CreateNextContextOptions) => {
  const { req, res } = opts;

  console.log("== TRPC CONTEXT on ", req.url)
  console.log("== We have cookies :", req.headers?.cookie)
  const authRequest = auth.handleRequest(req, res);
  console.log("== Auth request: ", authRequest.getCookie())

  return createInnerTRPCContext({
    authRequest,
    session: null
  });
};

This is the console output for calling a basic mutation which just prints to console. The request cookie is a valid one, I have checked in the database.

== TRPC CONTEXT on  /api/trpc/status.authMutation?batch=1
== We have cookies : auth_session=Lj8MqRAK1lnj5QFcKKrXHkF35LEc4TyMvyNlKK3p
== Auth request:  null
== NOT AUTHED no session
❌ tRPC failed on status.authMutation: UNAUTHORIZED

Curiously TRPC queries do seem to work. Here is the output of a TRPC query.

== TRPC CONTEXT on  /api/trpc/status.authQuery?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%7D%7D
== We have cookies : undefined
== NOT AUTHED no session
❌ tRPC failed on status.authQuery: UNAUTHORIZED

== TRPC CONTEXT on  /api/trpc/status.authQuery?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%7D%7D
== We have cookies : auth_session=G7RxCwhliSCBRAvsjCxdgE06NGpl4YG2p7VBOYkW
== AUTHED
+ Running AUTHED QUERY from  user1
== TRPC CONTEXT on  /api/trpc/status.authQuery,status.authQuery?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%7D%2C%221%22%3A%7B%22json%22%3Anull%7D%7D
== We have cookies : auth_session=G7RxCwhliSCBRAvsjCxdgE06NGpl4YG2p7VBOYkW
== AUTHED
== AUTHED
+ Running AUTHED QUERY from  user1
+ Running AUTHED QUERY from  user1

Here is my fairly bog-standard auth middleware.

const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
  const session = await ctx.authRequest.validateUser();
  if (!session || !session.session || ! session.user) {
    console.log("== NOT AUTHED")
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  console.log("== AUTHED")
  return next({
    ctx: {
      session,
    },
  });
});

System info

@lucia-auth/adapter-prisma 1.0.0 next 13.3.0 lucia-auth 1.2.2 trpc 10.20.0 node v19.9.0

Reproduction

No response

Relevant log output

No response

Additional information

No response

jonathanharg commented 1 year ago

Stepped through the lucia auth source code with a debugger. Looks like its a CSRF issue. Line 99 of request.js

        this.validateUserPromise = new Promise(async (resolve) => {
            try {
                const sessionId = this.auth.parseRequestHeaders(this.context.request);
                if (!sessionId)
                    return resolveNullSession(resolve);
                const { session, user } = await this.auth.validateSessionUser(sessionId);
                this.setSession(session);
                return resolve({ session, user });
            }
            catch {
                return resolveNullSession(resolve);
            }
        });

Here parseRequestHeaders throws a Lucia error because it fails a CSRF check for some reason (still not sure why). This is then caught without an error message and creates a null session. Disabling CSRF temporarily fixes this issue.

Also on a side note, it seems quite unintuitive that you can call getCookie() on an AuthRequest and get a null despite there being valid session cookies.

pilcrowonpaper commented 1 year ago

Does req.url not return the full url (with the protocol and host)? That's likely the issue

jonathanharg commented 1 year ago

Does req.url not return the full url (with the protocol and host)? That's likely the issue

It returns just the path, in this case /api/trpc/status.authMutation?batch=1

pilcrowonpaper commented 1 year ago

Should be resolved with #525

pilcrowonpaper commented 1 year ago

I'll add that this fix will be implemented in 1.3.0, which might take a while to come out (like at most 1 week)