Skip to content

refactor(auth)!: return Session from the auth methods that always issue one - #1845

Merged
spydon merged 2 commits into
mainfrom
lukasklingsbo/sdk-1874-return-session-from-auth-methods
Sep 17, 2026
Merged

spydon merged 2 commits into
mainfrom
lukasklingsbo/sdk-1874-return-session-from-auth-methods

Conversation

@spydon

@spydon spydon commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Summary

AuthResponse has a nullable session and a nullable user, and every signing-in method on AuthClient returned it. For most of those methods a null session is not a reachable state, so callers wrote response.session! for an outcome that cannot happen, and three call sites in the client asserted the same invariant at runtime before using ! themselves.

Resolves SDK-1874.

Return Session directly

  • signInAnonymously, signInWithPassword, signInWithIdToken, signInWithWeb3, linkIdentityWithIdToken, refreshSession, setSession and recoverSession on AuthClient.
  • AuthPasskeyApi.verifyAuthentication, and with it the supabase_flutter extensions signInWithPasskey and signInWithRestoreKey.
  • The internal refresh path (_refreshAccessToken, _callRefreshToken, _doRefresh and the pending refresh completers) carries a Session as well.

A token response without a session now throws AuthException('The server response did not contain a session.') from one private _sessionFromResponse helper, replacing the three throw then ! blocks and the two methods that silently resolved with session: null.

Kept on AuthResponse

signUp and verifyOTP, since both can legitimately complete without a session: a sign-up that needs email confirmation first, and the first step of a secure email or phone change. The AuthResponse dartdoc now says so.

Server behaviour checked

Verified against the gotrue source that the tightened methods cannot get a 200 without a token: SignupAnonymously, the password grant and PasskeyAuthenticationVerify all end in issueRefreshToken and sendJSON(w, http.StatusOK, token).

Tests and docs

  • Test call sites read the session off the return value directly; expect(x.session, isNotNull) assertions that the type now guarantees are dropped, and the supabase_test signInTestUser helper loses its !.
  • MIGRATION.md gains a section listing the affected methods with a before and after snippet.
  • The supabase_flutter README social sign-in snippets and the supabase_auth example return Session.

No public symbols are added or removed, so sdk-compliance.yaml is unchanged.

Out of scope

Making AuthState a sealed class and folding AuthChangeEvent into it. Much larger blast radius on onAuthStateChange, discussed separately.

Summary by CodeRabbit

  • API Updates

    • Authentication methods that establish a session now return Session directly, including sign-in, refresh, recovery, passkey, Web3, restore credential, and session management flows.
    • These methods throw an authentication error when the server response does not include a session.
    • signUp() and verifyOTP() continue returning AuthResponse because they may complete without creating a session.
  • Documentation

    • Updated authentication examples and API descriptions to reflect direct Session responses.

@spydon
spydon requested a review from a team as a code owner September 17, 2026 09:17
@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Warning

Review limit reached

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 41ad38b9-402b-4b03-a9ae-42d887d5f7e2

📥 Commits

Reviewing files that changed from the base of the PR and between 4bc9701 and 5e96871.

📒 Files selected for processing (24)
  • MIGRATION.md
  • examples/authentication/lib/auth_repository.dart
  • packages/supabase_auth/example/main.dart
  • packages/supabase_auth/lib/src/auth_client.dart
  • packages/supabase_auth/lib/src/auth_passkey_api.dart
  • packages/supabase_auth/lib/src/types/auth_response.dart
  • packages/supabase_auth/test/client_test.dart
  • packages/supabase_auth/test/missing_session_response_test.dart
  • packages/supabase_auth/test/otp_mock_test.dart
  • packages/supabase_auth/test/passkey_test.dart
  • packages/supabase_auth/test/refresh_token_race_test.dart
  • packages/supabase_auth/test/session_persistence_test.dart
  • packages/supabase_auth/test/src/auth_mfa_api_test.dart
  • packages/supabase_auth/test/src/auth_oauth_api_test.dart
  • packages/supabase_auth/test/src/set_session_test.dart
  • packages/supabase_auth/test/src/token_refresh_race_test.dart
  • packages/supabase_auth/test/web3_auth_integration_test.dart
  • packages/supabase_auth/test/web3_auth_test.dart
  • packages/supabase_flutter/README.md
  • packages/supabase_flutter/lib/src/supabase_passkey.dart
  • packages/supabase_flutter/lib/src/supabase_restore_credential.dart
  • packages/supabase_flutter/test/restore_credential_test.dart
  • packages/supabase_test/lib/src/test_supabase_client.dart
  • packages/supabase_test/test/mock_supabase_http_client_test.dart

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 0229fe7c-a8b9-4661-a112-8ab58285e44a

📥 Commits

Reviewing files that changed from the base of the PR and between 14dd823 and 4bc9701.

📒 Files selected for processing (24)
  • MIGRATION.md
  • examples/authentication/lib/auth_repository.dart
  • packages/supabase_auth/example/main.dart
  • packages/supabase_auth/lib/src/auth_client.dart
  • packages/supabase_auth/lib/src/auth_passkey_api.dart
  • packages/supabase_auth/lib/src/types/auth_response.dart
  • packages/supabase_auth/test/client_test.dart
  • packages/supabase_auth/test/missing_session_response_test.dart
  • packages/supabase_auth/test/otp_mock_test.dart
  • packages/supabase_auth/test/passkey_test.dart
  • packages/supabase_auth/test/refresh_token_race_test.dart
  • packages/supabase_auth/test/session_persistence_test.dart
  • packages/supabase_auth/test/src/auth_mfa_api_test.dart
  • packages/supabase_auth/test/src/auth_oauth_api_test.dart
  • packages/supabase_auth/test/src/set_session_test.dart
  • packages/supabase_auth/test/src/token_refresh_race_test.dart
  • packages/supabase_auth/test/web3_auth_integration_test.dart
  • packages/supabase_auth/test/web3_auth_test.dart
  • packages/supabase_flutter/README.md
  • packages/supabase_flutter/lib/src/supabase_passkey.dart
  • packages/supabase_flutter/lib/src/supabase_restore_credential.dart
  • packages/supabase_flutter/test/restore_credential_test.dart
  • packages/supabase_test/lib/src/test_supabase_client.dart
  • packages/supabase_test/test/mock_supabase_http_client_test.dart
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/supabase_auth/test/src/token_refresh_race_test.dart
  • packages/supabase_flutter/README.md
  • MIGRATION.md
  • packages/supabase_auth/lib/src/types/auth_response.dart

Included review availability: Your plan provides up to 4 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

Authentication methods that always issue a session now return Session directly. Missing sessions raise AuthException. Passkey, Flutter, example, documentation, and test call sites now use the direct result. signUp() and verifyOTP() retain AuthResponse.

Changes

Direct session return contract

Layer / File(s) Summary
AuthClient session flow
packages/supabase_auth/lib/src/auth_client.dart, packages/supabase_auth/lib/src/types/auth_response.dart, MIGRATION.md
Sign-in, refresh, recovery, session-setting, and identity-linking methods now return Session. _sessionFromResponse handles session extraction and missing-session errors.
Passkey and SDK integration
packages/supabase_auth/lib/src/auth_passkey_api.dart, packages/supabase_flutter/lib/src/*, examples/authentication/lib/auth_repository.dart, packages/supabase_auth/example/main.dart, packages/supabase_flutter/README.md
Passkey, restore-credential, repository, example, and Flutter API call sites now expose or consume direct Session results.
Call-site and behavior validation
packages/supabase_auth/test/*, packages/supabase_flutter/test/*, packages/supabase_test/*
Tests and test helpers now assert session fields directly and preserve coverage for refresh, recovery, persistence, passkey, Web3, OAuth, and race behavior.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthClient
  participant SessionStorage
  Client->>AuthClient: Call authentication method
  AuthClient->>AuthClient: Parse Session
  AuthClient->>SessionStorage: Save Session
  AuthClient-->>Client: Return Session
Loading

Suggested reviewers: grdsdev

Merge Risk: ⚪ Minimal · up to 4bc97

The breaking authentication API change includes migration guidance and updated consumers; no concrete current-head defect remains.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the breaking refactor that changes applicable auth methods to return Session directly.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI 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.

🟡 Changes recommended

The missing-session branch lacks coverage, migration wording is inaccurate, and affected changelogs are absent.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Refactors authentication APIs that always establish a session to return Session directly instead of nullable AuthResponse.

Changes:

  • Updates auth, passkey, restore-key, and refresh APIs.
  • Migrates callers, examples, documentation, and tests.
  • Centralizes missing-session validation.
File summaries
File Description
MIGRATION.md Documents the breaking API migration.
examples/authentication/lib/auth_repository.dart Updates repository return types.
packages/supabase_auth/example/main.dart Uses the returned session directly.
packages/supabase_auth/lib/src/auth_client.dart Refactors auth and refresh methods.
packages/supabase_auth/lib/src/auth_passkey_api.dart Returns Session from passkey verification.
packages/supabase_auth/lib/src/types/auth_response.dart Clarifies remaining response uses.
packages/supabase_auth/test/client_test.dart Updates core auth tests.
packages/supabase_auth/test/otp_mock_test.dart Updates password sign-in assertions.
packages/supabase_auth/test/passkey_test.dart Updates passkey tests.
packages/supabase_auth/test/refresh_token_race_test.dart Updates refresh race assertions.
packages/supabase_auth/test/session_persistence_test.dart Updates persistence assertions.
packages/supabase_auth/test/src/auth_mfa_api_test.dart Updates refresh-session usage.
packages/supabase_auth/test/src/auth_oauth_api_test.dart Updates login helper typing.
packages/supabase_auth/test/src/set_session_test.dart Updates session-setting tests.
packages/supabase_auth/test/src/token_refresh_race_test.dart Updates refresh deduplication tests.
packages/supabase_auth/test/web3_auth_integration_test.dart Updates Web3 integration assertions.
packages/supabase_auth/test/web3_auth_test.dart Updates Web3 unit tests.
packages/supabase_flutter/README.md Updates social sign-in examples.
packages/supabase_flutter/lib/src/supabase_passkey.dart Changes passkey extension return type.
packages/supabase_flutter/lib/src/supabase_restore_credential.dart Changes restore-key return type.
packages/supabase_flutter/test/restore_credential_test.dart Updates restore-key tests.
packages/supabase_test/lib/src/test_supabase_client.dart Simplifies the sign-in test helper.
packages/supabase_test/test/mock_supabase_http_client_test.dart Updates mock sign-in assertions.
Review details
  • Files reviewed: 23/23 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/supabase_auth/lib/src/auth_client.dart
Comment thread MIGRATION.md
Comment thread MIGRATION.md Outdated
@spydon
spydon added this pull request to stack #1847 September 17, 2026 10:05

@QuintinWillison QuintinWillison left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't really understand what I'm looking at, but is refactor the right nature of this change, because to my untrained (in Flutter/Dart) eyes the AuthResponse to Session rename looks breaking in nature... or is this explained by it being part of a wider refactor you're in the middle of (wider context)?

@spydon

spydon commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

I don't really understand what I'm looking at, but is refactor the right nature of this change, because to my untrained (in Flutter/Dart) eyes the AuthResponse to Session rename looks breaking in nature... or is this explained by it being part of a wider refactor you're in the middle of (wider context)?

It is a breaking refactor, that is what the ! in the conventional commit standard indicates.

@spydon
spydon force-pushed the lukasklingsbo/sdk-1874-return-session-from-auth-methods branch from 14dd823 to 4bc9701 Compare September 17, 2026 15:10
@Vinzent03

Copy link
Copy Markdown
Collaborator

Let's say I sign up, but get no session because confirmation is required. What would be the result of signing in with password into that user? Would I get a user back from the server, which would throw an exception now because no session exists?

@spydon

spydon commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Let's say I sign up, but get no session because confirmation is required. What would be the result of signing in with password into that user? Would I get a user back from the server, which would throw an exception now because no session exists?

signInWithPassword will get a 400 with error_code: email_not_confirmed, and the client will throw an AuthApiException with errorCode == ErrorCode.emailNotConfirmed.code and status 400. So this behavior does not change, it's the same as it was for v2.

…ue one

signInAnonymously, signInWithPassword, signInWithIdToken, signInWithWeb3,
linkIdentityWithIdToken, refreshSession, setSession, recoverSession and
AuthPasskeyApi.verifyAuthentication (and with it signInWithPasskey and
signInWithRestoreKey) return the Session directly instead of an
AuthResponse with a nullable session. A token response without a session
throws an AuthException instead of resolving with session set to null.

signUp and verifyOTP keep returning AuthResponse, as both can legitimately
complete without a session.
@spydon
spydon force-pushed the lukasklingsbo/sdk-1874-return-session-from-auth-methods branch from 4bc9701 to 5e96871 Compare September 17, 2026 15:46
@spydon
spydon merged commit b63898e into main Sep 17, 2026
40 checks passed
@spydon
spydon deleted the lukasklingsbo/sdk-1874-return-session-from-auth-methods branch September 17, 2026 16:37
spydon added a commit that referenced this pull request Sep 17, 2026
#1846)

## Summary

Stacked on #1845. `AuthState` was one class for every `AuthChangeEvent`,
so its payload rules lived in dartdoc: `session` is non-null for every
event except `initialSession` and `signedOut`, and `signOutReason` is
only set on `signedOut`. Listeners wrote `state.session!` and read a
field that is `null` for six of the seven events.

Resolves SDK-1876.

### The hierarchy

`AuthState` is sealed with one subtype per event, each carrying exactly
the data that event produces:

| Subtype | `session` | Extra |
| --- | --- | --- |
| `AuthInitialSession` | `Session?` | |
| `AuthSignedIn` | `Session` | |
| `AuthSignedOut` | always `null` | `SignOutReason? reason` |
| `AuthTokenRefreshed` | `Session` | |
| `AuthUserUpdated` | `Session` | |
| `AuthPasswordRecovery` | `Session` | |
| `AuthMfaChallengeVerified` | `Session` | |

```dart
switch (state) {
  case AuthSignedIn(:final session):
    showHome(session.user);
  case AuthSignedOut(reason: SignOutReason.sessionExpired):
    showSessionExpired();
  ...
}
```

### What stays

`AuthChangeEvent`, and the `event`, `session` and `fromBroadcast`
getters on the base type. The enum still maps to the wire strings the
cross-tab broadcast uses, and keeping `state.event ==
AuthChangeEvent.signedIn` compiling means most apps only have to move
`signOutReason` to `AuthSignedOut.reason`. `SupabaseClient` and the
existing tests keep using `event` unchanged, which is the evidence for
that claim.

### What breaks

- `AuthState.signOutReason` is `AuthSignedOut.reason`.
- `AuthState` has no public constructor; construct the subtype.

### Client changes

`notifyAllSubscribers` builds the state through a private
`_authStateFor` switch over the event. Every local emit already saves
the session first (checked each call site, `updateUser` throws
`AuthSessionMissingException` before it gets there), so the only way to
reach a session-carrying event without a session is a malformed
cross-tab broadcast. That case is now dropped with a warning instead of
emitted, where it used to surface as a `signedIn` with `session: null`.

## Tests and docs

- New `auth_state_test.dart` against `MockSupabaseHttpClient`: initial,
sign in, refresh, user update, sign out and late-subscriber cases assert
the subtype, its payload and `event`, and an exhaustive `switch`
expression over the hierarchy fails to compile if a subtype is added
without handling.
- The three `signOutReason` assertions use
`isA<AuthSignedOut>().having(...)`.
- `MIGRATION.md` section with before and after, the `AuthState` dartdoc
carries the same example, and the `supabase_flutter` README listen
snippet shows the switch.
- The seven subtypes and their members are registered under
`auth.session.subscribe_auth_events` in `sdk-compliance.yaml`, and
`auth.session.sign_out_reason` points at `AuthSignedOut.reason`. Symbol,
drift and schema checks pass locally against the pinned
`capability-matrix-v1.9.0`.

Full `supabase_auth` suite (578 with the new file), `supabase`,
`supabase_flutter` and `supabase_test` pass. `dart analyze` and DCM are
clean at warning level.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Auth state changes now use event-specific types, enabling exhaustive
pattern matching.
  * Sign-out reasons are available through `AuthSignedOut.reason`.
* Authenticated events provide session data, while signed-out events
have no session.
* **Bug Fixes**
  * User-update notifications are suppressed when no session is present.
  * Events requiring a session are no longer emitted without one.
* **Documentation**
* Updated migration guidance and listener examples for the new auth
state API.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants