supabase / supabase-js

An isomorphic Javascript client for Supabase. Query your Supabase database, subscribe to realtime events, upload and download files, browse typescript examples, invoke postgres functions via rpc, invoke supabase edge functions, query pgvector.
https://supabase.com
MIT License
2.86k stars 220 forks source link

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

Open ghost opened 1 year ago

ghost commented 1 year ago

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

// 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.

Qw4z1 commented 8 months ago

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/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.33.2";

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: 'my@email.com',
  password: 'mypassword',
})

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

const { data, error } = await supabase.functions.invoke('myfunction')
Qw4z1 commented 8 months ago

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.

kangmingtay commented 3 months ago

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.

agtseito commented 2 months ago

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/gotrue-js@2.57.0/esnext/gotrue-js.mjs:2:5282)
    at eventLoopTick (ext:core/01_core.js:64:7)
    at async Ie (https://esm.sh/v135/@supabase/gotrue-js@2.57.0/esnext/gotrue-js.mjs:2:6069)
    at async h (https://esm.sh/v135/@supabase/gotrue-js@2.57.0/esnext/gotrue-js.mjs:2:5806)
    at async https://esm.sh/v135/@supabase/gotrue-js@2.57.0/esnext/gotrue-js.mjs:2:26219
    at async m._useSession (https://esm.sh/v135/@supabase/gotrue-js@2.57.0/esnext/gotrue-js.mjs:2:25008)
    at async m._getUser (https://esm.sh/v135/@supabase/gotrue-js@2.57.0/esnext/gotrue-js.mjs:2:26136)
    at async https://esm.sh/v135/@supabase/gotrue-js@2.57.0/esnext/gotrue-js.mjs:2:25999
    at async https://esm.sh/v135/@supabase/gotrue-js@2.57.0/esnext/gotrue-js.mjs:2:24294 {
  __isAuthError: true,
  name: "AuthApiError",
  status: 401
}
Chipsnet commented 3 weeks ago

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

filippovd20 commented 3 weeks ago

@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.

mattaylor commented 3 weeks ago

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

mattaylor commented 3 weeks ago

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