Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[email protected] breaks client auth with edge functions #881

Closed
2 tasks
b-snel opened this issue Apr 19, 2024 · 15 comments · Fixed by #882
Closed
2 tasks

[email protected] breaks client auth with edge functions #881

b-snel opened this issue Apr 19, 2024 · 15 comments · Fixed by #882
Labels
bug Something isn't working

Comments

@b-snel
Copy link

b-snel commented Apr 19, 2024

Bug report

  • I confirm this is a bug with Supabase, not with my own application.
  • I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

When I get the authorization header from the client in an edge function I'm unable to make a supabase client with the newest version of supabase-js (using deno edge functions). The behavior doesn't happen on 2.42.4 and below. I have rolled back but I wanted to bring this to your attention.

To Reproduce

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

  1. Go to '…'
  2. Click on '…'
  3. Scroll down to '…'
  4. See error

Expected behavior

Able to get user auth details. See code snippet at bottom of how I typically do that.

Screenshots

If applicable, add screenshots to help explain your problem.

System information

  • OS: [e.g. macOS, Windows]
  • Browser (if applies) [e.g. chrome, safari]
  • Version of supabase-js: [e.g. 6.0.2]
  • Version of Node.js: [e.g. 10.10.0]

Additional context

`import { corsHeaders } from '../_shared/cors.ts'
import { createClient } from "supabase-js"
import { createSupabaseClient } from '../_shared/supabaseClient.ts'

Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
}

const authHeader = req.headers.get('Authorization')!
const supabaseClient = createSupabaseClient(authHeader)

// Get the session or user object
const { data } = await supabaseClient.auth.getUser()
const user = data.user

if (!user) {
throw new Error('User is null');
}

}`

and here is the definition for my client function:
`import { createClient, SupabaseClient } from "supabase-js"

export function createSupabaseClient(authHeader: string): SupabaseClient {
return createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
{ global: { headers: { Authorization: authHeader } } }
);
}`

@b-snel b-snel added the bug Something isn't working label Apr 19, 2024
@darrendawson
Copy link

I ran into this today as well when I deployed a new version of an existing edge function and immediately started getting Auth errors.

AuthSessionMissingError: Auth session missing!
    at https://esm.sh/v135/@supabase/[email protected]/deno/auth-js.mjs:2:29428
    at _._useSession (https://esm.sh/v135/@supabase/[email protected]/deno/auth-js.mjs:2:27511)
    at eventLoopTick (ext:core/01_core.js:64:7)
    at async _._getUser (https://esm.sh/v135/@supabase/[email protected]/deno/auth-js.mjs:2:29101)
    at async https://esm.sh/v135/@supabase/[email protected]/deno/auth-js.mjs:2:28964
    at async https://esm.sh/v135/@supabase/[email protected]/deno/auth-js.mjs:2:26791 {
  __isAuthError: true,
  name: "AuthSessionMissingError",
  status: 400,
  code: undefined
}

@zguo123
Copy link

zguo123 commented Apr 19, 2024

Looks like it's affecting me as well.

AuthSessionMissingError: Auth session missing!
    at https://esm.sh/v135/@supabase/[email protected]/esnext/auth-js.mjs:2:29430
    at _._useSession (https://esm.sh/v135/@supabase/[email protected]/esnext/auth-js.mjs:2:27513)
    at eventLoopTick (ext:core/01_core.js:64:7)
    at async _._getUser (https://esm.sh/v135/@supabase/[email protected]/esnext/auth-js.mjs:2:29103)
    at async https://esm.sh/v135/@supabase/[email protected]/esnext/auth-js.mjs:2:28966 {
  __isAuthError: true,
  name: "AuthSessionMissingError",
  status: 400,
  code: undefined
}

@CookedApps
Copy link

Wasted 4 hours debugging this... 🥲

@zguo123
Copy link

zguo123 commented Apr 19, 2024

Wasted 4 hours debugging this... 🥲

LMAO, you were not alone. I restarted the database, and I reset my jwt tokens lmao.

@andymitchell
Copy link

+1

Observations:

  • Authentication is working through the gateway, as the served code of the Edge Function executes
  • But using SupabaseJS 2.42.5 in the Edge Function, to createClient and then getUser, returns empty
Screenshot 2024-04-19 at 19 50 35

@christophemenager
Copy link

christophemenager commented Apr 19, 2024

I confirm I have the same issue and rolling back to
"supabase-js": "https://esm.sh/@supabase/[email protected]", in my import_map.json fixes the issue

Seems related to

CleanShot 2024-04-19 at 22 16 10@2x

@zguo123
Copy link

zguo123 commented Apr 19, 2024

Here's the solution:

via Discord:

No
You have to strip the Bearer part out
You just want a jwt.
supabaseClient.auth.getUser(authHeader.split(' ')[1]); Another user just posted this. Not verified.

@j4w8n
Copy link
Contributor

j4w8n commented Apr 19, 2024

Here is my observation:

PR #876 adds a check for data.session?.access_token inside of getUser(). If it doesn't exist, then processing stops and returns null - which is what devs are now seeing in edge functions, because there is no Supabase session at this point.

Previously, without the new PR code, it would have just made a fetch request to the auth backend using _request. Notice the headers and jwt properties.

/* https://github.com/supabase/auth-js/blob/v2.63.1/src/GoTrueClient.ts#L1182-L1185 */
return await _request(this.fetch, 'GET', `${this.url}/user`, {
  headers: this.headers,
  jwt: data.session?.access_token ?? undefined,
  xform: _userResponse,
})

And if you look at _request, it's taking the jwt value, if it exists, and sets the Authorization header - which means the auth backend looks at that header to grab the JWT, lookup the user, and return a user response. And even if jwt is undefined, it still passes any Supabase client headers. So by setting the Authorization header in the edge function's Supabase client, we were bypassing the need to pass-in the JWT to getUser() in order for this to work without a session.

/* https://github.com/supabase/auth-js/blob/v2.63.1/src/lib/fetch.ts#L127-L143 */
export async function _request(
  fetcher: Fetch,
  method: RequestMethodType,
  url: string,
  options?: GotrueRequestOptions
) {
  const headers = {
    ...options?.headers,
  }

  if (!headers[API_VERSION_HEADER_NAME]) {
    headers[API_VERSION_HEADER_NAME] = API_VERSIONS['2024-01-01'].name
  }

  if (options?.jwt) {
    headers['Authorization'] = `Bearer ${options.jwt}`
  }
...
  const data = await _handleRequest(
    fetcher,
    method,
    url + queryString,
    {
      headers,
      noResolveJson: options?.noResolveJson,
    },
    {},
    options?.body
  )

@ffaubert
Copy link

Seems like a pretty major change for such a minor version bump. This broke my app too.

@jareddr
Copy link

jareddr commented Apr 19, 2024

+1 This broke my app too. I was deploying new edge functions in dev today and thought I was having the worst coding day ever for an hour or two. Because my edge functions don't have a pinned supabase library version the new code was getting picked up whenever I would deploy a new version of an edge function.

I'm sure many people like me have used the supabase edge function auth docs as a starting point for how to get authenticated user data within an edge function. This suggests that you can leave the getUser() function empty and indeed it has been working that way for months prior.

I ended up having to go through every edge function and patch it like this

const supabaseClient = createClient(
	Deno.env.get('SUPABASE_URL') ?? '',
	Deno.env.get('SUPABASE_ANON_KEY') ?? '',
	{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
);
const JWT = req.headers.get('Authorization')?.split(' ')[1] || '';
const userData = await supabaseClient.auth.getUser(JWT);

I'm confused why the supabase client needs the same JWT twice. I don't know all of the intended uses of the getUser function, but surely there should be some convenience function within the supabase client that will grab the user data matching the authorization header passed in when creating the client.

I think my take away here is to not do this in my edge function code

import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';

and instead do this

import { createClient } from 'https://esm.sh/@supabase/[email protected]';

and then only upgrading when I explicitly intend to move to a new version.

@GaryAustin1
Copy link

GaryAustin1 commented Apr 19, 2024

I'm going to move this to the auth repository as it seems to be the underlying auth-js release is the issue.

Hopefully the right move...

@GaryAustin1
Copy link

@kangmingtay Does the supabase-js team know about this fix? Users are still running into the issue on Edge functions and your fix here has not been merged with supabase-js yet, or released.

@zguo123
Copy link

zguo123 commented Apr 23, 2024

@kangmingtay Does the supabase-js team know about this fix? Users are still running into the issue on Edge functions and your fix here has not been merged with supabase-js yet, or released.

You can use any version below 2.42.4.

Eg:

import { createClient } from 'https://esm.sh/@supabase/[email protected]';

@GaryAustin1
Copy link

GaryAustin1 commented Apr 23, 2024

Sure and that's what I'm telling users that come to discord, or github and I see them. Or just use getUser(jwt) with the jwt from the header.
BUT users are hitting this and not knowing what the issue is, so wasting alot of time on their part till they find the solution.

@jareddr
Copy link

jareddr commented Apr 23, 2024

Just to reinforce what GaryAustin1 is saying...

When I wrote in to support with this issue I got back:

Thank you for contacting Supabase support. This works and I have it in a few of my edge functions.

Have you tried logging the headers to debug it?

In reference to calling auth.getUser() with no JWT. It seems like the organization doesn't understand the existence or magnitude of the problem.

It's a big breaking issue for anyone who copied code form the JavaScript edge function auth docs. It's insidious in that it only shows up when you deploy a new version of your edge function and only if you are using the import line from the docs which just references @supabase/supabase-js@2.

Then you spend a lot of time trying to figure out how you completely broke your edge function with whatever change you made.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants