Core Concepts

Providers

The AuthProvider interface, strategy types, sign-in, sign-out, and token refresh.

Every authentication strategy in Authyra implements the AuthProvider interface. AuthyraClient registers providers by id and delegates signIn, signOut, and refreshToken to the matching provider at runtime.


AuthProvider interface

abstract class AuthProvider {
  String get id;                           // unique slug, e.g. 'google'
  String get name => id;                   // display name for logs/UI
  bool get supportsRefresh => false;
  bool get supportsSignOut => false;

  Future<AuthSignInResult?> signIn({AuthSignInParams? params});
  Future<void>              signOut({String? userId}) async {}
  Future<AuthTokenResult?>  refreshToken(String refreshToken) async => null;
}
MemberRequiredDescription
idyesUnique lowercase slug within an AuthyraClient
namenoHuman-readable label, defaults to id
supportsRefreshnoOpt-in to silent token renewal. Default: false
supportsSignOutnoOpt-in to server-side revocation on sign-out. Default: false
signInyesAuthenticate the user; return null on credential failure
signOutnoRevoke server-side credentials, only called when supportsSignOut: true
refreshTokennoExchange a refresh token for new tokens, only called when supportsRefresh: true
There's no MagicLinkSignInParams or PhoneSignInParams shipped yet. Build a passwordless or OTP flow today by extending AuthSignInParams yourself and implementing AuthProvider directly, see Custom Provider →.

Result types

AuthSignInResult

Returned by signIn. Carries the user profile and any tokens the provider received.

class AuthSignInResult {
  final AuthUser     user;
  final AuthAccount? account;      // omit it, AuthyraClient builds a minimal one
  final String?      accessToken;
  final String?      refreshToken;
  final DateTime?    expiresAt;
}

Tokens are optional, omit them for server-side session flows (cookie auth) where tokens are managed opaquely by the backend. When present, AuthyraClient stores them on the session's active AuthAccount, enabling getAccessToken() and silent refresh.

accessToken/refreshToken must be application session tokens, never a raw identity-provider credential that still needs exchanging (a Google idToken, an Apple identityToken). A provider that only acquires such a credential should put it in AuthAccount.providerData instead and leave these fields null. See authyra_apple's AppleProvider for an example built around exactly this distinction.

AuthTokenResult

Returned by refreshToken. Token-only, the existing user profile is preserved.

class AuthTokenResult {
  final String    accessToken;
  final String?   refreshToken;  // null = keep existing refresh token
  final DateTime  expiresAt;
}

Built-in providers

ProviderPackageStrategyPKCEClient secret
CredentialsProviderauthyraForm-basedn/an/a
CredentialsProvider.withTokensauthyraForm-basedn/an/a
OAuth2Providerauthyra_flutterBrowser OAuth2✓ (default)optional
GoogleProviderauthyra_flutterBrowser OAuth2✓optional
GitHubProviderauthyra_flutterBrowser OAuth2✗required
ProxyOAuthProviderauthyra_flutterBackend-delegatedn/aserver-side
AppleProviderauthyra_appleNative SDKn/anone needed in-app

Implementing a custom provider

Implement AuthProvider directly for any strategy Authyra doesn't ship:

class MyApiProvider implements AuthProvider {
  @override
  String get id => 'my-api';

  @override
  bool get supportsRefresh => true;

  @override
  bool get supportsSignOut => true;

  @override
  Future<AuthSignInResult?> signIn({AuthSignInParams? params}) async {
    final creds = params is CredentialsSignInParams ? params : null;
    final res = await myApi.post('/auth/login', body: {
      'email': creds?.email,
      'password': creds?.password,
    });
    if (res.statusCode == 401) return null;  // wrong credentials
    if (res.statusCode != 200) throw AuthenticationFailedException(
      'Login failed: HTTP ${res.statusCode}',
      providerName: id,
    );
    return AuthSignInResult(
      user: AuthUser(
        id:    res.data['id'],
        email: res.data['email'],
        name:  res.data['name'],
      ),
      accessToken:  res.data['accessToken'],
      refreshToken: res.data['refreshToken'],
      expiresAt:    DateTime.parse(res.data['expiresAt']),
    );
  }

  @override
  Future<AuthTokenResult?> refreshToken(String refreshToken) async {
    final res = await myApi.post('/auth/refresh',
        body: {'refreshToken': refreshToken});
    if (res.statusCode != 200) return null;
    return AuthTokenResult(
      accessToken: res.data['accessToken'],
      expiresAt:   DateTime.parse(res.data['expiresAt']),
      // refreshToken omitted → client keeps the existing one
    );
  }

  @override
  Future<void> signOut({String? userId}) async {
    // Fire-and-forget, never throw here
    await myApi.post('/auth/logout').catchError((_) {});
  }
}

Register it in AuthyraClient like any other provider:

AuthyraClient(
  providers: [MyApiProvider()],
  storage: SecureAuthStorage(),
)

Provider design rules

  • Stateless: providers hold configuration, never session state. Session state lives in SessionManager.
  • Return null on credential failure: AuthyraClient converts a null return from signIn into AuthenticationFailedException.
  • Throw for infrastructure errors: network failures, CSRF mismatches, and invalid config should throw an AuthyraException subclass, not return null.
  • signOut must never throw: catch and log errors internally to avoid blocking local session cleanup.
  • Populate token fields when available: leaving accessToken as null prevents getAccessToken() and silent refresh from working.
Copyright © 2026