Skip to content

feat(gotrue): surface the sign-out reason on the signedOut event#1453

Open
spydon wants to merge 8 commits into
fix/recover-session-duplicate-auth-errorfrom
feat/signed-out-reason
Open

feat(gotrue): surface the sign-out reason on the signedOut event#1453
spydon wants to merge 8 commits into
fix/recover-session-duplicate-auth-errorfrom
feat/signed-out-reason

Conversation

@spydon

@spydon spydon commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

Adds AuthState.exception, populated when the user is signed out involuntarily because the session could not be recovered (an invalid or expired refresh token). It is null for an explicit signOut() and for every event other than signedOut.

Stacked on top of #1450. Base branch is fix/recover-session-duplicate-auth-error; review/merge that one first. The diff here is only the follow-up commit.

Why

Follow-up to the discussion on #1450. That PR stops recoverSession from double-reporting an expected sign-out as an uncaught stream error. The remaining concern was that a pure onAuthStateChange listener then sees signedOut without knowing why the user was signed out, which removes the ability to handle it differently or notify the user.

This restores that capability through the event itself, rather than through an error that only reaches listeners with an onError handler:

supabase.auth.onAuthStateChange.listen((state) {
  if (state.event == AuthChangeEvent.signedOut && state.exception != null) {
    // involuntary sign out (e.g. expired session) -> show a message
  }
});

How

  • AuthState gains a nullable exception field.
  • notifyAllSubscribers accepts an optional exception and threads it onto the AuthState.
  • _executeRefresh passes the causing AuthException when it signs the user out on a non-retryable refresh failure.

The exception is intentionally not propagated across tabs via the broadcast channel, so it is always null when fromBroadcast is true.

Tests

  • signedOut event carries the exception on an invalid refresh token.
  • signedOut event has no exception on an explicit signOut.

Add AuthState.exception, populated when the user is signed out involuntarily
because the session could not be recovered (an invalid or expired refresh
token). It is null for an explicit signOut and for all other events, letting
listeners tell the two apart without attaching an onError handler.
@spydon spydon requested a review from a team as a code owner June 22, 2026 13:38
@github-actions github-actions Bot added the auth This issue or pull request is related to authentication label Jun 22, 2026
@Vinzent03

Copy link
Copy Markdown
Collaborator

Adding an exception field to the class feels a bit strange, if we simultaneously use errors on the stream itself. But I cannot come up with a better solution right now. I thought about an enum at first as well like you did here: #1450 (comment), but that is probably hard to properly maintain and this solution is more general.
However, I think you've missed the sign out reasons in the recover method, right? They only have a human message and no special code to handle. Might be interesting to create a code that we use across similar issues in the recover method?

spydon added 2 commits June 22, 2026 23:08
The missing-data and session-expired paths called notifyException explicitly
and then threw, and the surrounding catch notified again, so two errors hit
onAuthStateChange. Throw without the explicit notify and let the catch be the
single notification point. Add a test asserting exactly one error is emitted.
Replaces the `AuthState.exception` field with a typed `SignOutReason`
(userInitiated, sessionExpired, sessionMissing) so listeners can tell an
explicit sign out apart from an involuntary one without inspecting an
exception. The reason is threaded through `notifyAllSubscribers` and set on
every local sign-out path; it is still not broadcast across tabs.

The involuntary recoverSession/setInitialSession exceptions now also carry
stable `session_expired` / `session_missing` codes (added to `ErrorCode`,
which is now exported) so the same reason is available on the stream error.

Registers the new public symbols in sdk-compliance.yaml.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth This issue or pull request is related to authentication

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants