Core Concepts

Sessions

AuthSession lifecycle — fields, expiry helpers, token refresh, and multi-account support.

AuthSession is the core data object that represents an authenticated user's active session. It carries the user's identity, tokens, expiry metadata, and the provider that created it.


Fields

class AuthSession {
  final String    providerId;       // 'google', 'email', etc.
  final AuthUser  user;
  final String?   accessToken;
  final String?   refreshToken;
  final DateTime? expiresAt;
  final List<String> linkedProviders;
  final DateTime  createdAt;
  final DateTime  lastUsedAt;
}
FieldDescription
providerIdSlug of the provider that created this session
userIdentity profile — id, email, name, avatarUrl, metadata
accessTokenShort-lived token for API requests. null for cookie-session flows
refreshTokenLong-lived token for silent renewal. null if not returned by the provider
expiresAtUTC expiry of accessToken. null = no known expiry
linkedProvidersAll providers linked to this account (for multi-provider users)
createdAtWhen this session was first created
lastUsedAtTimestamp of the most recent activity — used for recency sorting

Lifecycle

signIn()
    └─ provider.signIn() returns AuthSignInResult
           └─ AuthyraClient builds AuthSession
                  └─ SessionManager.saveSession(session)
                         └─ persisted to AuthStorage
                                └─ authStateChanges emits AuthState.authenticated

        ↓  (token approaching expiry)

session.shouldRefresh → true
    └─ provider.refreshToken(session.refreshToken)
           └─ session.refreshed(newAccessToken, newExpiresAt)
                  └─ SessionManager.updateSession()
                         └─ authStateChanges emits refreshed state

        ↓  (sign-out)

signOut()
    └─ provider.signOut()  [if supportsSignOut]
           └─ SessionManager.clearActiveSession()
                  └─ storage cleared
                         └─ authStateChanges emits AuthState.unauthenticated

Expiry helpers

isExpired

bool get isExpired

true when DateTime.now() is past expiresAt. Returns false when expiresAt is null.

isExpiringSoon

bool isExpiringSoon([Duration threshold = const Duration(minutes: 5)])

true when the token expires within threshold. Use for manual pre-emptive refresh:

final session = await Authyra.instance.getSession();
if (session?.isExpiringSoon(const Duration(minutes: 10)) ?? false) {
  await Authyra.instance.refreshSession();
}

shouldRefresh

bool shouldRefresh({Duration threshold = const Duration(minutes: 5)})

Semantically identical to isExpiringSoon but accepts a named parameter — used internally by AuthyraClient's auto-refresh scheduler:

if (session.shouldRefresh(threshold: config.refreshThreshold)) {
  await client.refresh(session);
}

timeUntilExpiration

Duration get timeUntilExpiration

Remaining time until the access token expires. Returns Duration.zero when already expired or when expiresAt is null.

final remaining = session?.timeUntilExpiration ?? Duration.zero;
print('Token valid for ${remaining.inMinutes} more minutes');

Token refresh

When the session should be refreshed:

  1. AuthyraClient calls provider.refreshToken(session.refreshToken).
  2. The provider returns an AuthTokenResult.
  3. The session is updated via session.refreshed(...).
  4. The new session is persisted and authStateChanges emits the updated state.
// The refreshed() helper produces an updated AuthSession:
final renewed = session.refreshed(
  newAccessToken:  result.accessToken,
  newRefreshToken: result.refreshToken,  // null = keep existing
  newExpiresAt:    result.expiresAt,
);

When refreshToken returns null (expired or revoked), the session is cleared and AuthState.unauthenticated() is emitted.


canRefresh

bool get canRefresh  // refreshToken != null && refreshToken.isNotEmpty

Quick check before attempting a refresh. Providers that don't return a refresh token (e.g., cookie-session flows) will have canRefresh: false.


Reading the session

// Async — full session object
final session = await Authyra.instance.getSession();
print('Provider: ${session?.providerId}');
print('Expires: ${session?.expiresAt}');
print('Can refresh: ${session?.canRefresh}');

// Async — just the access token (triggers auto-refresh if needed)
final token = await Authyra.instance.getAccessToken();
myHttpClient.options.headers['Authorization'] = 'Bearer $token';

Serialisation

AuthSession is serialised to/from JSON for persistence in AuthStorage. Timestamps are encoded as ISO 8601 strings:

{
  "providerId":     "google",
  "user":           { "id": "123", "email": "alice@example.com" },
  "accessToken":    "ya29.xxx",
  "refreshToken":   "1//yyy",
  "expiresAt":      "2026-03-01T12:00:00.000Z",
  "linkedProviders": ["google"],
  "createdAt":      "2026-02-01T08:00:00.000Z",
  "lastUsedAt":     "2026-02-23T10:30:00.000Z"
}
The serialised session contains access and refresh tokens. It must be stored in an encrypted backend — SecureAuthStorage in Flutter, or your own encrypted implementation for other runtimes.

Multi-account sessions

SessionRegistry maintains an in-memory map of active sessions keyed by user.id. AccountManager exposes the public API:

final accounts = Authyra.instance.accounts;

// All signed-in accounts, sorted by last activity
final users = await accounts.getAll();

// Activate a different account
await accounts.switchTo(userId);

// Sign out one account, keep others active
await accounts.signOut(userId);

// Sign out all accounts
await accounts.signOutAll();

// Remove expired sessions
await accounts.cleanExpired();

The active account limit is controlled by AuthConfig.maxAccounts (default: 5).


linkedProviders

When a user signs in with multiple providers, all provider IDs are stored in linkedProviders. Use this to render a "connected accounts" UI:

final session = await Authyra.instance.getSession();
final providers = session?.linkedProviders ?? [];

if (session?.hasLinkedProvider('github') ?? false) {
  showGitHubBadge();
}

See also

Copyright © 2026