supabase / auth-helpers

A collection of framework specific Auth utilities for working with Supabase.
https://supabase.github.io/auth-helpers/
MIT License
902 stars 237 forks source link

invalid request: both auth code and code verifier should be non-empty #545

Closed probablykasper closed 1 year ago

probablykasper commented 1 year ago

Bug report

Describe the bug

When calling auth.exchangeCodeForSession(code) this error is thrown:

AuthApiError: invalid request: both auth code and code verifier should be non-empty
    at /Users/k/git/myrepo/node_modules/@supabase/gotrue-js/dist/main/lib/fetch.js:41:20
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  __isAuthError: true,
  status: 400

To Reproduce

Login callback handler:

const code = event.url.searchParams.get('code')
if (typeof code === 'string') {
  const { data, error } = await event.locals.supabase.auth.exchangeCodeForSession(code)
}

This is how I'm starting the login:

const supabaseAnonPkce = createClient<Database>(
    PUBLIC_SUPABASE_URL,
    PUBLIC_SUPABASE_ANON_KEY,
    {
        auth: {
            flowType: 'pkce',
        },
    }
)
const { error } = await supabaseAnonPkce.auth.signInWithOtp({
    email,
})

Expected behavior

Screenshots

If applicable, add screenshots to help explain your problem.

System information

imownbey commented 1 year ago

I am also seeing this with using google oauth. Our initiation of the auth flow is on the client and I am seeing this error on the server, not sure if that is important.

AlexIsMaking commented 1 year ago

I ran into this error earlier and the issue was caused by not passing the response from the sign in process to the callback request and using it to create the client, as shown in the docs.

So from:

// Start sign in with one-time password
  const { error } = await supabase.auth.signInWithOtp({
    email: 'foo@example.com',
    options: {
      emailRedirectTo: 'http://localhost:3000/api/auth/callback',
    },
  })

To:

// api/auth/callback
import { NextApiRequest, NextApiResponse } from 'next'
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // Create authenticated Supabase Client
  const supabase = createServerSupabaseClient(
    { req, res },
    {
      supabaseUrl: SUPABASE_URL,
      supabaseKey: SUPABASE_ANON_KEY,
    }
  )
imownbey commented 1 year ago

I am running into this with createRouteHandlerSupabaseClient (which is the same as createServerComponentSupabaseClient) that only takes headers and cookies, maybe something is lacking there?

KrisBraun commented 1 year ago

I am also seeing this with using google oauth. Our initiation of the auth flow is on the client and I am seeing this error on the server, not sure if that is important.

Seeing exactly the same thing (client-initiated Google PKCE failing on exchangeCodeForSession on the server). The code returned from Google looks fine and is being passed to await supabase.auth.exchangeCodeForSession(code), so this must have something to do with the code verifier.

silentworks commented 1 year ago

The issue in the code seems to be the use of two different clients. You are using the auth helpers along with the normal supabase client, this won't work and will result in the error you are getting. We have a branch of the auth-helpers with PKCE support that you should use until we make the final release. You can install this using:

npm install @supabase/auth-helpers-sveltekit@next

You shouldn't mix the auth-helpers client with a normal supabase-js client unless you are planning to do things with the service_role key. There is an example project using these at the moment in this repo https://github.com/supabase-community/supabase-by-example/tree/next/magic-link/sveltekit. We will be releasing the next version of the auth-helpers with full support soon. Currently we are just finalising the documentation for release.

KrisBraun commented 1 year ago

Yes, this was exactly my issue. Using 0.2.0-next.3 I've now moved on to a new error: AuthApiError: invalid flow state, no valid flow state found

silentworks commented 1 year ago

@KrisBraun do you have a repo somewhere that I can check out the code? I'm still thinking there is a mix going on with the supabase-js client libraries.

KrisBraun commented 1 year ago

@silentworks To avoid confusing the original issue here, I've created https://github.com/supabase/auth-helpers/issues/549. The issue seems to be related to exchangeCodeForSession expecting user_id to be set in the flow state. It's unclear to me how that would be, since this is the initial login, so no user has been created yet.

probablykasper commented 1 year ago

@silentworks I was mixing clients and what you suggested did fix it for me locally, but I'm still getting the error on Vercel unfortunately

silentworks commented 1 year ago

@probablykasper thats likely due to either incorrect version being installed on Vercel or your code is cached on there somehow. Try deploying the fixed version to a new Vercel setup and report back here if there are still errors.

probablykasper commented 1 year ago

That was my suspicion as well, but no luck. The lockfile does use 0.10.0-next.3 - tried redeploying without build cache, pushing new commits and visiting the commit-specific deployment domain doesn't work

silentworks commented 1 year ago

@probablykasper I'm going to make a big ask of you, but can you create a minimal reproducible example repo that I can take a look at please?

probablykasper commented 1 year ago

I tried reproducing it with the new 0.10.0 version, and the issue wasn't there. I'll let you know once I figure out more!

EdmondChuiHW commented 1 year ago

I played with this in the debugger and found a reliable repro. The blog post also confirms the following flow:

  1. User clicks "sign in with magic link", Supabase signInWithOTP gets called with a redirect, usually the project's /auth/callback
  2. Supabase generates a code verifier, saves it in a cookie called sb-<project ID>-auth-token-code-verifier
  3. User clicks the magic link in the email, gets redirected to /auth/callback with the code query param
  4. Supabase checks the code from the query param against the code verifier in the cookie
  5. If the user clicks the link from another client, the code verifier cookie won't be found and causes the error thrown here

"Another client" cold mean an incognito window, another browser, another device, etc. E.g. someone initiated sign in from the browser on a desktop, then open the magic link from a mobile email app. Or initiated from the main browser app, but continued in a webview with a separate cookies store.

If I understand correctly, the code verifier shouldn't leave the client (that's the whole point of PKCE). So the fix here would just be failing gracefully from Supabase/GoTrue, and let the project display a recovery path. Something shorter than

Looks like you requested sign in from another device.

Open this page in Chrome on Windows
– or –
Sign in with another magic link on this iPhone

Workaround for now is to catch it (as it's not returned via result.error)

Edit: Example with screenshots and code added here: https://github.com/supabase/auth-helpers/issues/545#issuecomment-1666960329

Manouchehri commented 1 year ago

Is there a way to use PKCE easily with Cloudflare Workers? We've been using createClient (from @supabase/supabase-js) and it's been working great, until trying to add PKCE.

const supabase_client = createClient(c.env.SUPABASE_URL, c.env.SUPABASE_KEY, {
  auth: {
    persistSession: false,
    flowType: 'pkce'
  }
})
const token_test = 'asdfasdf-asdfasdf-adfsadf-asdfsdf' // We get the actual token from our URL manually elsewhere
const wat = await supabase_client.auth.exchangeCodeForSession(token_test);
AuthApiError: invalid request: both auth code and code verifier should be non-empty
    at index.js:12757:18 {
  __isAuthError: true,
  name: AuthApiError,
  status: 400,
  stack: AuthApiError: invalid request: both auth code and …fier should be non-empty
    at index.js:12757:18,
  message: invalid request: both auth code and code verifier should be non-empty
}
z-x commented 1 year ago

I am having this exact issue, but only when I run npm run dev --host and open the page from my mobile device (or a laptop using the hosted IP). It works on production deployed to Vercel, works when running locally with npm run dev and using localhost but fails with this error when running from hosted development server (so when I use the IP instead of localhost).

I am using the code example from the documentation without much changes so the error is appearing on the callback page.

I thought that maybe I am missing the redirect URL configured in Supabase, but it's there.

SvelteKit @ 1.20.2 Svelte @ 3.59.1 Auth Helpers SvelteKit @ 0.10.1 Supabase JS @ 2.24.0

tholder commented 1 year ago

Issue for me is with email confirmation flow not oauth. Works fine locally. Should point out, it seems to actually confirm the email but throws an error.

This is happening on Vercel and it's kicking out:

- error AuthApiError: invalid request: both auth code and code verifier should be non-empty
    at /var/task/.next/server/chunks/928.js:3374:24
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  __isAuthError: true,
  status: 400
}

For now, I have worked around this simply by suppressing the offending function. This obviously isn't an ideal situation though:

try {
    await supabase.auth.exchangeCodeForSession(code)
} catch (error) {}
krsnaa commented 1 year ago

The issue in the code seems to be the use of two different clients. You are using the auth helpers along with the normal supabase client, this won't work and will result in the error you are getting.

@silentworks, Is this mentioned anywhere in the docs? I spent pretty much all day today trying to get OAuth to work in SSR mode on a NextJS project with a refine.dev template and coming across this information earlier would have been incredibly helpful!

Also, the naming is rather misleading - auth-helpers strikes more as an add-on to supabase-js and not so much a replacement.

probablykasper commented 1 year ago

I tried reproducing it with the new 0.10.0 version, and the issue wasn't there. I'll let you know once I figure out more!

Updating the auth helpers made the error go away for me @silentworks

AnthonyLzq commented 1 year ago

I'm having the same error with @supabase/auth-helpers-remix^0.2.1.

wottpal commented 1 year ago

Same issue. Not able to reproduce it locally. Also, on Vercel, only some users are experiencing it :/ Using Next.js pages directory.

Thanks @tholder, probably gonna use this fix as well for now…

ky1ejs commented 1 year ago

nb: I'm using App Router on NextJS 13.4.7

I did some digging this evening and found that my NextJS route handler is not receiving cookies when calling cookies().

I confirmed this by changing the route.ts of the handler to a page.tsx and checking the cookies, indeed finding that the sb-<id>-auth-token-code-verifier key was present.

I noticed this because throughout my debugging, the cookie was in the client and was being seen by the middleware, yet the exception we're talking about was still thrown...

The problem I have now is that only Route Handlers and Server Actions can return cookies to the client, so using a Server Component will not be possible.

I'll share if I find a solution, but it seems this could be a NextJS issue rather than a Supabase issue?

ky1ejs commented 1 year ago

Did some more digging... in an older YouTube video on the Supabase channel, the presenter describes that there is a bug in NextJS with GET requests where cookies are not piped through! Here's to the point in the video where this is mentioned.

However, in a newer video, this seems to no longer be the case... I'm still experiencing empty cookies in NextJS 13.4.7, though :/

ky1ejs commented 1 year ago

After much googling and trawling the Vercel's GitHub discussions/issues, I've not found a solution to this.

In the end, I've taken a hacky approach in my middleware to handle the auth cookie, since cookies are available there for every request. I feel there is probably a way to do this with a ServerComponent and Server Actions, but I've already lost too much time to this issue and I want to get back to developing my project.

This is what my middleware looked like now:

import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse, NextRequest } from 'next/server'
import type { Database } from '@/supabase/database.types'

export async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const url = new URL(req.url)
  const supabase = createMiddlewareClient<Database>({ req, res })

  if (url.pathname === "/auth/callback") {
    const code = url.searchParams.get('code')
    if (code) {
      await supabase.auth.exchangeCodeForSession(code)
    }
  } else {
    await supabase.auth.getSession()
  }
  return res
}

Needless to say, this is not idea.

There are quite a few discussions/issues related to cookies in NextJS 13:

I couldn't find many that are exactly about cookies being missing:

So I made my own report with an example to clearly describe this exact case: https://github.com/vercel/next.js/issues/52209

Jordaneisenburger commented 1 year ago

Hi @ky1ejs,

For what it's worth, I'm also working with Next.js and I've did the following which works for me:

//app/auth/callback/route.ts

import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';

import type { NextRequest } from 'next/server';
import type { Database } from '@/types/database.types';
import { ROUTES } from '@/consts';

export async function GET(request: NextRequest) {
    const requestUrl = new URL(request.url);
    const code = requestUrl.searchParams.get('code');

    if (code) {
        const supabase = createRouteHandlerClient<Database>({ cookies });
        await supabase.auth.exchangeCodeForSession(code);
    }

    // URL to redirect to after sign in process completes
    return NextResponse.redirect(`${requestUrl.origin}${ROUTES.accountNew}`);
}

Hope this helps 🎉 
ky1ejs commented 1 year ago

Turns out I need to upgrade to the latest version of Node 18.x: https://github.com/vercel/next.js/issues/52209#issuecomment-1621889571

crushingCodes commented 1 year ago

@ky1ejs Thanks, that was a mystery working out why cookies were not not showing up

nine-long commented 1 year ago

I'm still facing the issue. Followed the documentation https://supabase.com/docs/guides/auth/auth-helpers/sveltekit#server-side and I'm trying server side authentication.

Supabase auth logs are showing the same:

{"component":"api","error":"400: invalid request: both auth code and code verifier should be non-empty","level":"info","method":"POST","msg":"400: invalid request: both auth code and code verifier should be non-empty","path":"/token","referer":"","remote_addr":"","time":"2023-07-09T11:21:32Z","timestamp":"2023-07-09T11:21:32Z"}

rygine commented 1 year ago

i'm also experiencing this error when i try to use the remix auth helpers following the official guide here.

using:

with auth email settings:

auth works fine out of the box with this remix stack, but i'd like to switch to the official remix helpers.

revogabe commented 1 year ago

my solution was this, this worked for me perfectly

// middleware.ts  in next.js

export default async function middleware(req: NextRequest) {
  const res = NextResponse.next()
  const url = new URL(req.url)
  const supabase = createMiddlewareClient<Database>({ req, res })
  await supabase.auth.getSession()

  if (url.pathname == '/auth/callback') {
    const code = url.searchParams.get('code')

    if (code) {
      try {
        await supabase.auth.exchangeCodeForSession(code)
        return NextResponse.redirect('http://app.localhost:3000/reset-password')
      } catch (error) {
        console.log(error)
      }
    } else {
      return NextResponse.redirect('http://app.localhost:3000/login')
    }
  }
}
rygine commented 1 year ago

for anyone landing here using Remix, the fix that worked for me was to patch @supabase/auth-helpers-shared and replace pkce with implicit for the flowType values in both ESM and CJS exports.

this assumes you've followed the official guide.

parsasi commented 1 year ago

for anyone landing here using Remix, the fix that worked for me was to patch @supabase/auth-helpers-shared and replace pkce with implicit for the flowType values in both ESM and CJS exports.

this assumes you've followed the official guide.

That takes away the SSR aspect of things. That only work if you are doing your auth on the front end, which doesn't support the best Remix experience.

nickjg1 commented 1 year ago

Is there a way to handle this issue if a user opens a verify/forgot password email and it is not on the same device as they sent it on? (e.g forgot password link sent on laptop, verifier cookie saved locally, user opens link on phone, verifier cookie not present)

EdmondChuiHW commented 1 year ago

Here's a possible flow. More code but better UX imo.

No client JS needed in this example. Should work with SSR and SSG.

As mentioned above, missing cookie/clicking from a different device is a normal case that should be handled imo.

Screenshots here. Code below.

Flow

  1. Present login options

    Login in form with 'Login with magic link' button
  2. Show a quick tip after sending magic link

    Landing page shown after logging in via magic link with message 'Magic link sent. Make sure to click the link from this device. Why? You can only login with a magic link from the same device you requested it from. This is to prevent bad guys from stealing the link from your email.'
  3. Green path: home page; missing cookie (this issue): show recovery options. Optionally, show other ways to login, e.g. password

    Login page after clicking a magic link from a different device with the message 'Looks like you requested this magic link from a different device. No worries. You can try again from this device.'
  4. Bonus (unrelated to this issue). If the link has been clicked or expired, return to login page with a message:

    Login page after clicking an expired magic link with the message 'Looks like the magic link has expired. No worries. You can try again here.'

These messages can probably be improved if you work with a design team.

Code

  1. Update your login page support the above layouts. For example, use a type query param with these values:

    1. diff_device
    2. bad_code
    3. sent_magic_link (optional)
  2. Update the emailRedirectTo field when handling the "Sign in with magic link" button click. You can do this on the client or the server.

    const urlSafeEmail = encodeURIComponent(email);
    await getServerActionSupabase().auth.signInWithOtp({
      email,
      options: {
        emailRedirectTo: `${YOUR_BASE_URL}/auth/callback/${urlSafeEmail}`,
      },
    });
    
    // Show the "magic link sent" layout here. Options:
    // Server or client: redirect to `/YOUR_LOGIN_PATH?type=sent_magic_link&email=${urlSafeEmail}`
    // Client: You can also use JS to show the message
  3. Example route handler in Next.js:

    // src/app/auth/callback/[email]/route.ts
    import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
    import { cookies } from "next/headers";
    import { NextResponse } from "next/server";
    
    import type { NextRequest } from "next/server";
    import { isAuthApiError } from "@supabase/supabase-js";
    
    type Props = {
      params: { email: string };
    };
    
    export async function GET(request: NextRequest, { params }: Props) {
      const requestUrl = new URL(request.url);
      const code = requestUrl.searchParams.get("code");
    
      if (!code) {
        return redirectBadCode();
      }
    
      const supabase = createRouteHandlerClient({ cookies });
    
      try {
        const { data } = await supabase.auth.exchangeCodeForSession(code);
        if (data.session) {
          return NextResponse.redirect(`${requestUrl.origin}/YOUR_HOME_PATH`");
        } else {
          return redirectBadCode();
        }
    
        /* https://github.com/supabase/auth-helpers/issues/545
         * When fixed, rm catch and use the error returned from exchangeCodeForSession(…)
         */
      } catch (error) {
        if (isAuthApiError(error)) {
          return redirectDiffDevice();
        }
    
        // If you, too, like living dangerously:
        throw error;
      }
    
      function redirectBadCode() {
        const searchParams = encodeSearchParams({
          email: params.email,
          type: "bad_code",
        });
    
        return NextResponse.redirect(`${requestUrl.origin}/YOUR_LOGIN_PATH?${searchParams}`);
      }
    
      function redirectDiffDevice() {
        const searchParams = encodeSearchParams({
          email: params.email,
          type: "diff_device",
        });
    
        return NextResponse.redirect(`${requestUrl.origin}/YOUR_LOGIN_PATH?${searchParams}`);
      }
    }
    
    function encodeSearchParams(params: Record<string, string>) {
      return new URLSearchParams(params).toString();
    }

Bit more complicated to maintain but worth it for the overall UX imo.

Should work with other SSR/SSG frameworks too since the above doesn't need any client-side JS.

Share here if you found better ways 🙌

thomastilkema commented 1 year ago

I was running into this issue as well running my app locally. When running locally, my host was http://something.localtest.me. This issue was gone when going back to http://localhost.

edgarasben commented 1 year ago

I ran into the same issue, I am using Supabase+Next.js auth helpers.

The code:

// invite_user.ts

import { NextResponse } from 'next/server'
import { signInWithMagicLink } from '@/utils/supabase-queries'
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'

export async function GET(request: Request) {
    const supabase = createRouteHandlerClient<Database>({ cookies })

    const { searchParams } = new URL(request.url)
    const email = searchParams.get('email')

    if (email) {
        await signInWithMagicLink(supabase, email)
        return NextResponse.json('Invite sent!')
    }

    return NextResponse.json('No email provided')
}
// supabase-queries.ts
import { SupabaseClient } from '@supabase/supabase-js'
import { toSiteURL } from './utils'
import { errors } from './errors'

export const signInWithMagicLink = async (
    supabase: SupabaseClient<Database>,
    email: string
) => {
    const { error } = await supabase.auth.signInWithOtp({
        email,
        options: {
            emailRedirectTo: toSiteURL('/auth/callback'),
        },
    })

    if (error) {
        errors.add(error.message)
        throw error
    }
}
// /auth/callback/route.ts

import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export async function GET(request: Request) {
    const requestUrl = new URL(request.url)
    const code = requestUrl.searchParams.get('code')

    console.log('requestUrl', request.url)
    console.log('code', code)

    if (code) {
        try {
            const supabase = createRouteHandlerClient<Database>({ cookies })
            await supabase.auth.exchangeCodeForSession(code)
        } catch (error) {
            console.error('Failed to exchange code for session: ', error)
        }
    }

    const redirectTo = new URL('/required-session', requestUrl.origin)

    return NextResponse.redirect(redirectTo)
}

I am getting the code correctly from Supabase API:

requestUrl: http://localhost:3000/auth/callback?code=1aeb35ec-029b-494c-88d3-6ce887d86035
code: 1aeb35ec-029b-494c-88d3-6ce887d86035

But there is the same error:

Failed to exchange code for session:  AuthApiError: invalid request: both auth code and code verifier should be non-empty
melekabbassi commented 1 year ago

I ran into the same issue, I am using Supabase+Next.js auth helpers.

The code:

// invite_user.ts

import { NextResponse } from 'next/server'
import { signInWithMagicLink } from '@/utils/supabase-queries'
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'

export async function GET(request: Request) {
    const supabase = createRouteHandlerClient<Database>({ cookies })

    const { searchParams } = new URL(request.url)
    const email = searchParams.get('email')

    if (email) {
        await signInWithMagicLink(supabase, email)
        return NextResponse.json('Invite sent!')
    }

    return NextResponse.json('No email provided')
}
// supabase-queries.ts
import { SupabaseClient } from '@supabase/supabase-js'
import { toSiteURL } from './utils'
import { errors } from './errors'

export const signInWithMagicLink = async (
    supabase: SupabaseClient<Database>,
    email: string
) => {
    const { error } = await supabase.auth.signInWithOtp({
        email,
        options: {
            emailRedirectTo: toSiteURL('/auth/callback'),
        },
    })

    if (error) {
        errors.add(error.message)
        throw error
    }
}
// /auth/callback/route.ts

import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export async function GET(request: Request) {
    const requestUrl = new URL(request.url)
    const code = requestUrl.searchParams.get('code')

    console.log('requestUrl', request.url)
    console.log('code', code)

    if (code) {
        try {
            const supabase = createRouteHandlerClient<Database>({ cookies })
            await supabase.auth.exchangeCodeForSession(code)
        } catch (error) {
            console.error('Failed to exchange code for session: ', error)
        }
    }

    const redirectTo = new URL('/required-session', requestUrl.origin)

    return NextResponse.redirect(redirectTo)
}

I am getting the code correctly from Supabase API:

requestUrl: http://localhost:3000/auth/callback?code=1aeb35ec-029b-494c-88d3-6ce887d86035
code: 1aeb35ec-029b-494c-88d3-6ce887d86035

But there is the same error:

Failed to exchange code for session:  AuthApiError: invalid request: both auth code and code verifier should be non-empty

I have the same issue but with social login did you find a solution? I'm using astro SSR btw.

edgarasben commented 1 year ago

I ran into the same issue, I am using Supabase+Next.js auth helpers. The code:

// invite_user.ts

import { NextResponse } from 'next/server'
import { signInWithMagicLink } from '@/utils/supabase-queries'
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'

export async function GET(request: Request) {
    const supabase = createRouteHandlerClient<Database>({ cookies })

    const { searchParams } = new URL(request.url)
    const email = searchParams.get('email')

    if (email) {
        await signInWithMagicLink(supabase, email)
        return NextResponse.json('Invite sent!')
    }

    return NextResponse.json('No email provided')
}
// supabase-queries.ts
import { SupabaseClient } from '@supabase/supabase-js'
import { toSiteURL } from './utils'
import { errors } from './errors'

export const signInWithMagicLink = async (
    supabase: SupabaseClient<Database>,
    email: string
) => {
    const { error } = await supabase.auth.signInWithOtp({
        email,
        options: {
            emailRedirectTo: toSiteURL('/auth/callback'),
        },
    })

    if (error) {
        errors.add(error.message)
        throw error
    }
}
// /auth/callback/route.ts

import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export async function GET(request: Request) {
    const requestUrl = new URL(request.url)
    const code = requestUrl.searchParams.get('code')

    console.log('requestUrl', request.url)
    console.log('code', code)

    if (code) {
        try {
            const supabase = createRouteHandlerClient<Database>({ cookies })
            await supabase.auth.exchangeCodeForSession(code)
        } catch (error) {
            console.error('Failed to exchange code for session: ', error)
        }
    }

    const redirectTo = new URL('/required-session', requestUrl.origin)

    return NextResponse.redirect(redirectTo)
}

I am getting the code correctly from Supabase API:

requestUrl: http://localhost:3000/auth/callback?code=1aeb35ec-029b-494c-88d3-6ce887d86035
code: 1aeb35ec-029b-494c-88d3-6ce887d86035

But there is the same error:

Failed to exchange code for session:  AuthApiError: invalid request: both auth code and code verifier should be non-empty

I have the same issue but with social login did you find a solution? I'm using astro SSR btw.

Nope. Still struggling with this... :(

edgarasben commented 1 year ago

OK, I solved it partly — it works if I send a signup email with supabase.auth.signInWithOtp in server component (a regular) page. I just tested with a server action and it works!

However, what doesn't work, and what I need for my use case is sending a signup email via API route.

So probably something is wrong with createRouteHandlerClient({ cookies }), probably it's not setting a "code verifier" that is used later in callback route.


Can someone try sending a sign up email via API route with createRouteHandlerClient({ cookies }) and supabase.auth.signInWithOtp to confirm it doesn't work?

nickjg1 commented 1 year ago

OK, I solved it partly — it works if I send a signup email with supabase.auth.signInWithOtp in server component (a regular) page. I just tested with a server action and it works!

However, what doesn't work, and what I need for my use case is sending a signup email via API route.

So probably something is wrong with createRouteHandlerClient({ cookies }), probably it's not setting a "code verifier" that is used later in callback route.


Can someone try sending a sign up email via API route with createRouteHandlerClient({ cookies }) and supabase.auth.signInWithOtp to confirm it doesn't work?

Also still struggling with this. Pretty bad UX in my app and can't seem to get around the code verifier being stored locally.

Slyracoon23 commented 1 year ago

I have the same issue on nextjs and supabase-auth-nextjs.

I have a workaround where I use authStateChange on client-side. It doesn't break the UI/UX too much.


import { useEffect } from 'react';
import { createClientComponentClient } from '@supabase/supabase-js';
import { useRouter } from 'next/router';

export default function Home() {
  const supabase = createClientComponentClient();
  const router = useRouter();

  useEffect(() => {
    // Set up an authentication listener
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        console.log(`Supabase auth event in home: ${event}`);

        if (event === 'SIGNED_IN') {
          // Redirect to the dashboard upon successful sign-in
          router.push('/dashboard');
        }

        if (session !== null) {
          // Redirect to the dashboard if a session is present
          console.log('session is:', session);
          router.push('/dashboard');
        }
        // Additional checks or redirects can be implemented
      }
    );

    // Cleanup function: unsubscribe the auth listener when the component unmounts
    return () => {
      subscription.unsubscribe();
    };
  }, []);

  return (
    <div>
      {/* Your home page content */}
    </div>
  );
}
silentworks commented 1 year ago

We have published a new guide on how to handle this from the email perspective and framework side of things. This requires an email template change and a new endpoint in your application https://supabase.com/docs/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr

If you are using the Supabase CLI and want to customize your email templates, you can do this now by following this guide https://supabase.com/docs/guides/cli/customizing-email-templates

edgarasben commented 1 year ago

We have published a new guide on how to handle this from the email perspective and framework side of things. This requires an email template change and a new endpoint in your application https://supabase.com/docs/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr

If you are using the Supabase CLI and want to customize your email templates, you can do this now by following this guide https://supabase.com/docs/guides/cli/customizing-email-templates

Awesome! So, should app/auth/confirm route with verifyOtp be used from now on instead of app/auth/callback with exchangeCodeForSession like in other examples?

silentworks commented 1 year ago

@edgarasben yes if you are doing any auth process that uses magic links (signUp, resetPasswordForEmail, inviteUserByEmail and signInWithOtp). If you plan on doing signInWithOAuth in your app then you will still need the app/auth/callback route.

edgarasben commented 1 year ago

@edgarasben yes if you are doing any auth process that uses magic links (signUp, resetPasswordForEmail, inviteUserByEmail and signInWithOtp). If you plan on doing signInWithOAuth in your app then you will still need the app/auth/callback route.

If I need both, and there is only one email template, will the /callback route be able to handle the updated email templates?

silentworks commented 1 year ago

@edgarasben but signInWithOAuth doesn't use email templates. You would still just have both endpoints in your project.

silentworks commented 1 year ago

Closing this out as we now have a guide showing how to get around this issue:

https://github.com/supabase/supabase/pull/16728 https://github.com/supabase/supabase/pull/16683

tolgaergin commented 1 year ago

Looks like the problem still continues for signInWithOAuth in nextjs@13.4.19. @silentworks
with the same code not getting any error if i use nextjs@13.4.7

error AuthApiError: invalid flow state, no valid flow state found

client component

const signInWithGithub = async () => {
    await supabase.auth.signInWithOAuth({
      provider: "github",
      options: {
        redirectTo: window.location.origin + "/auth/callback",
      },
    });
  };

app/auth/callback/route.js

import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export const dynamic = "force-dynamic";

export async function GET(request) {
  const requestUrl = new URL(request.url);
  const code = requestUrl.searchParams.get("code");

  if (code) {
    const supabase = createRouteHandlerClient({ cookies });
    await supabase.auth.exchangeCodeForSession(code);
  }

  return NextResponse.redirect(requestUrl.origin);
}
silentworks commented 1 year ago

Re-opening this issue as a number of users have reported it still happening.

Can anyone still experiencing this please also state the browser they are using along with Next version and if its app router or pages directory?

vinch commented 1 year ago

Re-opening this issue as a number of users have reported it still happening.

Can anyone still experiencing this please also state the browser they are using along with Next version and if its app router or pages directory?

@silentworks I encounter the same error with exchangeCodeForSession with SvelteKit on Chrome, simply by following this official tutorial: https://supabase.com/docs/guides/auth/auth-helpers/sveltekit

The complete code for my src/routes/auth/callback/+server.ts file is the following (adapted for TypeScript):

import type { SupabaseClient } from '@supabase/supabase-js';
import { redirect } from '@sveltejs/kit';

export const GET = async ({
    url,
    locals: { supabase }
}: {
    url: URL;
    locals: { supabase: SupabaseClient };
}) => {
    const code = url.searchParams.get('code');

    if (code) {
        await supabase.auth.exchangeCodeForSession(code);
    }

    throw redirect(303, '/');
};

The error message: AuthApiError: invalid request: both auth code and code verifier should be non-empty