Skip to main content

Authentication Events

The nauth-toolkit SDK emits events throughout the authentication lifecycle, allowing your application to react to auth state changes, track user behavior, and update the UI accordingly.

Overview

Events are emitted for:

  • Authentication flows: Login, signup, logout
  • Challenge flows: MFA, email verification, password changes
  • OAuth flows: Social login initiation, callbacks, completion
  • Token management: Token refresh (via callbacks)
  • Errors: Authentication failures, OAuth errors

Available Events

Event TypeWhen EmittedEvent Data
auth:loginLogin initiated{ identifier: string }
auth:signupSignup initiated{ email: string }
auth:successUser successfully authenticatedAuthResponse
auth:challengeChallenge required (MFA, verification, etc.)AuthResponse with challengeName
auth:errorAuthentication failedNAuthClientError
auth:logoutUser logged out{ forgetDevice: boolean, global: boolean }
auth:refreshToken refresh attempted{ success: boolean }
auth:session_expiredRefresh token expired or invalid{}
oauth:startedSocial login initiated{ provider: string }
oauth:callbackOAuth callback detected{ provider: string }
oauth:completedOAuth flow completedAuthResponse
oauth:errorOAuth flow failedNAuthClientError

Event Data Structure

All events are strongly typed using a discriminated union for type safety:

type AuthEvent =
| AuthLoginEvent
| AuthSignupEvent
| AuthSuccessEvent
| AuthChallengeEvent
| AuthErrorEvent
| AuthLogoutEvent
| AuthRefreshEvent
| AuthSessionExpiredEvent
| OAuthStartedEvent
| OAuthCallbackEvent
| OAuthCompletedEvent
| OAuthErrorEvent;

// Example: Login event
interface AuthLoginEvent {
type: 'auth:login';
data: { identifier: string };
timestamp: number;
}

// Example: Success event
interface AuthSuccessEvent {
type: 'auth:success';
data: AuthResponse;
timestamp: number;
}

// Example: Error event
interface AuthErrorEvent {
type: 'auth:error';
data: NAuthClientError;
timestamp: number;
}

Each event type has a specific payload structure, providing full type safety and autocomplete in TypeScript.

Angular: Using AuthService Observables

The Angular AuthService exposes events as RxJS Observables, making it easy to integrate with Angular's reactive patterns.

Available Observables

import { AuthService } from '@nauth-toolkit/client-angular';

// All authentication events
authEvents$: Observable<AuthEvent>

// Filtered event streams
authSuccess$: Observable<AuthEvent> // Only success events
authError$: Observable<AuthEvent> // Only error events

Basic Usage

import { Component, OnInit, OnDestroy } from '@angular/core';
import { AuthService } from '@nauth-toolkit/client-angular';
import { Subscription } from 'rxjs';

@Component({
selector: 'app-root',
template: `...`,
})
export class AppComponent implements OnInit, OnDestroy {
private subscriptions = new Subscription();

constructor(private auth: AuthService) {}

ngOnInit(): void {
// Subscribe to all auth events
this.subscriptions.add(
this.auth.authEvents$.subscribe((event) => {
console.log('Auth event:', event.type, event.data);

switch (event.type) {
case 'auth:success':
this.handleAuthSuccess(event);
break;
case 'auth:challenge':
this.handleChallenge(event);
break;
case 'auth:error':
case 'oauth:error':
this.handleError(event);
break;
}
}),
);

// Or subscribe to specific event types
this.subscriptions.add(
this.auth.authSuccess$.subscribe((event) => {
console.log('User authenticated:', event.data.user);
this.analytics.track('login_success');
}),
);

this.subscriptions.add(
this.auth.authError$.subscribe((event) => {
console.error('Auth error:', event.data.message);
this.showErrorToast(event.data.message);
}),
);
}

ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}

private handleAuthSuccess(event: AuthEvent): void {
const response = event.data as AuthResponse;
if (response.user) {
this.router.navigate(['/dashboard']);
}
}

private handleChallenge(event: AuthEvent): void {
const response = event.data as AuthResponse;
if (response.challengeName) {
this.router.navigate(['/challenge', response.challengeName]);
}
}

private handleError(event: AuthEvent): void {
const error = event.data as NAuthClientError;
this.toastr.error(error.message);
}
}

Challenge Detection

@Component({ /* ... */ })
export class ChallengeRouterComponent implements OnInit {
constructor(
private auth: AuthService,
private router: Router,
) {}

ngOnInit(): void {
// Listen for challenge events
this.auth.authEvents$
.pipe(
filter((e) => e.type === 'auth:challenge'),
map((e) => e.data as AuthResponse),
)
.subscribe((response) => {
switch (response.challengeName) {
case 'VERIFY_EMAIL':
this.router.navigate(['/verify-email']);
break;
case 'VERIFY_PHONE':
this.router.navigate(['/verify-phone']);
break;
case 'MFA_REQUIRED':
this.router.navigate(['/mfa']);
break;
case 'MFA_SETUP_REQUIRED':
this.router.navigate(['/mfa-setup']);
break;
case 'FORCE_CHANGE_PASSWORD':
this.router.navigate(['/change-password']);
break;
}
});
}
}

Vanilla JS/TypeScript: Using NAuthClient

For vanilla JavaScript/TypeScript applications, use the on() method to subscribe to events.

Basic Usage

import { NAuthClient } from '@nauth-toolkit/client';

const client = new NAuthClient({
baseUrl: 'https://api.example.com/auth',
// ... other config
});

// Subscribe to specific event
const unsubscribe = client.on('auth:success', (event) => {
console.log('User logged in:', event.data.user);
analytics.track('login_success', { method: 'password' });
});

// Subscribe to multiple events
client.on('auth:challenge', (event) => {
const response = event.data as AuthResponse;
if (response.challengeName === 'MFA_REQUIRED') {
window.location.href = '/mfa';
}
});

client.on('auth:error', (event) => {
const error = event.data as NAuthClientError;
showToast(error.message, 'error');
});

// Listen to all events
client.on('*', (event) => {
console.log('Auth event:', event.type, event.timestamp);

// Log to analytics
analytics.track('auth_event', {
type: event.type,
timestamp: event.timestamp,
});
});

// Unsubscribe when done
unsubscribe();

OAuth Event Handling

// Track OAuth flow
client.on('oauth:started', (event) => {
console.log('OAuth started:', event.data.provider);
analytics.track('oauth_started', { provider: event.data.provider });
});

client.on('oauth:callback', () => {
console.log('OAuth callback received');
showLoadingSpinner();
});

client.on('oauth:completed', (event) => {
console.log('OAuth completed');
hideLoadingSpinner();
const response = event.data as AuthResponse;
if (response.challengeName) {
// Handle challenge
} else {
// Navigate to dashboard
window.location.href = '/dashboard';
}
});

client.on('oauth:error', (event) => {
const error = event.data as NAuthClientError;
console.error('OAuth failed:', error.message);
showError(error.message);
});

Common Use Cases

1. Analytics Tracking

// Track all authentication events
client.on('*', (event) => {
analytics.track('auth_event', {
event_type: event.type,
timestamp: event.timestamp,
has_data: !!event.data,
});
});

// Track specific events
client.on('auth:success', (event) => {
const response = event.data as AuthResponse;
analytics.track('user_login', {
method: 'password', // or 'oauth'
user_id: response.user?.sub,
});
});

2. UI Updates

// Show/hide loading states
client.on('oauth:started', () => {
showLoadingOverlay('Redirecting to login provider...');
});

client.on('oauth:callback', () => {
showLoadingOverlay('Completing authentication...');
});

client.on('oauth:completed', () => {
hideLoadingOverlay();
});

client.on('oauth:error', () => {
hideLoadingOverlay();
showErrorDialog('Authentication failed. Please try again.');
});

3. Challenge Navigation

// Auto-navigate based on challenge type
client.on('auth:challenge', (event) => {
const response = event.data as AuthResponse;
const challengeRoutes: Record<string, string> = {
VERIFY_EMAIL: '/verify-email',
VERIFY_PHONE: '/verify-phone',
MFA_REQUIRED: '/mfa',
MFA_SETUP_REQUIRED: '/mfa-setup',
FORCE_CHANGE_PASSWORD: '/change-password',
};

const route = challengeRoutes[response.challengeName];
if (route) {
router.navigate([route]);
}
});

4. Error Handling

// Centralized error handling
client.on('auth:error', (event) => {
const error = event.data as NAuthClientError;

// Log error
logger.error('Auth error', {
code: error.code,
message: error.message,
timestamp: event.timestamp,
});

// Show user-friendly message
const userMessage = getUserFriendlyMessage(error);
toast.error(userMessage);

// Track error
analytics.track('auth_error', {
error_code: error.code,
error_message: error.message,
});
});

Event Timing

Events are emitted synchronously during the authentication flow:

  1. Login Flow:

    • auth:login → Login initiated
    • auth:success or auth:challenge → Based on response
    • auth:error → On failure
  2. Signup Flow:

    • auth:signup → Signup initiated
    • auth:success or auth:challenge → Based on response
    • auth:error → On failure
  3. OAuth Flow:

    • oauth:started → User clicks social login
    • oauth:callback → OAuth provider redirects back
    • auth:challenge or auth:success → Based on response
    • oauth:completed → Flow completed
    • oauth:error → On any OAuth error
  4. Logout:

    • auth:logout → Session cleared (data includes forgetDevice and global flags)
  5. Token Refresh:

    • auth:refresh → Token refresh attempted (data includes success flag)
    • auth:session_expired → Emitted when refresh fails with 401 (refresh token expired); user must re-authenticate

Best Practices

1. Unsubscribe to Prevent Memory Leaks

// Angular
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}

// Vanilla JS
const unsubscribe = client.on('*', handler);
// Later...
unsubscribe();

2. Use Type Guards

client.on('auth:success', (event) => {
if (event.data && 'user' in event.data) {
const response = event.data as AuthResponse;
console.log('User:', response.user);
}
});

3. Handle Errors Gracefully

client.on('*', (event) => {
try {
// Your event handling logic
} catch (error) {
console.error('Error handling auth event:', error);
// Don't let event handler errors break the app
}
});

4. Debounce Rapid Events

import { debounceTime } from 'rxjs/operators';

// Angular: Debounce rapid events
this.auth.authEvents$
.pipe(debounceTime(100))
.subscribe((event) => {
// Handle event
});