Core Concepts
Callbacks
AuthCallbacks middleware, intercept and block authentication actions before they run.
AuthCallbacks is the one mechanism in Authyra that can block an operation before it runs. Every hook returns a CallbackResult: .allow() lets the operation proceed, .deny(reason) aborts it and surfaces AuthenticationFailedException to the caller.
This is different from
AuthyraPlugin and AuthEventBus, both of which only observe after the fact and can't stop anything. See Events → for that distinction.Registering callbacks
Subclass AuthCallbacks and override only the hooks you need; everything defaults to allow().
class MyAuthCallbacks extends AuthCallbacks {
@override
Future<CallbackResult> onBeforeSignIn(
String providerName,
AuthSignInParams? params,
) async {
if (await isRateLimited(providerName)) {
return const CallbackResult.deny('Too many sign-in attempts');
}
return const CallbackResult.allow();
}
}
final client = AuthyraClient(
providers: [...],
storage: SecureAuthStorage(),
callbacks: MyAuthCallbacks(),
);
Hooks
| Hook | Called before | Typical use |
|---|---|---|
onBeforeSignIn(providerName, params) | The provider processes a sign-in | Rate limiting, IP/device blocking |
onBeforeSessionCreate(user, providerName) | A new session is written to storage (after the provider confirms identity) | Verify the user exists in your own DB, enforce an allow-list domain |
onBeforeSignOut(user) | Sign-out of the active account is processed | Prompt to save unsaved changes; use sparingly, blocking sign-out is rarely good UX |
onBeforeAccountSwitch(fromUser, toUserId) | AccountManager.switchTo() activates a different account | Verify the target account is allowed in the current context |
onBeforeTokenRefresh(user, refreshToken) | A refresh token is exchanged, manual or background | Check a server-side revocation list, rate-limit refresh frequency |
onBeforeAccountRemove(user) | AccountManager.signOut(userId) removes an account from the registry | Require confirmation, prevent removing the last account |
Every hook is async and can call your backend before deciding.
CallbackResult
class CallbackResult {
const CallbackResult.allow([Map<String, dynamic>? data]);
const CallbackResult.deny(String errorMessage, [Map<String, dynamic>? data]);
bool get isAllowed;
bool get isDenied;
}
data lets a hook attach context the caller can read back, for example enriching SessionMetadata from onBeforeSessionCreate.
What a denial does
| Hook | On deny |
|---|---|
onBeforeSignIn, onBeforeSessionCreate | signIn() throws AuthenticationFailedException; nothing is persisted |
onBeforeSignOut | signOut() throws AuthenticationFailedException; the session is untouched |
onBeforeAccountSwitch | accounts.switchTo() throws AuthenticationFailedException; the active account is unchanged |
onBeforeAccountRemove | accounts.signOut(userId) throws AuthenticationFailedException; the account stays registered |
onBeforeTokenRefresh | The refresh is treated as non-retryable (RefreshDeniedException internally); the session expires immediately instead of being silently renewed |
Example: gate token refresh against a revocation list
class RevocationAwareCallbacks extends AuthCallbacks {
final RevocationStore _store;
RevocationAwareCallbacks(this._store);
@override
Future<CallbackResult> onBeforeTokenRefresh(
AuthUser user,
String refreshToken,
) async {
if (await _store.isRevoked(refreshToken)) {
return const CallbackResult.deny('Refresh token revoked');
}
return const CallbackResult.allow();
}
}