[Bug] Desktop Auth: LinkWithCredential (and Reload) never persist the updated user; user is restored as the pre-link anonymous user and signed out after restart
I maintainer di solito rispondono entro 3 giorni
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 55/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- cpp
- Ambito
- authentication
Direzione di ricerca
Start with auth/src/desktop/user_desktop.cc and auth/src/desktop/authentication_result.cc, following the link paths and persistence calls described in the issue. Check existing desktop Auth tests for link, reload, and persisted-user behavior, then run the relevant tests. Done means successful linking persists the updated user across restart, with regression coverage for the reported stale state.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
[REQUIRED] Please fill in the following fields:
- Pre-built SDK from the website or open-source from this repo: Pre-built (
firebase_cpp_sdk_windows_13.5.0.zip, downloaded by FlutterFire'sfirebase_core4.10.0 Windows plugin;firebase_authplugin 6.5.2). The relevant code is unchanged on currentmain(0fe9d97). - Firebase C++ SDK version: 13.5.0
- Problematic Firebase Component: Auth
- Other Firebase Components in use: App, Firestore (not involved)
- Platform you are using the C++ SDK on: Windows
- Platform you are targeting: desktop (Windows)
[REQUIRED] Please describe the issue here:
On desktop, linking a credential to the signed-in user (same uid) updates the in-memory user but never writes it to the persisted user data. After the next process start, the SDK restores the stale pre-link state: the user is still anonymous, has no linked provider, and has the old tokens. For an email/password link, the restored session is then rejected by the backend and the SDK signs the user out. So an anonymous user who upgrades their account by linking is signed out on the next launch. We see this in production on Windows.
Why it happens (current main, 0fe9d97a8e067418a607db17016948671a0b6fa7)
-
UserDataPersistis registered only as anAuthStateListenerand saves only fromOnAuthStateChanged:- https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/auth_desktop.cc#L638-L646
auth_data->auth->AddAuthStateListener(auth_impl->user_data_persist.get()); - https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/user_desktop.cc#L454-L460
- https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/auth_desktop.cc#L638-L646
-
AuthenticationResult::SetAsCurrentUsernotifies auth-state listeners only when the uid changes. A link keeps the uid, so the persister is never called:- https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/authentication_result.cc#L58-L63
if (previous_user.uid != uid()) { NotifyAuthStateListeners(auth_data); } if (previous_user.id_token != id_token()) { NotifyIdTokenListeners(auth_data); }
- https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/authentication_result.cc#L58-L63
-
The only other save path,
TriggerSaveUserFlow, is called only fromPerformSetAccountInfoFlow(UpdateUserProfile,UpdatePassword,UpdateEmail, ...) and fromPerformReauthFlow:- https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/user_desktop.cc#L240-L245
- https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/user_desktop.cc#L273-L278
- https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/user_desktop.cc#L404-L409
-
Neither link path calls it:
- Email/password:
DoLinkWithEmailAndPassword→PerformSignUpFlow. That function even callsNotifyIdTokenListenersexplicitly "since this is used to upgrade anonymous accounts", but the persister is not an ID-token listener:
https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/user_desktop.cc#L247-L265 - Other providers: the
verifyAssertionlambda inDoLinkCredential:
https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/user_desktop.cc#L357-L377
- Email/password:
-
User::Reload()(the callback comment says "No listeners will be notified") and token refresh inEnsureFreshTokendon't save either:
So after an anonymous user links email/password, the persisted data still says is_anonymous = true, has no email/password provider, and holds the pre-link ID and refresh tokens. As far as we can observe, setting a password on the account invalidates the previously issued tokens on the backend. After a restart:
- the first
Reload()fails withkAuthErrorUserTokenExpired, andSignOutIfUserNoLongerValidsigns the user out
(https://github.com/firebase/firebase-cpp-sdk/blob/0fe9d97a8e067418a607db17016948671a0b6fa7/auth/src/desktop/user_desktop.cc#L86-L94); - without a
Reload(), the same sign-out happens once the cached ID token expires andEnsureFreshTokentries the stale refresh token (L140-L146).
For an OAuth link, the restored user is reported as anonymous with no linked provider until something reloads it.
Steps to reproduce:
Have you been able to reproduce this issue with just the Firebase C++ quickstarts ?
Not with the quickstart. We hit it through the FlutterFire Windows plugin, which calls User::LinkWithCredential directly. The snippet below is the same sequence of calls in plain C++.
What's the issue repro rate? (eg 100%, 1/5 etc)
100% for the stale persisted state (it follows from the code paths above). The sign-out after an email/password link happens on every launch we have looked at.
What happened? How can we make the problem occur?
SignInAnonymously(). The user is persisted (the uid changed, so the persister ran).current_user().LinkWithCredential(EmailAuthProvider::GetCredential(email, password)). This succeeds; in memory the user is no longer anonymous and has thepasswordprovider.- Restart the process (or destroy
Auth/Appand create them again) and wait for the persisted user to load. current_user()has the same uid butis_anonymous() == true, an emptyemail()and nopasswordinprovider_data().Reload()fails withkAuthErrorUserTokenExpiredand the user is signed out.
Expected: a successful link (and arguably Reload() and a token refresh) persists the updated user, the same way sign-in, UpdateUserProfile and reauthentication do. After the restart the user is the linked, non-anonymous account and stays signed in.
Suggested fix: call TriggerSaveUserFlow(handle->auth_data) after a successful SetAsCurrentUser in PerformSignUpFlow (when used for linking) and in the verifyAssertion link lambda in DoLinkCredential. The same probably applies to Unlink, to Reload() after MergeToCurrentUser, and to EnsureFreshToken when has_token_changed. Another option is to have UserDataPersist also save on ID-token changes.
Workaround: after a successful link, call UpdateUserProfile with an unchanged display name. It goes through PerformSetAccountInfoFlow → TriggerSaveUserFlow and writes the current user to disk.
Relevant Code:
#include "firebase/app.h"
#include "firebase/auth.h"
using firebase::auth::Auth;
using firebase::auth::EmailAuthProvider;
// Run 1
firebase::App* app = firebase::App::Create(options);
Auth* auth = Auth::GetAuth(app);
auto anon = auth->SignInAnonymously();
// ... wait for completion; the anonymous user is persisted ...
auto link = auth->current_user().LinkWithCredential(
EmailAuthProvider::GetCredential("[email protected]", "password123"));
// ... wait for completion: error() == kAuthErrorNone,
// current_user().is_anonymous() == false, provider "password" present.
// Nothing is written to the persisted user data here.
// Run 2 (new process)
firebase::App* app2 = firebase::App::Create(options);
Auth* auth2 = Auth::GetAuth(app2);
// ... wait for the first AuthStateListener callback (persisted user loaded) ...
firebase::auth::User user = auth2->current_user();
// user.is_anonymous() == true, user.email() == "", no "password" provider
// (the pre-link state).
auto reload = user.Reload();
// ... wait: reload.error() == kAuthErrorUserTokenExpired,
// and auth2->current_user().is_valid() == false (signed out).
- Lingua principale
- C++
- Stelle
- 326
- Fork
- 137
- Merge medio
- 2g 5h
- PR unite (30g)
- 8
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di firebase/firebase-cpp-sdk
-
[FR] Support desktop Linux on aarch64/arm (#error Unknown Linux architecture in app_common.cc)Apertanew type: question
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
firebase/firebase-cpp-sdk#1926 ·
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
firebase/firebase-cpp-sdk#1905 · 2 commenti ·
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 3/5 Mezza giornata Idoneità per principianti 55/100
firebase/firebase-cpp-sdk#1936 ·
I maintainer di solito rispondono entro 3 giorni
-
[Bug] Desktop Auth token refresh ignores UseEmulator() and targets productionForse già presa @AustinBenoit l’ha presa 1 giorno fa. Apertaapi: auth type: bug
Difficoltà 3/5 1-2 giorni Idoneità per principianti 45/100
firebase/firebase-cpp-sdk#1934 · 1 commento · 1 assegnatario ·
I maintainer di solito rispondono entro 3 giorni
-
api: firestore
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
firebase/firebase-cpp-sdk#1920 · 3 commenti ·
I maintainer di solito rispondono entro 3 giorni
Tutte le issue di firebase/firebase-cpp-sdk
Issue simili
-
agent:Windows bug code-review LOW testing
Difficoltà 2/5 1-3 ore Idoneità per principianti 63/100
I maintainer di solito rispondono entro 1 giorno
-
[Cleanup] Include the public header in work_split.cppForse già presa @blozano-tt l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
tenstorrent/tt-metal#60850 ·
I maintainer di solito rispondono entro 1 giorno
-
Build: the CUDA backend fails without cuDNN instead of skipping the cuDNN shim (as the cuDF shim does)Forse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
beehive-lab/TornadoVM#1248 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
SimVascular/svZeroDSolver#253 ·
-
core: RAC_LLM_OPTIONS_DEFAULT.min_p disagrees with the IDL's annotated defaultForse già presa Una pull request collegata a questa issue è aperta o già unita. Apertabug core p3
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
RunanywhereAI/runanywhere-sdks#1083 ·
I maintainer di solito rispondono entro 5 giorni