supabase-js: AuthApiError: invalid claim: missing sub claim

Bug report

Describe the bug

Credentials are malformed are not being stored properly and therefore the client is not able to lately resolve the session

image

After calling supabase.auth.signInWithPassword({email, password}) this strangely formatted token is generated

'["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjY4NjAyNTM1LCJzdWIiOiJlYThiMjkxYS0wZTVkLTQ0YmEtYmViYi0zOGViYTQ1Y2UyOTgiLCJlbWFpbCI6ImFsZXh2Y2FzaWxsYXNAZ21haWwuY29tIiwicGhvbmUiOiIiLCJhcHBfbWV0YWRhdGEiOnsicHJvdmlkZXIiOiJlbWFpbCIsInByb3ZpZGVycyI6WyJlbWFpbCJdfSwidXNlcl9tZXRhZGF0YSI6e30sInJvbGUiOiJhdXRoZW50aWNhdGVkIiwic2Vzc2lvbl9pZCI6ImFkN2FmYzRiLTZmYzQtNGQxOC04OWJiLTRiYmM1YmNmMzUxMyJ9.LK3i6Dyh-dZX-yk4NvgEdxYwmxsoOnAk1vfK-0Nmoe4","ktuUPxuJF-PHPhRpWfvUfQ",null,null]'

And later on, doing await supabase.auth.setSession(token); throws the following error:

{
  error: AuthApiError: invalid claim: missing sub claim
      at /home/alexvcasillas/otpfy/otpfy-v2/node_modules/@supabase/gotrue-js/dist/main/lib/fetch.js:41:20
      at processTicksAndRejections (node:internal/process/task_queues:96:5) {
    __isAuthError: true,
    status: 401
  }
}

This is the versions that I’m currently using:

"@supabase/auth-helpers-nextjs": "^0.5.2",
"@supabase/auth-helpers-react": "^0.3.1",
"@supabase/supabase-js": "^2.1.0",

This worked before with versions:

 "@supabase/auth-helpers-nextjs": "^0.4.0-next.4",
"@supabase/auth-helpers-react": "^0.3.0-next.4",
"@supabase/supabase-js": "^2.0.0",

And I have my product live without issues https://otpfy.com, but after upgrading everything to latest, it stopped working as expected 🤔

To Reproduce

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

I’m using next for everything related

Sign in a user server-side

import { createServerSupabaseClient } from "@supabase/auth-helpers-nextjs";
import { NextApiRequest, NextApiResponse } from "next";
import { Database } from "../../types/database";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { email, password } = req.body;

  if (!email || !password) {
    res.status(400).send({ data: null });
    return;
  }

  const supabaseServerClient = createServerSupabaseClient<Database>({
    req,
    res,
  });

  const { data, error } = await supabaseServerClient.auth.signInWithPassword({
    email,
    password,
  });

  if (error) {
    res.status(500).send({ data: null, error: error.message });
    return;
  }

  res.status(200).send({ data: data });
}

Try to see if the user is authenticated after server-side:

import { createServerSupabaseClient } from "@supabase/auth-helpers-nextjs";
import { NextApiRequest, NextApiResponse } from "next";
import { fetchUser } from "../../services/user";
import { Database } from "../../types/database";

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const supabaseServerClient = createServerSupabaseClient<Database>({
    req,
    res,
  });

  const supabaseAuthToken = req.headers["supabase-auth-token"];

  if (supabaseAuthToken === "undefined") {
    res.status(401).send({ data: null });
    return;
  }

  const token = JSON.parse(supabaseAuthToken as string) as SupabaseAuthToken;

  await supabaseServerClient.auth.setSession(token);

  const {
    data: { user },
    error,
  } = await supabaseServerClient.auth.getUser(token.access_token);

  if (error) {
    res.status(401).send({ data: null });
    return;
  }

  if (user) {
    const userData = await fetchUser(user.id, supabaseServerClient);

    res.status(200).send({ data: userData ?? null });
    return;
  }

  res.status(500).send({ data: null });
}

Expected behavior

I should be able to sign up a user and work fine as expected

System information

  • OS: Windows
  • Browser (if applies): Chrome 107.0.5304.107
  • Version of supabase-js: 2.1.0
  • Version of Node.js: 16.15.0 on the local environment. 16.x on Vercel

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 8
  • Comments: 19 (6 by maintainers)

Most upvoted comments

Too bad to see that this error still exists. Especially when making a POST request inside a Next js /app dir.

How do we get around this? 🤔

Not sure if this is a perfect solution, but I made some option-changes to the call to createClient, which seems to have fixed the issue for me.

const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    autoRefreshToken: false, // All my Supabase access is from server, so no need to refresh the token
    detectSessionInUrl: false, // We are not using OAuth, so we don't need this. Also, we are manually "detecting" the session in the server-side code
    persistSession: false, // All our access is from server, so no need to persist the session to browser's local storage
  },

It seems that after I added persistSession: false the getUser call no longer resulted in an error.

Yeah, there’s no good docs on this. For getUser() to work server-side, persistSession needs to be false.

Not sure if this is a perfect solution, but I made some option-changes to the call to createClient, which seems to have fixed the issue for me.

const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    autoRefreshToken: false, // All my Supabase access is from server, so no need to refresh the token
    detectSessionInUrl: false, // We are not using OAuth, so we don't need this. Also, we are manually "detecting" the session in the server-side code
    persistSession: false, // All our access is from server, so no need to persist the session to browser's local storage
  },

It seems that after I added persistSession: false the getUser call no longer resulted in an error.

@Spiralis @j4w8n thanks for bringing this up folks, I’m passing this as a feedback to the Auth team 😃

After following this example from the auth-helpers repository I managed to have all these features fully working and working like I charm I have to say.

Having upgraded to Next 13 seemed to be the root cause of this issue as now I can see that the supabase-auth-token is looking the same way as the one that I pointed out to be “malformed” because it didn’t look like in the previous version.

I think it’s safe to close this bug report as it relates to using supabase-js the same way as we did for next<13, which now is different and I’ve figured it out thanks to the given example. Hope others can find this example helpful too 😃

I can’t reproduce the malformed credential. I’m using OAuth instead of email/pass; but I’d assume a JWT is created with the same code, no matter which is used.

As for setSession(), we’re now required to pass in a “session”. But not a whole session, just the access_token and refresh_token. The docs have not been fully updated to reflect this, but the Parameters section is correct.

const { data, error } = await client.auth.setSession({ access_token: 'value', refresh_token: 'value' })

Any workarounds? Using supabase on both server and client log-outs users from time to time, really annoying 😦 @awalias can we please reopen this issue since the error still occurs?

I ran into this issue tonight, getting invalid claim: missing sub claim, and ended up on this issue so I’ll add my solution here for intrepid explorers 😄

I’m doing something similar to what was mentioned above with a client that logs in via Supabase, then sends the access/refresh tokens to a separate server.

The problem for me was that client.auth.setSession returns a Promise! I was just missing an await

const supabaseUserClient = createClient(supabaseUrl, supabasePublicKey, {
  auth: {
    autoRefreshToken: false,
    persistSession: false,
  },
});

// you have to `await` this promise for the session to actually be set!
await supabaseUserClient.auth.setSession({
  access_token: accessToken,
  refresh_token: refreshToken,
});

return supabaseUserClient;

No problem @soedirgo. What I said is only true if you don’t pass an access token into getUser. Because in that case, it calls getSession; which is where things really breakdown.

Gary opened an issue about it https://github.com/supabase/gotrue-js/issues/539

There are also issues with using setSession on the server side without setting persistSession to false. Not because of that method’s direct code, but because it calls _saveSession and it’s code causes issues.

I’m having that issue using supabase with expo. When I set persistSession: false authentication works but I definitely want to persist my session. Any idea if there is another way to fix?

I was getting this error because I have an Express backend and want to attach the user object in Express middleware. I am passing the accessToken / refreshToken via the serverSideRendering docs.

In the middlware, I was calling

const supabase = createClient<Database>(
      SUPABASE_URL ?? "",
      SUPABASE_ANON_KEY ?? "",
    );
supabase.auth.setSession(/*tokens*/)
// ...
supabse.auth.getUser() // returned the invalid claim error

Adding this flag (as referenced above) seems to have fixed it:

      // Without this flag, we get "invalid subclaim" error on supabase.auth.getUser
const supabase = createClient<Database>(
      SUPABASE_URL ?? "",
      SUPABASE_ANON_KEY ?? "",
      {
        auth: {
          persistSession: false,
        },
      }
    );

I am having the same issue. But I am not in NextJS-land.

I am working on a server-side Solid Start solution, using cookies. Having said that the reproducing sample is just about identical.

The only difference is that I am getting the access_token and refresh_token from a cookie.

The weird thing is that the setSession call completes fine (I am passing an object with the access and header tokens). It even returns the session-data and the error property is indeed null.

The error comes from the getUser() call. And it is exactly the same as described in this issue.

A few days ago when I started working on the code, it was working too (AFAICT). I first then started to see this a few times and now it is there every time.

Is it necessary to even call getUser? The samples all show the setSession call without using the returned data, which includes the user. Can’t I just use the user returned?

In this app I am not exposing the supabase-client at all in the browser. All the code is run server-side. I am creating a secure cookie that contains the access and refresh tokens (encrypted). So, every request from the client to the server will pass that cookie and I will then be able to authenticate the user (via setSession).

Should I then update the refresh-token in my cookie after the call, if it has changed? As I believe that it may have been rotated - and I guess it should not be reused.