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

autoRefreshToken doesn't work #620

Open
nuurcodes opened this issue May 24, 2021 · 27 comments
Open

autoRefreshToken doesn't work #620

nuurcodes opened this issue May 24, 2021 · 27 comments
Assignees
Labels
bug Something isn't working released

Comments

@nuurcodes
Copy link

nuurcodes commented May 24, 2021

Bug report

Describe the bug

After initialising supabase client with

createClient(supabaseUrl, supabaseKey, {
  localStorage: AsyncStorage as any,
  autoRefreshToken: true
});

and setting the JWT expiry to 60 (seconds) on the supabase dashboard, the token does not refresh after 60 seconds have elapsed. Oddly enough, setting the JWT expiry to anything less than 60 triggers the token refresh but it constantly updates without waiting for expiry

To Reproduce

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

  1. Setup RN project (I used expo bare)
  2. Set JWT expiry to 60
  3. Add supabase auth state listener
  4. Sign in
  5. Wait for refresh token to update (doesn't update)

Expected behavior

The token should refresh after the expiry time set on the supabase dashboard

System information

  • OS: macOS
  • Version of supabase-js: 1.11.14
  • Version of Node.js: 16.1.0
@nuurcodes nuurcodes added the bug Something isn't working label May 24, 2021
@awalias
Copy link
Member

awalias commented Jun 7, 2021

hey @nuurcodes thanks for the issue, could you please test if you can reproduce with the latest supabase-js version v1.13.1

@nuurcodes
Copy link
Author

nuurcodes commented Jun 7, 2021

@awalias Setting the JWT expiry to anything less than 60 still causes the accessToken to update in a loop on v1.13.1

When I set the JWT expiry to 60, I was expecting the accessToken to automatically update every 60 seconds but it doesn't. I've tried logging supabase.auth.session() and also listening to onAuthStateChange

@churichard
Copy link

Having the same issue here. I'm constantly needing to log back into my app, even though I've set the expiration date to be a week long and have autoRefreshToken set to true. It seems like I need to re-enter my credentials even if the token is not actually expired (i.e. I need to re-enter my credentials even though a week has not passed since the last time I did it). This happens both on localhost and on my production website.

If autoRefreshToken is true, I would expect the token to be automatically refreshed without needing to enter my credentials again. In other words, if I've logged in once, I should stay logged in unless I've explicitly logged out or cleared my cookies/cache.

@heauton
Copy link

heauton commented Jun 13, 2021

Shouldn't this be on by default? https://github.com/supabase/supabase-js/blob/master/src/SupabaseClient.ts#L11-L12

I still see this issue on 1.15.0.

@kiwicopple
Copy link
Member

🎉 This issue has been resolved in version 1.15.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@b2m9
Copy link

b2m9 commented Dec 2, 2021

Sorry to dig out an old issue, but are we sure this is fixed? Despite initializing the Supabase client with autoRefreshToken: true I have to double check session.expires_at and initiate supabase.auth.refreshSession() manually throughout my app. (on version 1.28.2)

There isn't much documentation on how autoRefreshToken is supposed to work. But judging from the questions in the discussions section, I'm not the only one who is having trouble letting Supabase refresh expired JWTs.

@bangdragon
Copy link

Sorry to dig out an old issue, but are we sure this is fixed? Despite initializing the Supabase client with autoRefreshToken: true I have to double check session.expires_at and initiate supabase.auth.refreshSession() manually throughout my app. (on version 1.28.2)

There isn't much documentation on how autoRefreshToken is supposed to work. But judging from the questions in the discussions section, I'm not the only one who is having trouble letting Supabase refresh expired JWTs.

How you catch session expires and and initiate supabase.auth.refreshSession() manually. Thanks

@ARMATAV
Copy link

ARMATAV commented Jul 12, 2022

Still broken. autoRefreshToken setting does nothing to actually trigger a refresh and you will keep setting the error in perpetuity until you manually reload the page.

image

@edgarsilva
Copy link

edgarsilva commented Feb 2, 2023

Yeah this seems to still be happening, at least when running Supabase locally for development, working on a React Native app using Expo ->

LOG  SB Res --> {"data": {"session": null}, "error": [AuthApiError: Invalid Refresh Token]}

Trying to figure out how long before it expires, and supabase.auth.refreshToken() also throws an error once it has expired.

@erickreutz
Copy link

Can confirm this is also happening for me.

@kangmingtay kangmingtay transferred this issue from supabase/supabase-js Mar 1, 2023
@kangmingtay kangmingtay reopened this Mar 1, 2023
@kangmingtay
Copy link
Member

Hey everyone, we're investigating this issue and it would be great if yall can provide the supabase-js / gotrue-js versions that you experience this error on.

@uze
Copy link

uze commented Mar 7, 2023

Hey everyone, we're investigating this issue and it would be great if yall can provide the supabase-js / gotrue-js versions that you experience this error on.

Version: "@supabase/supabase-js": "^2.1.3"

@optinforce
Copy link

optinforce commented Mar 12, 2023

Experiencing this with these versions:

supabase-js: 2.10.0
gotrue-js: 2.13.0

UPDATE: For my case, I have found that the problem actually comes from nuxt-supabase:
nuxt-modules/supabase#137 (comment)

@shimi-chans-tree
Copy link

shimi-chans-tree commented May 18, 2023

When I left the app running on the iOS simulator for a while and reloaded Metro, I encountered the error
Invalid Refresh Token: Already Used

@supabase/supabase-js: "^2.22.0"
expo: "^48.0.10" (bare workflow)

@huksley
Copy link

huksley commented May 22, 2023

I am also getting "Invalid Refresh Token: Already Used"
@supabase/supabase-js: "^2.21.0"

I don't see previous invocations which might have used the token.
And btw, isn't the refresh token supposed to be constant and used only to reissue access tokens as needed?

@lewisd1996
Copy link

Also getting this in my RN project, constantly being signed out in a matter of minutes, anything we can do here?

@vbylen
Copy link

vbylen commented May 29, 2023

Have you tried setting the jwt_expiry in config.toml to one week (the maximum allowed):

jwt_expiry = 604800

@kangmingtay
Copy link
Member

Hey everyone, i'm attempt to reproduce this issue using the expo user management example (https://github.com/supabase/supabase/tree/master/examples/user-management/expo-user-management) but i haven't been able to reproduce this. It would be great if someone has a repo to share or can outline the reproducible steps.

I'm running on "@supabase/supabase-js": "^2.24.0", and this is how i'm initialising the supabase client:

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: ExpoSecureStoreAdapter as any,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
});

@kangmingtay kangmingtay self-assigned this May 31, 2023
@claycoleman
Copy link

Yeah this seems to still be happening, at least when running Supabase locally for development, working on a React Native app using Expo ->

LOG  SB Res --> {"data": {"session": null}, "error": [AuthApiError: Invalid Refresh Token]}

Trying to figure out how long before it expires, and supabase.auth.refreshToken() also throws an error once it has expired.

This response describes the issue pretty similarly to me – I'm running supabase locally using npx supabase start, my client is initialized just like yours is @kangmingtay but with local supabase url and anon key.

I have a supabase.auth.onAuthStateChange which runs on app init and whenever the auth state changes. typically the repro case is logging in then waiting until the token expires, usually this happens while the app simulator / the app is in the background on my mac. in this case, my onAuthStateChange fires and receives a session with a jwt and refresh token. This triggers a fetch from my profiles table, but that request fails with {"code": "PGRST301", "details": null, "hint": null, "message": "JWT expired"}. subsequent reloads of the app also fire onAuthStateChange with what looks like a valid session but the same error occurs when trying to fetch data from supabase. my guess is that this session is coming from the secure store.

cc @lewisd1996 @b2m9 @edgarsilva @uze for your repro cases?

@claycoleman
Copy link

fwiw, the refresh token that is the session with the expired jwt is still valid according to my local db. i looked this up using the auth.refresh_tokens table. so my issue is slightly different from some of the above responses where even calling refreshToken() errors out. my client is being inited with autoRefreshToken: true though, so also confused why supabase isn't refreshing it automatically! supabase version 2.25.0

@swyxio
Copy link

swyxio commented Jul 24, 2023

getting AuthApiError: Invalid Refresh Token: Refresh Token Not Found right now as well; not sure if this is the same issue

image

@MilesV64
Copy link

MilesV64 commented Aug 1, 2023

We're also getting the Refresh Token: Refresh Token Not Found issue every time we try to refresh.

@JeremyMees
Copy link

The issue was resolved after I updated the Supabase Nuxt package to ^0.3.1. Additionally, I had to clear my existing Supabase cookies, which allowed the functionality to work as intended.

@lorsk
Copy link

lorsk commented Oct 3, 2023

Only way I am able to resolve this issue is uninstalling the app and installing it again. This occurs in local development and not in production.

Using React native with @supabase/supabase-js: ^2.37.0

@sebastiangrebe
Copy link

sebastiangrebe commented Apr 27, 2024

Same happening for me.
"@supabase/supabase-js": "^2.42.7"

Here my supabase client setup:

import { Database } from '../../../../supabase/types'
import { createClient } from '@supabase/supabase-js'
import * as SecureStore from 'expo-secure-store'
import AsyncStorage from "@react-native-async-storage/async-storage";

import * as aesjs from 'aes-js';
import 'react-native-get-random-values';

import { replaceLocalhost } from '../getLocalhost.native'

if (!process.env.EXPO_PUBLIC_SUPABASE_URL) {
  throw new Error(
    `EXPO_PUBLIC_SUPABASE_URL is not set. Please update the root .env.local and restart the server.`
  )
}

if (!process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY) {
  throw new Error(
    `EXPO_PUBLIC_SUPABASE_ANON_KEY is not set. Please update the root .env.local and restart the server.`
  )
}

const supabaseUrl = replaceLocalhost(process.env.EXPO_PUBLIC_SUPABASE_URL)

// As Expo's SecureStore does not support values larger than 2048
// bytes, an AES-256 key is generated and stored in SecureStore, while
// it is used to encrypt/decrypt values stored in AsyncStorage.
class LargeSecureStore {
  private async _encrypt(key: string, value: string) {
    const encryptionKey = crypto.getRandomValues(new Uint8Array(256 / 8));

    const cipher = new aesjs.ModeOfOperation.ctr(encryptionKey, new aesjs.Counter(1));
    const encryptedBytes = cipher.encrypt(aesjs.utils.utf8.toBytes(value));

    await SecureStore.setItemAsync(key, aesjs.utils.hex.fromBytes(encryptionKey));

    return aesjs.utils.hex.fromBytes(encryptedBytes);
  }

  private async _decrypt(key: string, value: string) {
    const encryptionKeyHex = await SecureStore.getItemAsync(key);
    if (!encryptionKeyHex) {
      return encryptionKeyHex;
    }

    const cipher = new aesjs.ModeOfOperation.ctr(aesjs.utils.hex.toBytes(encryptionKeyHex), new aesjs.Counter(1));
    const decryptedBytes = cipher.decrypt(aesjs.utils.hex.toBytes(value));

    return aesjs.utils.utf8.fromBytes(decryptedBytes);
  }

  async getItem(key: string) {
    const encrypted = await AsyncStorage.getItem(key);
    if (!encrypted) { return encrypted; }

    return await this._decrypt(key, encrypted);
  }

  async removeItem(key: string) {
    await AsyncStorage.removeItem(key);
    await SecureStore.deleteItemAsync(key);
  }

  async setItem(key: string, value: string) {
    const encrypted = await this._encrypt(key, value);

    await AsyncStorage.setItem(key, encrypted);
  }
}

export const supabase = createClient<Database>(
  supabaseUrl,
  process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY,
  {
    auth: {
      storage: new LargeSecureStore(),
      autoRefreshToken: true,
      persistSession: true,
      detectSessionInUrl: false,
    },
  }
)

Plus this one for handling foreground / background handling but even with or without no difference:

AppState.addEventListener('change', (state) => {
  if (state === 'active') {
    supabase.auth.startAutoRefresh()
  } else {
    supabase.auth.stopAutoRefresh()
  }
})

Majorly I dont see any events or tries of refreshing the token.

Moreover default 1 hour of course stays longer logged in but setting 50, 60 or 120 seconds mostly logs me out after good 20 seconds.
Can someone official have a look into this as it seems to be a big UX blocker?

@xl0
Copy link

xl0 commented Aug 15, 2024

I'm getting the same thing. To reproduce more easily, I set the token to 10 seconds.
I'm running with supabase/ssr server-only with SvelteKit, a single supabase client in locals that is used throughout the request:

export const handle: Handle = async ({ event, resolve }) => {
	event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
		auth: { debug: true },
		cookies: {
			get: (key) => {
				const value = event.cookies.get(key);
				// debug('cookies get', { key, value });
				return value;
			},
			set: (key, value, options) => {
				// debug('cookies set', { key, value, options });
				event.cookies.set(key, value, { ...options, path: options.path ?? '/' });
			},
			remove: (key, options) => {
				// debug('cookies remove', { key, options });
				event.cookies.delete(key, { ...options, path: options.path ?? '/' });
			}
		}
	});


	event.locals.safeGetSession = async () => {
		const {
			data: { session }
		} = await event.locals.supabase.auth.getSession();
		if (!session) {
			debug('No session found');
			return { session: null, user: null };
		}

		const {
			data: { user },
			error
		} = await event.locals.supabase.auth.getUser();
		if (error) {
			debug('Error getting user: ', error);
			// JWT validation has failed
			return { session: null, user: null };
		}

		return { session, user };
	};

	const { session, user } = await event.locals.safeGetSession();
	event.locals.session = session ?? undefined;
	event.locals.user = user ?? undefined;

        [ ... ]

Here is the traceback:

  app:hooks:supabase Error getting user: AuthApiError: invalid JWT: unable to parse or verify signature, token has invalid claims: token is expired
    at handleError (/home/xl0/work/projects/llms/assistant/node_modules/@supabase/auth-js/dist/main/lib/fetch.js:63:11)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async _handleRequest (/home/xl0/work/projects/llms/assistant/node_modules/@supabase/auth-js/dist/main/lib/fetch.js:108:9)
    at async _request (/home/xl0/work/projects/llms/assistant/node_modules/@supabase/auth-js/dist/main/lib/fetch.js:89:18)
    at async /home/xl0/work/projects/llms/assistant/node_modules/@supabase/auth-js/dist/main/GoTrueClient.js:874:24
    at async SupabaseAuthClient._useSession (/home/xl0/work/projects/llms/assistant/node_modules/@supabase/auth-js/dist/main/GoTrueClient.js:774:20)
    at async SupabaseAuthClient._getUser (/home/xl0/work/projects/llms/assistant/node_modules/@supabase/auth-js/dist/main/GoTrueClient.js:864:20)
    at async /home/xl0/work/projects/llms/assistant/node_modules/@supabase/auth-js/dist/main/GoTrueClient.js:851:20 {
  __isAuthError: true,
  status: 403,
  code: 'bad_jwt'
} +8m

I turned on auth: {debug: true}, will see if I can catch another one.

@xl0
Copy link

xl0 commented Aug 16, 2024

Ok, I think I know what's going on, at least in my case. There is a race condition between the check for the session expiration and when the session is used to get the user:
node_modules/@supabase/auth-js/src/GoTrueClient.ts:

      const hasExpired = currentSession.expires_at
        ? currentSession.expires_at <= Date.now() / 1000
        : false

      this._debug(
        '#__loadSession()',
        `session has${hasExpired ? '' : ' not'} expired`,
        'expires_at',
        currentSession.expires_at
      )

      if (!hasExpired) {
              [ return the session ]
      [ refresh the session ]

If the session is expiring in the new milliseconds, this causes the exception. I think an easy workaround would be to add an arbitrary or configurable offset to the session expiration check and refresh a session that expires very soon.

@kiwicopple @awalias

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

No branches or pull requests