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

HookCalled beforeTypical use
onBeforeSignIn(providerName, params)The provider processes a sign-inRate 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 processedPrompt to save unsaved changes; use sparingly, blocking sign-out is rarely good UX
onBeforeAccountSwitch(fromUser, toUserId)AccountManager.switchTo() activates a different accountVerify the target account is allowed in the current context
onBeforeTokenRefresh(user, refreshToken)A refresh token is exchanged, manual or backgroundCheck a server-side revocation list, rate-limit refresh frequency
onBeforeAccountRemove(user)AccountManager.signOut(userId) removes an account from the registryRequire 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

HookOn deny
onBeforeSignIn, onBeforeSessionCreatesignIn() throws AuthenticationFailedException; nothing is persisted
onBeforeSignOutsignOut() throws AuthenticationFailedException; the session is untouched
onBeforeAccountSwitchaccounts.switchTo() throws AuthenticationFailedException; the active account is unchanged
onBeforeAccountRemoveaccounts.signOut(userId) throws AuthenticationFailedException; the account stays registered
onBeforeTokenRefreshThe 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();
  }
}

See also

Copyright © 2026