Providers
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;
}
| Member | Required | Description |
|---|---|---|
id | yes | Unique lowercase slug within an AuthyraClient |
name | no | Human-readable label — defaults to id |
type | no | Strategy discriminant — used in logging and validation |
supportsRefresh | no | Opt-in to silent token renewal. Default: false |
supportsSignOut | no | Opt-in to server-side revocation on sign-out. Default: false |
signIn | yes | Authenticate the user; return null on credential failure |
signOut | no | Revoke server-side credentials — only called when supportsSignOut: true |
refreshToken | no | Exchange 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
| Provider | Package | Type | PKCE | Client secret |
|---|---|---|---|---|
CredentialsProvider | authyra | credentials | — | — |
CredentialsProvider.withTokens | authyra | credentials | — | — |
OAuth2Provider | authyra_flutter | oauth2 | ✓ (default) | optional |
GoogleProvider | authyra_flutter | oauth2 | ✓ | optional |
GitHubOAuth2Provider | authyra_flutter | oauth2 | ✗ | required |
AppleProvider | authyra_flutter | oauth2 | ✓ | ES256 JWT |
ProxyOAuthProvider | authyra_flutter | oauth2 | — | server-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
nullon credential failure:AuthyraClientconverts anullreturn fromsignInintoAuthenticationFailedException. - Throw for infrastructure errors: network failures, CSRF mismatches, and invalid config should throw an
AuthyraExceptionsubclass — not returnnull. signOutmust never throw: catch and log errors internally to avoid blocking local session cleanup.- Populate token fields when available: leaving
accessTokenasnullpreventsgetAccessToken()and silent refresh from working.