Flutter Web - Code verifier could not be found in local storage
In flutter web, I am trying to authenticate the user by calling this:
await Supabase.instance.client.auth.signInWithOAuth(OAuthProvider.google, redirectTo: 'http://localhost:5000/login');
But I get the following error:
Error: AuthException(message: Code verifier could not be found in local storage., statusCode: null)
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 294:3 throw_
packages/gotrue/src/gotrue_client.dart 301:7 exchangeCodeForSession
What is the URL of the page that initiates the Google login process?
@dshukertjr My app is on http://localhost:5000/login when I click on the login button which opens a popup where I can sign into google or if that is already done I can choose an account. After that, I am taken back to the login page but the url has a query param called code.
Hmm, signInWithOAuth() should not open a popup, but should perform a redirect to the Google sign-in screen.
Could you share a screen recording of walking through the sign-in process?
This is really weird. When I was trying last night, it was definitely opening a popup. I just tried it now and it automatically logs me in because the chrome session already has my profile. I tried in an incognito window and it redirected me to a google sign in page.
So, in short, I am not seeing the popup. Also, the code verifier bug does not happen after signing in. If I reload the page while the code is still in the URL, then I get the error which is not really a realistic scenario as I expect to be redirected to another URL once the user has been authorized.
Side question: If I check the currentUser right after calling signInWithOAuth(), I get null. I think that is expected as the docs say that I need to listen for auth state change in the change listener. Is there a way to avoid that? I would really like to keep my business logic in one place because it is tightly coupled with some other custom logic.
There must be a way because Firebase allows it. I am not sure how they do it without ID Token though.
When I was trying last night, it was definitely opening a popup. I just tried it now and it automatically logs me in because the chrome session already has my profile. I tried in an incognito window and it redirected me to a google sign in page.
Hmm, that is weird. If it happens again, please don't hesitate to report it!
If I check the currentUser right after calling signInWithOAuth(), I get null. I think that is expected as the docs say that I need to listen for auth state change in the change listener. Is there a way to avoid that? I would really like to keep my business logic in one place because it is tightly coupled with some other custom logic.
There must be a way because Firebase allows it. I am not sure how they do it without ID Token though.
Yeah, there is a slight delay after the redirect until the session is retrieved. Currently, the only way to retrieve the session and user safely is to listen to onAuthStateChange.
So, in short, I am not seeing the popup. Also, the code verifier bug does not happen after signing in. If I reload the page while the code is still in the URL, then I get the error which is not really a realistic scenario as I expect to be redirected to another URL once the user has been authorized.
@dshukertjr This is happening to me. Like @muezz mentioned here.
@dshukertjr
same thing is happening to me and I thought I was going crazy. In localhost debug mode I intermittently get the stupid login error that code could not be found. After logging in and out / refreshing page for a few hours it goes away with no code change. I deployed to prod and encountered the issue and tried to fix for several hours only for it to randomly go away. Please reopen this.
As mentioned before this only happens on refresh. If I login and bring browser up later it works fine, but as soon as I refresh I get logged out with
at Object.throw_ [as throw] (errors.dart:296:3)
at gotrue_client.GoTrueClient.new.exchangeCodeForSession (gotrue_client.dart:345:7)
at exchangeCodeForSession.next (<anonymous>)
at async_patch.dart:45:50
at _RootZone.runUnary (zone.dart:1661:54)
at _FutureListener.thenAwait.handleValue (future_impl.dart:163:18)
at handleValueCallback (future_impl.dart:861:44)
at _Future._propagateToListeners (future_impl.dart:890:13)
at async._AsyncCallbackEntry.new.callback (future_impl.dart:472:9)
at Object._microtaskLoop (schedule_microtask.dart:40:11)
at _startMicrotaskLoop (schedule_microtask.dart:49:5)
at async_patch.dart:181:7
As mentioned before this only happens on refresh.
Where did you mention it before?
If I login and bring browser up later it works fine, but as soon as I refresh I get logged out with
Could you provide a step-by-step instruction so that I can reproduce the issue if you have a way to reproduce it reliably?
@dshukertjr Ill try to post some code samples later and versions info but I just realized something from testing
- 'https://example.com' click google sign in
- complete auth flow
- redirected back to my home page and logged in
- observe url has a code appended to it 'https://example.com?code=123xyz
- refresh page
- logged out and says code auth exception
At this point every time I login and refresh it fails with the above exception. I notice if I just manually load the url without the code appended, I am still logged in and everything works, even refreshing. e.g type in browser 'https://example.com' and I am logged in without having to click sign in since session exists.
During local dev I am redirected back to 'http://localhost:3000' but no code appended, and it works fine. Not sure why in prod with my custom domain it appends ?code and fails saying code not found.
edit: 1 way I find to reproduce this is after logging in, in the url lets say 'www.example.com' if I type 'www.example.com?code=123' it fails. Seems the code query param is having issues in one way or another.
flutter doctor -v
[✓] Flutter (Channel stable, 3.24.0, on macOS 14.5 23F79 darwin-x64, locale en-US)
• Flutter version 3.24.0 on channel stable at /Users/jacob.hong/flutter/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 80c2e84975 (3 months ago), 2024-07-30 23:06:49 +0700
• Engine revision b8800d88be
• Dart version 3.5.0
• DevTools version 2.37.2
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
• Android SDK at /Users/jacob.hong/Library/Android/sdk
• Platform android-35, build-tools 35.0.0
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 17.0.11+0-17.0.11b1207.24-11852314)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 15F31d
• CocoaPods version 1.15.2
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 2024.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 17.0.11+0-17.0.11b1207.24-11852314)
[✓] IntelliJ IDEA Community Edition (version 2024.1.4)
• IntelliJ at /Applications/IntelliJ IDEA CE.app
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
[✓] VS Code (version 1.94.0)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.98.0
[✓] Connected device (3 available)
• iPhone 15 Pro Max (mobile) • F05E1E0F-CC97-4BBC-9611-B8AE00D39362 • ios • com.apple.CoreSimulator.SimRuntime.iOS-17-5 (simulator)
• macOS (desktop) • macos • darwin-x64 • macOS 14.5 23F79 darwin-x64
• Chrome (web) • chrome • web-javascript • Google Chrome 129.0.6668.101
[✓] Network resources
• All expected network resources are available.
code to login
await Supabase.instance.client.auth.signInWithOAuth(
OAuthProvider.google,
redirectTo: const String.fromEnvironment('REDIRECT_URI'),
scopes:
"https://www.googleapis.com/auth/photoslibrary.readonly",
queryParams: {
"access_type": "offline",
"prompt": "consent",
},
);
login auth handler
_authStateSubscription =
Supabase.instance.client.auth.onAuthStateChange.listen((data) {
final event = data.event;
final session = Supabase.instance.client.auth.currentSession;
if (event == AuthChangeEvent.initialSession && session != null) {
if (kDebugMode) {
print('startup, already logged in');
}
UserService.instance.loadSession();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const MyHomePage(title: 'Home'),
),
);
} else {
// No active session, stay on login page
if (kDebugMode) {
print("No active session, staying on login page.");
}
UserService.instance.setSession(
accessToken:
Supabase.instance.client.auth.currentSession?.providerToken,
refreshToken: Supabase
.instance.client.auth.currentSession?.providerRefreshToken,
);
// }
}
if (event == AuthChangeEvent.signedIn) {
if (kIsWeb) {
setState(() {
if (kDebugMode) {
print('setting session on login');
}
UserService.instance.setSession(
accessToken:
Supabase.instance.client.auth.currentSession?.providerToken,
refreshToken: Supabase
.instance.client.auth.currentSession?.providerRefreshToken,
);
});
saveRefreshToken();
}
if (kDebugMode) {
print('redirect to hmoe after web logiun');
}
if (!mounted) return;
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const MyHomePage(
title: 'home',
),
),
);
}
if (event == AuthChangeEvent.signedOut) {
if (kDebugMode) {
print("logged out listener");
}
if (!kIsWeb) {
googleSignIn.signOut().then((_) {
if (kDebugMode) {
print('google logout');
}
if (mounted) {
// Navigator.of(context).pushReplacement(
// MaterialPageRoute(builder: (context) => const LoginPage()),
// );
}
}).catchError((error) {
print("Error during Google sign out: $error");
}); // Redirect to login page
}
UserService.instance.clearSession();
}
});
current workaround by redirecting back to home page if code is in url
void handleAuthRedirect(context) {
final uri = Uri.parse(window.location.href);
final code = uri.queryParameters['code'];
if (code != null) {
removeQueryParams();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const MyHomePage(title: 'Home'),
),
);
// Remove the code from the URL after processing
}
Any update on this issue? it's still happening on flutter web
Package: supabase_flutter: ^2.8.4.
Environment: localhost (Web)
Steps to reproduce
- Implement signin with Google following the steps in the docs
- Listen to auth changes via
supabase.auth.onAuthStateChange. - Sign in with google (works fine, and you can see the code as a query parameter in the url).
- Refresh the page
Outcome:
You are logged out and the error below is logged:
AuthException(message: Code verifier could not be found in local storage., statusCode:
null, errorCode: null)
Expected Outcome:
Session is kept and you are still logged in.
@silverhairs I don't think so. I follow the workaround provided by @jacobhong
Hi! I have just run into a similar issue. Actually I receive the same error message when I register a new user and then get redirected back to the Flutter app after clicking on the link in the confirmation email. I am using user/password authentication, not OAuth.
In my case, I am running the Flutter app in dev mode, which will open its own instance of Chrome (I am using Flutter web). And then when I receive the email and click on it, it will open my default browser, which will be a different instance of Chrome, not the same one where I registered the user. (So they will not share the local storage.)
As it happens, if I do open the confirmation link in the same browser where I registered the user, it seems to work fine, I do not get the error message saying "Code verifier could not be found in local storage".
It feels as if the Supabase library will assume that the confirmation link will be opened in the very same browser where I registered the user. I am not sure if this is really the case, but if it is, then it does not seem reasonable to me. I may be working on my desktop and may want to quickly click the confirmation link on my phone as i receive the notification about the email.
Am I right that this may be a design issue?
in a new project using a newer version of supabase 2.9.1 I don't have this issue anymore, my old project was on 2.6.0.
Hi, I also experienced this when using 0Auth supabase to login with google, is there any solution to solve this case in flutter?