Skip to content

feat(auth): post a notification when Auth signs out the user automatically - #16858

Draft
paulb777 wants to merge 3 commits into
mainfrom
pb-auth-automatic-sign-out-notification
Draft

paulb777 wants to merge 3 commits into
mainfrom
pb-auth-automatic-sign-out-notification

Conversation

@paulb777

@paulb777 paulb777 commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Summary

Add Auth.automaticSignOutNotification, which is posted when Auth signs out
the current user because the backend reported that the user or the session is
no longer valid. Its userInfo has the error that caused the sign-out and the
ID of the user who was signed out.

Fixes #16855. This adds public API, so it needs API review.

Why

Auth signs the user out when a request or token refresh fails with
userNotFound, userDisabled, invalidUserToken or userTokenExpired. Until
now, apps only got authStateDidChangeNotification with no current user, the
same as after signOut(). The error reached the app only if the app's own call
had caused the sign-out. Refreshes started by other Firebase SDKs through
AuthInterop, or by the automatic token refresh, gave the app nothing. In
#16498, users are signed out unexpectedly in production, and the app can't tell
an SDK sign-out from other causes.

Changes

  • New public constants on Auth, next to authStateDidChangeNotification:
    automaticSignOutNotification, automaticSignOutErrorKey and
    automaticSignOutUserIDKey.
  • New internal Auth.signOutAutomatically(withUserID:error:). On
    kAuthGlobalWorkQueue, it signs out like signOutByForce(withUserID:), then
    posts the notification on the main queue.
  • User.signOutIfTokenIsInvalid(withError:) uses it. That covers both request
    errors and the token refresh path (internalGetTokenAsync), including
    refreshes for AuthInterop and the automatic token refresh.
  • signOut() and User.delete() are unchanged and don't post it.

API proposal

// In `Auth`:
public static let automaticSignOutNotification =
  NSNotification.Name(rawValue: "FIRAuthAutomaticSignOutNotification")
public static let automaticSignOutErrorKey = "FIRAuthAutomaticSignOutErrorKey"
public static let automaticSignOutUserIDKey = "FIRAuthAutomaticSignOutUserIDKey"

Usage:

NotificationCenter.default.addObserver(forName: Auth.automaticSignOutNotification,
                                       object: Auth.auth(), queue: .main) { notification in
  let error = notification.userInfo?[Auth.automaticSignOutErrorKey] as? Error
  let userID = notification.userInfo?[Auth.automaticSignOutUserIDKey] as? String
  // Log it, or tell the user why they were signed out.
}

Notes for reviewers

  • API review options: this follows authStateDidChangeNotification. The
    API guidelines prefer Sendable APIs without Any, so one alternative is an
    AsyncSequence like authStateChanges (for example automaticSignOuts)
    that yields a Sendable struct. Another option is an Objective-C constant
    like FIRAuthStateDidChangeNotification. This PR is Swift-only.
  • Ordering: the notification is posted on the main queue after the auth
    state change notification for the sign-out, which updateCurrentUser has
    already queued. A test checks this order.
  • Threading: signOutAutomatically runs synchronously on
    kAuthGlobalWorkQueue, like signOut() (from review). So the sign-out still
    finishes before the failed call reports its error. None of its callers run on
    that queue. signOutByForce, now used only by delete(), still runs on the
    caller's thread, as before this PR.
  • Keychain errors: with byForce, the user is signed out even if removing
    the stored user fails, so the notification is still posted.
  • With the fix for Auth signs the user out when a request's ID token is rejected, without checking the session #16854: invalidUserToken and userTokenExpired from
    requests trigger a refresh instead. If the session has ended, the
    notification has the refresh error.

Testing

  • New tests in UserTests:
    • testAutomaticSignOutNotificationAfterRequestError: updatePassword fails
      with USER_DISABLED. The notification is posted on the main thread, after
      the state change, with the error and the user ID, and the user is signed
      out.
    • testAutomaticSignOutNotificationAfterTokenRefreshError: a forced
      getIDTokenResult fails with TOKEN_EXPIRED from the securetoken
      endpoint.
    • testSignOutDoesNotPostAutomaticSignOutNotification and
      testDeleteDoesNotPostAutomaticSignOutNotification.
  • Check that the tests catch the bug: with the old signOutByForce call
    temporarily restored, both positive tests fail. With User.delete()
    temporarily routed through signOutAutomatically, the delete test fails.
  • xcodebuild -scheme AuthUnit -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.5' IPHONEOS_DEPLOYMENT_TARGET=15.0 -test-timeouts-enabled YES -default-test-execution-time-allowance 60 test: 359 tests, 0 failures.
  • After the review change, same command: 358 of 359 passed, including the four
    new tests. AuthAPNSTokenManagerTests.testGetTokenTimeoutWithMultipleWaiters
    hung and was stopped. That's a race in AuthAPNSTokenManager, which this PR
    doesn't touch.

Related

Also found while triaging #16498: #16853 (listener's first call can be nil)
and #16854 (sign-out when a request's ID token is rejected).

…cally

When a request or token refresh failed with userNotFound, userDisabled,
invalidUserToken or userTokenExpired, Auth signed the user out and only
posted authStateDidChangeNotification, the same as for signOut(). Apps
couldn't tell why the user was signed out unless their own call had
failed. Refreshes started by other Firebase SDKs or by the automatic token
refresh gave them no error at all.

Post Auth.automaticSignOutNotification after the sign-out, with the error
and the user ID in userInfo. signOut() and User.delete() don't post it.

Fixes #16855
@gemini-code-assist

Copy link
Copy Markdown
Contributor
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

@paulb777

paulb777 commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Auth.automaticSignOutNotification and its associated keys, which are posted when a user is automatically signed out due to backend errors. It updates the automatic sign-out flow to use the new signOutAutomatically method and adds unit tests to verify this behavior. Feedback highlights a thread-safety issue in signOutAutomatically(withUserID:error:) where _currentUser and updateCurrentUser are accessed outside of the serial kAuthGlobalWorkQueue, potentially causing data races. It is recommended to wrap this logic in kAuthGlobalWorkQueue.async as suggested.

Comment thread FirebaseAuth/Sources/Swift/Auth/Auth.swift
Run `signOutAutomatically(withUserID:error:)` synchronously on
`kAuthGlobalWorkQueue`, where `_currentUser` is read and written. Before, it
ran on the caller's thread, which could race with the work queue. Using
`sync` keeps the sign-out finished before the caller reports the error.
@paulb777

paulb777 commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces Auth.automaticSignOutNotification and associated keys to notify observers when a user is automatically signed out due to an invalid session or backend error. It updates the sign-out flow to use the new signOutAutomatically(withUserID:error:) method and adds comprehensive unit tests. The feedback suggests adding a dispatch precondition to signOutAutomatically to prevent potential deadlocks if called from kAuthGlobalWorkQueue.

Comment thread FirebaseAuth/Sources/Swift/Auth/Auth.swift
@paulb777
paulb777 marked this pull request as ready for review October 6, 2026 21:07
@paulb777
paulb777 marked this pull request as draft October 6, 2026 21:09
@paulb777

paulb777 commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

Keeping it a draft PR until we determine if we want to do the API change.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR]: Tell apps why Auth signed the user out automatically

1 participant