Core Concepts
Events
AuthEventBus, listen to post-action events for analytics, audit, and monitoring.
AuthEventBus broadcasts what already happened. Unlike Callbacks →, a listener can't block or change anything, it's pure notification: analytics, audit logs, monitoring.
Each AuthyraClient owns its own bus (not a singleton), so multiple clients in the same process stay fully isolated.
Subscribing
// Typed subscription
client.events.on<SignInEvent>((e) {
analytics.track('sign_in', {'provider': e.providerName, 'new_user': e.isNewUser});
});
// Raw stream, every event, useful for a single audit sink
client.events.stream.listen((e) {
auditLog.write(e.toJson());
});
// Unsubscribe
client.events.off<SignInEvent>(myListener);
A listener runs synchronously during emit; an exception thrown inside one is caught and logged, it never propagates back to the caller that triggered the action.
Event catalogue
| Event | Emitted when |
|---|---|
SignInEvent | Sign-in succeeded. Carries user, providerName, isNewUser, duration. |
SignInFailedEvent | A sign-in attempt failed. Carries providerName, errorMessage, errorCode. |
SignOutEvent | Sign-out completed. Carries user, sessionDuration. |
SessionCreatedEvent | A session was written to the registry, first sign-in or a repeat sign-in refreshing an existing session. Carries session, isNewUser. |
SessionExpiredEvent | An active session expired and was cleared, from exhausted refresh or an explicit expiry check. Carries user, expiredAt. |
TokenRefreshEvent | A refresh attempt completed, success or failure. Carries user, success, errorMessage. |
AccountSwitchEvent | The active account changed via AccountManager.switchTo(). Carries fromUser, toUser. |
AccountRemovedEvent | An account was explicitly removed via AccountManager.signOut(userId), distinct from expiry. Carries userId, email. |
Every event has a timestamp and a toJson() for structured logging.
Example: audit log
client.events.stream.listen((event) {
auditLog.write({
'event': event.runtimeType.toString(),
...event.toJson(),
});
});
Example: react to forced sign-out
client.events.on<SessionExpiredEvent>((e) {
showSnackBar('Your session expired. Please sign in again.');
});
client.events.on<TokenRefreshEvent>((e) {
if (!e.success) showReAuthPrompt();
});
Example: multi-account analytics
client.events.on<AccountSwitchEvent>((e) {
analytics.track('account_switch', {'from': e.fromUser.id, 'to': e.toUser.id});
});
client.events.on<AccountRemovedEvent>((e) {
analytics.track('account_removed', {'user': e.userId});
});
Events vs plugins vs callbacks
| Mechanism | Can block? | Use for |
|---|---|---|
AuthCallbacks | Yes | Rate limiting, deny lists, authorization checks |
AuthyraPlugin | No | Cross-cutting side effects installed once (audit logging, rate-limit bookkeeping) |
AuthEventBus | No | Ad hoc listeners registered anywhere, analytics, one-off UI reactions |
AuthyraPlugin hooks and AuthEventBus events overlap in spirit; reach for a plugin when the behaviour ships with your app's setup, and for client.events.on<T>() when you want a quick listener local to one screen or test.