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

AuthApiError: invalid claim: missing sub claim #641

Closed
alexvcasillas opened this issue Nov 16, 2022 · 23 comments
Closed

AuthApiError: invalid claim: missing sub claim #641

alexvcasillas opened this issue Nov 16, 2022 · 23 comments
Labels
bug Something isn't working

Comments

@alexvcasillas
Copy link

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
@alexvcasillas alexvcasillas added the bug Something isn't working label Nov 16, 2022
@j4w8n
Copy link
Contributor

j4w8n commented Nov 16, 2022

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' })

@alexvcasillas
Copy link
Author

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 :)

@awalias awalias closed this as completed Nov 17, 2022
@Spiralis
Copy link

Spiralis commented Nov 27, 2022

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.

@Spiralis
Copy link

Although, I haven't tried to actually do anything after the setSession call yet. That might fail just as badly as the getUser call of course... I have just been passing the user-data back to tell the browser-client that the user is logged in. Got to go now, but I guess that is something that I will need to test...

@Spiralis
Copy link

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.

@j4w8n
Copy link
Contributor

j4w8n commented Nov 27, 2022

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.

@soedirgo
Copy link
Member

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

@j4w8n
Copy link
Contributor

j4w8n commented Nov 28, 2022

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 supabase/auth-js#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.

@Spiralis
Copy link

Spiralis commented Nov 30, 2022

I have also added passing the access_code (since I have it from setSession), as I understand that this reduces the work that getUser need to do.

@j4w8n
Copy link
Contributor

j4w8n commented Nov 30, 2022

Yep, that's a good way to do it on the server side.

@jeremyisatrecharm
Copy link

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,
        },
      }
    );

@NorweskiDrwal
Copy link

NorweskiDrwal commented Jan 20, 2023

I am experiecing this issue too.

I have an nx.dev monorepo with an SPA in React sporting Vite and an express app for backend stuff (both will go inside Tauri app).

Before I landed on the invalid claim: missing sub claim problem I have been debugging a different issue also related to how I was using Supabase. I was getting util.inherits is not a function, because I was trying to use one client for both express and react app (it is a monorepo, why duplicate configs). Anyway, one guy was having an issue with the util.inherits and the underlaying problem was that he was using jsonwebtoken that doesn't want to work with Vite because some nodejs APIs are not polyfilled. I split my code into two separate clients, which solved the util.inherits is not a function bug, but it gave me the invalid claim: missing sub claim one.

Could it be that the underlying issue is in Supabasejs not communicating properly with server-based build frameworks? This library is using jsonwebtoken. The "bug progression" on my end could indicate that jsonwebtoken is missing context, since it is not polyfilled anywhere. This problem also exists for browser's storage APIs and I bet jsonwebtoken is used here to en/decode data.

Since Supabasejs is isomorphic the solution could be in polyfilling jsonwebtoken's missing node APIs or replacing jwt with eg. jose that seems to be supported across environemnts.

I have no idea if any of this applies to invalid claim: missing sub claim, but I am going to debug it some more to narrow down the problem.

@TranquilMarmot
Copy link

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;

@clemwo
Copy link

clemwo commented Oct 19, 2023

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?

@j4w8n
Copy link
Contributor

j4w8n commented Oct 19, 2023

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'd recommend opening your own issue. This one is pretty stale. You can also ask on the Discord server.

@khuongduy354
Copy link

I also encounter this problem, set persistSession to false not works for me, and also this answer link is dead, so this problem is not resolved for me. Is this isssue answered somewhere or is it not fix yet?

@clemwo
Copy link

clemwo commented Jan 17, 2024

I also encounter this problem, set persistSession to false not works for me, and also this answer link is dead, so this problem is not resolved for me. Is this isssue answered somewhere or is it not fix yet?

I'm not 100% sure I understand your problem but have you tried the solution from my comment in another issue:

#786 (comment)

@devodii
Copy link

devodii commented Mar 31, 2024

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? 🤔

@yahorbarkouski
Copy link

yahorbarkouski commented Apr 22, 2024

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?

@timobehrens
Copy link

Same issue here on Safari, working fine on Chrome.

@axhl
Copy link

axhl commented May 31, 2024

@awalias why is this closed please? This is a persistent issue.

@awalias
Copy link
Member

awalias commented May 31, 2024

I think worth opening a new issue and linking to it from here so other folks coming from Google find their way

@alexthewilde
Copy link

Same issue here on Safari, working fine on Chrome.

The same just happened to me with a Next.js app running in Safari for days in the background. For some reason that I haven't figured out yet, there was an infinite loop that kept calling /auth/v1/user again and again, receiving missing sub claim every time, until my Supabase egress quota limit was reached and I actually took notice of the issue.

According to my Auth logs, those calls stopped once I closed Safari. The network tab in Safari didn't list any of these calls though. So I suspect that there was something going back and forth between the client and my server which issued the calls to the Supabase API.

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

No branches or pull requests