supabase-js icon indicating copy to clipboard operation
supabase-js copied to clipboard

The User object is null in Edge Function runtime with a valid authorization token

Open ghost opened this issue 3 years ago • 13 comments

Bug report

The user object is null in Edge Function runtime with a valid authorization token.

Describe the bug

I'm trying to get the User object with a valid Authorization token to perform an insert on a specific table that contains the user id; the user object is always at runtime, even with a valid authorization token.

To Reproduce

  • Create an Edge function and deploy it
// Get the authorization header from the request.
const authHeader = req.headers.get('Authorization').replace("Bearer ","")
// Client now respects auth policies for this user
supabaseClient.auth.setAuth(authHeader)
// set the user profile
const user = supabase.auth.user()

console.log(user);
>> null

Expected behavior

Returns the current Auth user with UUID.

ghost avatar Aug 22 '22 19:08 ghost

Getting the same result from copy pasting sample code from the RESTful service.

Function call runs, but the user object is null and I can't access the database with my supabase client (assuming RLS blocks queries because the user is not authenticated).

myfunction/index.ts

import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/[email protected]";

serve(async (req) => {
  const { url, method } = req;

  try {
    const supabaseClient = createClient(
      // Supabase API URL - env var exported by default.
      Deno.env.get("SUPABASE_URL") ?? "",
      // Supabase API ANON KEY - env var exported by default.
      Deno.env.get("SUPABASE_ANON_KEY") ?? "",
      // Create client with Auth context of the user that called the function.
      // This way your row-level-security (RLS) policies are applied.
      {
        global: {
          headers: { Authorization: req.headers.get("Authorization")! },
        },
      }
    );

    const {
      data: { user },
    } = await supabaseClient.auth.getUser();

    // Null here
    console.log("USER : ", user);

    return new Response(JSON.stringify({ user: user }), {
      headers: { "Content-Type": "application/json" },
      status: 200,
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      headers: { "Content-Type": "application/json" },
      status: 400,
    });
  }
});

Sample node app:

import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'LOCAL HOST',
  'SUPABASE_PUBLIC_KEY'
)


const { data: userData, error: userError } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'mypassword',
})

// Exists here
console.log("User Data: ", userData)

const { data, error } = await supabase.functions.invoke('myfunction')

Qw4z1 avatar Sep 18 '23 10:09 Qw4z1

Or maybe this is the issue... =/

No storage option exists to persist the session, which may result in unexpected behavior when using auth. If you want to set persistSession to true, please provide a storage option or you may set persistSession to false to disable this warning.

Qw4z1 avatar Sep 18 '23 10:09 Qw4z1

Hi everyone, we have a guide here on how to set up the auth context in your edge function correctly: https://supabase.com/docs/guides/functions/auth#auth-context

@Qw4z1 are you still facing this issue? You need to set persistSession to false when you create the client in your sample node app

As for your code in your edge function, did you check if req.headers.get("Authorization") returns a user's JWT? You should also be setting persistSession to false when you create the client here.

kangmingtay avatar Feb 16 '24 07:02 kangmingtay

Hi everyone, we have a guide here on how to set up the auth context in your edge function correctly: https://supabase.com/docs/guides/functions/auth#auth-context

@Qw4z1 are you still facing this issue? You need to set persistSession to false when you create the client in your sample node app

As for your code in your edge function, did you check if req.headers.get("Authorization") returns a user's JWT? You should also be setting persistSession to false when you create the client here.

Followed the guide, verified auth header returns JWT, set persistSession to false. Still getting null when calling getUser() and the missing sub claim error

// Create a Supabase client with the Auth context of the logged in user.
    const supabaseClient = createClient<Database>(
      // Supabase API URL - env var exported by default.
      Deno.env.get("SUPABASE_URL") ?? "",
      // Supabase API ANON KEY - env var exported by default.
      Deno.env.get("SUPABASE_ANON_KEY") ?? "",
      // Create client with Auth context of the user that called the function.
      // This way your row-level-security (RLS) policies are applied.
      {
        global: {
          headers: { Authorization: req.headers.get("Authorization")! },
        },
        auth: {
          persistSession: false,
        },
      },
    );
    const { data: { user }, error } = await supabaseClient.auth.getUser();
    console.log(user, error); // returns null

The error

[Info] null AuthApiError: invalid claim: missing sub claim
    at le (https://esm.sh/v135/@supabase/[email protected]/esnext/gotrue-js.mjs:2:5282)
    at eventLoopTick (ext:core/01_core.js:64:7)
    at async Ie (https://esm.sh/v135/@supabase/[email protected]/esnext/gotrue-js.mjs:2:6069)
    at async h (https://esm.sh/v135/@supabase/[email protected]/esnext/gotrue-js.mjs:2:5806)
    at async https://esm.sh/v135/@supabase/[email protected]/esnext/gotrue-js.mjs:2:26219
    at async m._useSession (https://esm.sh/v135/@supabase/[email protected]/esnext/gotrue-js.mjs:2:25008)
    at async m._getUser (https://esm.sh/v135/@supabase/[email protected]/esnext/gotrue-js.mjs:2:26136)
    at async https://esm.sh/v135/@supabase/[email protected]/esnext/gotrue-js.mjs:2:25999
    at async https://esm.sh/v135/@supabase/[email protected]/esnext/gotrue-js.mjs:2:24294 {
  __isAuthError: true,
  name: "AuthApiError",
  status: 401
}

agtseito avatar Mar 02 '24 15:03 agtseito

I am having the same error, but was able to get the user as follows

  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 { data: { user }, error } = await supabaseClient.auth.getUser(jwt);

However, this is a different behavior from the documentation and this issue should be corrected

Chipsnet avatar Apr 20 '24 20:04 Chipsnet

@Chipsnet thanks for figuring it out! In fact, the current behavior is correct and you have to pass the JWT token to getUser. It's documentation says "Gets the current user details if there is an existing session.". Apparently, there's no session in the edge environment so it's required. Edge docs have to be fixed though.

filippovd20 avatar Apr 21 '24 22:04 filippovd20

This is super annoying and is a recent breaking change which has had serious consequences on our production services. Please FIX DOCS ASAP!

mattaylor avatar Apr 24 '24 18:04 mattaylor

Setting authorization headers on new clients does not work as documented. The only solution to using clients within edge function is the undocumented solution identified by @Chipsnet

mattaylor avatar Apr 24 '24 18:04 mattaylor

Hey guys, any update on this issue?

I tested everything above + https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts, but still getting null user.

What I'm trying to do is to trigger webhook with Edge Function, to update table based on any operation in another table.

SquareGraph avatar Aug 06 '24 17:08 SquareGraph

any update on this issue?

just-dodo avatar Oct 07 '24 12:10 just-dodo