Skip to content

feat(auth): add optional accessToken parameter to setSession()#1327

Open
grdsdev wants to merge 6 commits intomainfrom
guilherme/sdk-784-featauth-add-setsession-support-for-both-access_token-and
Open

feat(auth): add optional accessToken parameter to setSession()#1327
grdsdev wants to merge 6 commits intomainfrom
guilherme/sdk-784-featauth-add-setsession-support-for-both-access_token-and

Conversation

@grdsdev
Copy link
Copy Markdown
Contributor

@grdsdev grdsdev commented Mar 24, 2026

Summary

Adds an optional `accessToken` named parameter to `setSession()` in `GoTrueClient`, mirroring the JS SDK behaviour.

When both tokens are provided and the access token is still valid, the session is restored by calling `GET /user` with the supplied access token — skipping the `POST /token?grant_type=refresh_token` round-trip. If the access token is expired or omitted, the existing `_callRefreshToken` path is unchanged.

Changes

  • `gotrue_client.dart`: extended `setSession(refreshToken, {String? accessToken})` with JWT expiry check, `getUser` call, and `_saveSession` + `signedIn` event when token is valid; concurrent fast-path calls are deduplicated via `_refreshTokenCompleter`; `expiresIn` is now derived from `exp - iat` (full token lifetime) instead of remaining seconds
  • `client_test.dart`: 4 new tests covering valid dual-token path, expired fallback, empty token, and malformed JWT; error tests use idiomatic `throwsA`

Behaviour matrix

Call Network call Event fired
`setSession(rt)` `POST /token` (refresh) `tokenRefreshed`
`setSession(rt, accessToken: validAt)` `GET /user` only `signedIn`
`setSession(rt, accessToken: expiredAt)` `POST /token` (refresh) `tokenRefreshed`
`setSession(rt, accessToken: '')` None throws `AuthSessionMissingException`
`setSession(rt, accessToken: 'bad')` None throws `AuthInvalidJwtException`

Test plan

  • `Set session` — existing test, no regression
  • `Set session with an empty refresh token` — existing test, no regression
  • `Set session with both access token and refresh token skips network refresh`
  • `Set session with expired access token falls back to refresh token`
  • `Set session with empty access token throws AuthSessionMissingException`
  • `Set session with malformed access token throws AuthInvalidJwtException`
  • All 42 tests pass against live GoTrue instance

Closes: SDK-784
Fixes: #1298


🤖 Generated with Claude Code /take

Mirrors the JS SDK behaviour: when both tokens are supplied and the
access token has not yet expired, the session is restored directly
(via a getUser() call) without an extra /token refresh round-trip.
If the access token is expired or omitted the existing _callRefreshToken
path is preserved unchanged.

- Valid, non-expired accessToken: calls getUser(accessToken), builds
  Session locally, fires AuthChangeEvent.signedIn.
- Expired accessToken: falls back to _callRefreshToken(refreshToken).
- Malformed accessToken: throws AuthInvalidJwtException.
- Empty accessToken: throws AuthSessionMissingException.

Linear: SDK-784

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 24, 2026 11:52
@github-actions github-actions bot added the auth This issue or pull request is related to authentication label Mar 24, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the GoTrue Dart client’s setSession() API to optionally accept an accessToken alongside a refreshToken, allowing session restoration via /user when the access token is still valid (mirroring supabase-js behavior) and falling back to the existing refresh-token flow otherwise.

Changes:

  • Added accessToken optional named parameter to GoTrueClient.setSession() with JWT expiry check + /user fetch path.
  • Persisted session + emitted SIGNED_IN when restoring directly from provided tokens.
  • Added tests for dual-token success path, expired-token fallback, and invalid/empty access token errors.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/gotrue/lib/src/gotrue_client.dart Adds the optional accessToken path to setSession() with JWT exp check, getUser call, and session persistence/event emission.
packages/gotrue/test/client_test.dart Adds integration tests covering the new setSession(refreshToken, accessToken: ...) behaviors and error cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

Copilot AI commented Mar 25, 2026

@grdsdev I've opened a new pull request, #1331, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Copy Markdown

Copilot AI commented Mar 25, 2026

@grdsdev I've opened a new pull request, #1332, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 5 commits March 25, 2026 11:40
* Initial plan

* docs: clarify setSession skips /token refresh but still calls /user

Co-authored-by: grdsdev <5923044+grdsdev@users.noreply.github.com>
Agent-Logs-Url: https://github.com/supabase/supabase-flutter/sessions/db643f38-73ec-4b87-92e3-9d995d319a73

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: grdsdev <5923044+grdsdev@users.noreply.github.com>
…n() (#1331)

* Initial plan

* fix(auth): apply expiryMargin buffer to hasExpired check in setSession()

Co-authored-by: grdsdev <5923044+grdsdev@users.noreply.github.com>
Agent-Logs-Url: https://github.com/supabase/supabase-flutter/sessions/80e8e775-47d3-4e08-aa75-07f715c27411

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: grdsdev <5923044+grdsdev@users.noreply.github.com>
- Use exp - iat for expiresIn (full token lifetime, not remaining seconds)
- Deduplicate concurrent fast-path calls via _refreshTokenCompleter
- Use idiomatic throwsA in error-throwing tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New set_session_test.dart with 10 mock-based tests covering:
  - Validation order (empty RT checked before AT)
  - Expiry margin boundary (exp within 30 s treated as expired)
  - JWT without exp claim falls back to refresh
  - expiresIn = exp - iat when both claims present
  - expiresIn = null when iat absent
  - expiresAt matches JWT exp
  - Preserved access/refresh token values
  - Fast path fires signedIn, fallback fires tokenRefreshed
  - Concurrent calls deduplicated to one /user request
  - signedIn fires exactly once under concurrent load
- Fix remaining try/catch in client_test.dart to use throwsA

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dart_jsonwebtoken auto-injects an iat claim when signing, which broke:
- 'expiresIn equals exp minus iat' (iat value was overridden)
- 'expiresIn is null when iat absent' (iat was auto-added)

Replace dart_jsonwebtoken usage with _makeRawJwt(), which base64url-encodes
the payload map directly (no auto-claims, no signature verification needed).
Also removes the dart_jsonwebtoken import from the test file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

Question: Set session using both access_token and refresh_token

3 participants