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
  AuthProviderType get type => AuthProviderType.custom;
  bool get supportsRefresh => false;
  bool get supportsSignOut => false;

  Future<AuthSignInResult?> signIn({Map<String, dynamic>? 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
typenoStrategy discriminant — used in logging and validation
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

Provider types

AuthProviderType communicates the authentication strategy to AuthyraClient:

enum AuthProviderType {
  credentials,  // email/password or any form-based flow
  oauth2,       // OAuth 2.0 / OIDC Authorization Code
  magicLink,    // passwordless email flow
  phone,        // SMS one-time-password
  custom,       // anything else
}

Result types

AuthSignInResult

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

class AuthSignInResult {
  final AuthUser  user;
  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 in AuthSession, enabling getAccessToken() and silent refresh.

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

ProviderPackageTypePKCEClient secret
CredentialsProviderauthyracredentials
CredentialsProvider.withTokensauthyracredentials
OAuth2Providerauthyra_flutteroauth2✓ (default)optional
GoogleProviderauthyra_flutteroauth2optional
GitHubOAuth2Providerauthyra_flutteroauth2required
AppleProviderauthyra_flutteroauth2ES256 JWT
ProxyOAuthProviderauthyra_flutteroauth2server-side

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
  AuthProviderType get type => AuthProviderType.credentials;

  @override
  bool get supportsRefresh => true;

  @override
  bool get supportsSignOut => true;

  @override
  Future<AuthSignInResult?> signIn({Map<String, dynamic>? params}) async {
    final res = await myApi.post('/auth/login', body: params);
    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.

See also

Copyright © 2026