supabase: `supabase.auth.api.getUserByCookie()` doesn't work in Next.JS server-side environment

Bug report

Describe the bug

supabase.auth.api.getUserByCookie() doesn’t work in Next.JS server-side environment (_middleware.ts)

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Create a supabase project
  2. Create a Next.JS project
  3. Create

Add following code to _middleware.ts

export async function middleware(req: NextRequest, ev: NextFetchEvent) {
    let supabase = createClient(...);
    let user = await supabase.auth.api.getUserByCookie(req);
    console.log('Auth ', user)
}
  1. See logs
Auth  {
  user: null,
  data: null,
  error: ReferenceError: XMLHttpRequest is not defined
      at eval (webpack-internal:///./node_modules/cross-fetch/dist/browser-ponyfill.js?d2fb:462:17)
      at new Promise (<anonymous>)
      at fetch (webpack-internal:///./node_modules/cross-fetch/dist/browser-ponyfill.js?d2fb:455:12)
      at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js?85f4:44:63)
      at new Promise (<anonymous>)
      at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js?85f4:43:16)
      at Generator.next (<anonymous>)
      at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js?85f4:16:71)
      at new Promise (<anonymous>)
      at __awaiter (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js?85f4:12:12)
}

Expected behavior

getUserByCookie() should either work (use fetch() internally instead of XMLHttpRequest), OR message should be more clear (check the presence of XMLHttpRequest in getUserByCookie() and throw Sorry, you shouldn't call this method in server side environment

System information

Next version is 12.0.1

Additional context

Add any other context about the problem here.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 6
  • Comments: 43 (18 by maintainers)

Most upvoted comments

FYI, this workaroung does the job:

    let supabase = createClient(supabaseUrl, supabaseKey);
    let token = req.cookies['sb:token'];
    if (!token) {
        return 
    }
    let authRequestResult = await fetch(`${supabaseUrl}/auth/v1/user`, {
        headers: {
            'Authorization': `Bearer ${token}`,
            'APIKey': supabaseKey
        }
    });

I was suffering from the same issue server side as has been mentioned a couple times here: getUserByCookie() returns null even though the client returns a user as expected. I’m using a magic link as my login method and what I noticed was that my session cookie wasn’t set.

I found this post on dev.to that solves my problem. OP uses supabase.auth.onAuthStateChange() and supabase.auth.api.setAuthCookie() to manually set the supabase cookie client side. Once that’s happened the server correctly fetches the user.

The following is added to the _app.tsx:

useEffect(() => {
    const { data: authListener } = supabase.auth.onAuthStateChange(
      (event, session) => {
        if (event === "SIGNED_IN") {
          updateSupabaseCookie(event, session);
        }
      }
    );

    return () => {
      authListener?.unsubscribe();
    };
  });

  async function updateSupabaseCookie(event: string, session: Session | null) {
    await fetch("/api/auth", {
      method: "POST",
      headers: new Headers({ "Content-Type": "application/json" }),
      credentials: "same-origin",
      body: JSON.stringify({ event, session }),
    });
  }

The following is added to /api/auth.ts:

const handler = (req: NextApiRequest, res: NextApiResponse) => {
    supabase.auth.api.setAuthCookie(req, res);
};

The supabase.auth.api.XXX methods aren’t documented anywhere. As far as I can tell at least. It seems to do the trick, though. Not sure if this is the desired implementation, would love to get feedback on this.

after more thorough testing I found the error was persisiting. sorry about the false posititve.

I added another commit to the PR, based on Thor’s link to the cross-fetch package - in particular, this comment. I can confirm that this works in Next.js middleware, on a local environment.

I’ll follow up on the linked package and see if that change is OK to merge

@saltcod have you set server cookie from client-side code? We published a post today, with a step-by-step guide on how to make next.js middleware work with Supabase auth: https://jitsu.com/blog/supabase-nextjs-middleware

Thanks for the feedback @IHIutch, I understand your frustration, a bunch of things are in flux atm which creates this less than ideal experience 😞

  1. Am I required/expected to use @supabase/ui Auth when handling auth when using Supabase? That feels odd to me.

Not at all a requirement. The Auth UI Element is solely meant to make things faster/easier by giving you a prebuilt UI, but not at all a requirement. You can always built your custom UI if you prefer.

  1. Using a community package to circumvent an issue with Supabase and NextJS doesn’t feel like a “Closed” worthy resolution, imho

It’s technically not an issue, or at least not a bug, since you very well can set the cookies yourself if you want. It’s just not the easiest thing to do. So having some convenience libraries to help you with this is our philosophy here.

Further, having to install 2 additional packages feels like a weird solution. Temporary, sure, but as a permanent solution?

Again, all of these are optional and are meant as a convenience. the @supabase/auth-helpers are non-ui helpers and the @supabase/auth-ui (https://github.com/supabase-community/auth-ui) is where the Auth UI Elements will be moved to in the future.

Does it actually solve the _middleware issue

It does, have a look at the example here: https://github.com/supabase-community/auth-helpers/blob/main/examples/nextjs/pages/middleware-protected/_middleware.ts

Sorry, we’re working on improving this experience, just a lot of moving parts right now. We really appreciate your feedback and will use it to improve things!

I’m a little confused by this suggestion because the documented example uses the @supabase/ui Auth which I’ve found tends to obfuscate what is actually happening when authenticating. I appreciate that this community package appears to solve some issues (Does it actually solve the _middleware issue? I’m not seeing that confirmed anywhere) but I still feel a bit in the dark on some other things.

My 2 cents:

  1. Am I required/expected to use @supabase/ui Auth when handling auth when using Supabase? That feels odd to me.
  2. Using a community package to circumvent an issue with Supabase and NextJS doesn’t feel like a “Closed” worthy resolution, imho
    • Further, having to install 2 additional packages feels like a weird solution. Temporary, sure, but as a permanent solution?
  3. In my app specifically, I use a /api/auth/register route to create a user in my DB and sign them in. It’s unclear to me how I’d do that using the community package

I don’t mean to offend, I’m an intermediate developer at best, I’m just trying to understand and leverage the incredible framework that is Supabase. But as such, I’m really struggling to understand the solution here. My app works fine, with everything except getUserByCookie when using _middleware, so this feels like an additional piece of complexity to understand and keep up with as things change.

If anyone can shed some light, I’m eager to understand better, thanks.

Closing this out, as mentioned, please use https://github.com/supabase-community/supabase-auth-helpers/blob/main/src/nextjs/README.md going forward and open issues there if you encounter any. Thanks 💚

@yudyananda @kiwicopple I noticed this as well. As a workaround used 2 instances of SupabaseClient: one for client side (without custom fetch) and one for server side with this PR applied.

@kiwicopple should that already be working with Next.js’s fetch?

// src/supabaseClient.ts
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL as string;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string;

const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  fetch: fetch,
});

export default supabase;

I have this simple configuration, but I still get an error that refers to cross-fetch when checking the auth in a middleware function.

{
    token: null,
    user: null,
    data: null,
    error: ReferenceError: XMLHttpRequest is not defined
        at eval (webpack-internal:///./node_modules/cross-fetch/dist/browser-ponyfill.js?d2fb:462:17)
        at new Promise (<anonymous>)
        at fetch (webpack-internal:///./node_modules/cross-fetch/dist/browser-ponyfill.js?d2fb:455:12)
        at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js?85f4:44:13)
        at new Promise (<anonymous>)
        at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js?85f4:43:16)
        at Generator.next (<anonymous>)
        at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js?85f4:16:71)
        at new Promise (<anonymous>)
        at __awaiter (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js?85f4:12:12)
}

I have the latest version of supabase (1.28.0).

I haven’t deployed the app yet, so I believe that Cloudflare has nothing do to with it. It’s reproducible with local next dev. I’ll try to make an isolated example and maybe suggest a patch

If you prefer not to use the helpers, you would probably want to follow the same process in the helpers code. eg: set the Auth token inside a cookie, and then extract the value of the cookie inside getServerSideProps - the idea of the helpers isn’t to do anything unique, it’s simply to provide a library which will follow the same steps that you’d do yourself

Thanks @thorwebdev, I appreciate the response. Genuinely, Supabase has such a great community and I appreciate the conversation.

supabase.auth.api.getUserByCookie(req) doesn’t work in getServerSideProps (only in production for me either).

I’ve tried so many things, including what the OP wrote in this article here.

Any updates on this?

Very weird, @kiwicopple’s PR didn’t work for me as well. I tried to replace custom fetch with:

  return createClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_KEY, {
    // @ts-ignore
    fetch: (...args) => {
      console.log("Args", args)
    },
  })

And I’m still seeing same error (meaning custom fetch wasn’t passed to gotrue-js):

Authorization error, redirecting to login page ReferenceError: XMLHttpRequest is not defined
    at eval (webpack-internal:///./node_modules/cross-fetch/dist/browser-ponyfill.js:462:17)
    at new Promise (<anonymous>)
    at fetch (webpack-internal:///./node_modules/cross-fetch/dist/browser-ponyfill.js:455:12)
    at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js:44:13)
    at new Promise (<anonymous>)
    at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js:43:16)
    at Generator.next (<anonymous>)
    at eval (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js:16:71)
    at new Promise (<anonymous>)
    at __awaiter (webpack-internal:///./node_modules/@supabase/gotrue-js/dist/module/lib/fetch.js:12:12)

(I did rm -rf node_modules && rm -rf yarn.lock && yarn install to make sure all packages are uptodate)