or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication-service.mdcomponents-module.mdevent-broadcasting.mdhttp-interceptor.mdindex.mdroute-protection.md
tile.json

event-broadcasting.mddocs/

Event Broadcasting

Service for broadcasting MSAL events and tracking interaction status throughout the authentication lifecycle, enabling reactive programming patterns in Angular applications.

Capabilities

MsalBroadcastService

Service that provides Observable streams for MSAL events and interaction status, allowing applications to react to authentication state changes.

/**
 * Service for broadcasting MSAL events and interaction status
 * Provides reactive streams for authentication lifecycle events
 */
class MsalBroadcastService {
  /** Observable stream of all MSAL events */
  msalSubject$: Observable<EventMessage>;
  
  /** Observable stream of interaction status changes */
  inProgress$: Observable<InteractionStatus>;
  
  /**
   * Resets the interaction status to None
   * Useful for clearing stuck interaction states
   */
  resetInProgressEvent(): void;
}

MsalBroadcastConfiguration

Configuration object for customizing broadcast behavior including event replay functionality.

/**
 * Configuration object for MsalBroadcastService
 * Controls event broadcasting and replay behavior
 */
interface MsalBroadcastConfiguration {
  /** Number of events to replay when using ReplaySubject for late subscribers */
  eventsToReplay: number;
}

Usage Examples:

import { Component, OnInit, OnDestroy } from "@angular/core";
import { MsalBroadcastService } from "@azure/msal-angular";
import { EventMessage, EventType, InteractionStatus } from "@azure/msal-browser";
import { Subject } from "rxjs";
import { filter, takeUntil } from "rxjs/operators";

@Component({
  selector: 'app-auth-status',
  template: `
    <div class="auth-status">
      <div class="status-indicator" [class]="statusClass">
        {{ statusMessage }}
      </div>
      <div *ngIf="lastEvent" class="last-event">
        Last Event: {{ lastEvent.eventType }} at {{ lastEvent.timestamp }}
      </div>
    </div>
  `
})
export class AuthStatusComponent implements OnInit, OnDestroy {
  private readonly destroying$ = new Subject<void>();
  
  statusMessage = 'Initializing...';
  statusClass = 'initializing';
  lastEvent: EventMessage | null = null;

  constructor(private broadcastService: MsalBroadcastService) {}

  ngOnInit() {
    // Subscribe to interaction status changes
    this.broadcastService.inProgress$
      .pipe(takeUntil(this.destroying$))
      .subscribe((status: InteractionStatus) => {
        this.updateStatus(status);
      });

    // Subscribe to all MSAL events
    this.broadcastService.msalSubject$
      .pipe(takeUntil(this.destroying$))
      .subscribe((payload: EventMessage) => {
        this.lastEvent = payload;
        this.handleMsalEvent(payload);
      });
  }

  ngOnDestroy() {
    this.destroying$.next(undefined);
    this.destroying$.complete();
  }

  private updateStatus(status: InteractionStatus) {
    switch (status) {
      case InteractionStatus.None:
        this.statusMessage = 'Ready';
        this.statusClass = 'ready';
        break;
      case InteractionStatus.Login:
        this.statusMessage = 'Logging in...';
        this.statusClass = 'in-progress';
        break;
      case InteractionStatus.Logout:
        this.statusMessage = 'Logging out...';
        this.statusClass = 'in-progress';
        break;
      case InteractionStatus.AcquireToken:
        this.statusMessage = 'Acquiring token...';
        this.statusClass = 'in-progress';
        break;
      case InteractionStatus.SsoSilent:
        this.statusMessage = 'Silent authentication...';
        this.statusClass = 'in-progress';
        break;
      case InteractionStatus.HandleRedirect:
        this.statusMessage = 'Handling redirect...';
        this.statusClass = 'in-progress';
        break;
    }
  }

  private handleMsalEvent(payload: EventMessage) {
    console.log('MSAL Event:', payload);
    
    // Handle specific event types
    switch (payload.eventType) {
      case EventType.LOGIN_SUCCESS:
        console.log('Login successful', payload.payload);
        break;
      case EventType.LOGIN_FAILURE:
        console.error('Login failed', payload.error);
        break;
      case EventType.ACQUIRE_TOKEN_SUCCESS:
        console.log('Token acquired', payload.payload);
        break;
      // Handle other event types as needed
    }
  }
}

Event Filtering and Specific Subscriptions

import { Component, OnInit } from "@angular/core";
import { MsalBroadcastService } from "@azure/msal-angular";
import { EventType, InteractionStatus } from "@azure/msal-browser";
import { filter } from "rxjs/operators";

@Component({
  selector: 'app-login-monitor',
  template: `
    <div *ngIf="isLoggingIn" class="login-progress">
      <mat-spinner diameter="20"></mat-spinner>
      Authenticating...
    </div>
  `
})
export class LoginMonitorComponent implements OnInit {
  isLoggingIn = false;

  constructor(private broadcastService: MsalBroadcastService) {}

  ngOnInit() {
    // Monitor only login-related status changes
    this.broadcastService.inProgress$
      .pipe(
        filter(status => 
          status === InteractionStatus.Login || 
          status === InteractionStatus.None
        )
      )
      .subscribe(status => {
        this.isLoggingIn = status === InteractionStatus.Login;
      });

    // Listen only to login success events
    this.broadcastService.msalSubject$
      .pipe(
        filter(payload => payload.eventType === EventType.LOGIN_SUCCESS)
      )
      .subscribe(payload => {
        console.log('User logged in successfully:', payload.payload);
        // Perform post-login actions
        this.onLoginSuccess();
      });

    // Listen only to login failure events
    this.broadcastService.msalSubject$
      .pipe(
        filter(payload => payload.eventType === EventType.LOGIN_FAILURE)
      )
      .subscribe(payload => {
        console.error('Login failed:', payload.error);
        // Handle login failure
        this.onLoginFailure(payload.error);
      });
  }

  private onLoginSuccess() {
    // Redirect to dashboard or refresh user data
    // This could also trigger other Angular services
  }

  private onLoginFailure(error: any) {
    // Show error message, redirect to error page, etc.
  }
}

Token Acquisition Monitoring

import { Component, Injectable } from "@angular/core";
import { MsalBroadcastService } from "@azure/msal-angular";
import { EventType, InteractionStatus } from "@azure/msal-browser";
import { BehaviorSubject } from "rxjs";
import { filter, map } from "rxjs/operators";

@Injectable({
  providedIn: 'root'
})
export class TokenStatusService {
  private tokenAcquisitionInProgress$ = new BehaviorSubject<boolean>(false);
  
  constructor(private broadcastService: MsalBroadcastService) {
    // Monitor token acquisition status
    this.broadcastService.inProgress$
      .pipe(
        map(status => status === InteractionStatus.AcquireToken)
      )
      .subscribe(inProgress => {
        this.tokenAcquisitionInProgress$.next(inProgress);
      });

    // Log token acquisition events
    this.broadcastService.msalSubject$
      .pipe(
        filter(payload => 
          payload.eventType === EventType.ACQUIRE_TOKEN_SUCCESS ||
          payload.eventType === EventType.ACQUIRE_TOKEN_FAILURE
        )
      )
      .subscribe(payload => {
        if (payload.eventType === EventType.ACQUIRE_TOKEN_SUCCESS) {
          console.log('Token acquired successfully:', payload.payload);
        } else {
          console.error('Token acquisition failed:', payload.error);
        }
      });
  }

  get isAcquiringToken$() {
    return this.tokenAcquisitionInProgress$.asObservable();
  }
}

@Component({
  selector: 'app-api-button',
  template: `
    <button 
      (click)="callApi()" 
      [disabled]="isAcquiringToken$ | async"
      class="api-button">
      <mat-spinner *ngIf="isAcquiringToken$ | async" diameter="16"></mat-spinner>
      {{ (isAcquiringToken$ | async) ? 'Getting Token...' : 'Call API' }}
    </button>
  `
})
export class ApiButtonComponent {
  constructor(public tokenStatusService: TokenStatusService) {}

  get isAcquiringToken$() {
    return this.tokenStatusService.isAcquiringToken$;
  }

  callApi() {
    // API call logic here
    // Token acquisition will be handled by MsalInterceptor
  }
}

Account Change Detection

import { Component, OnInit } from "@angular/core";
import { MsalBroadcastService } from "@azure/msal-angular";
import { EventType, AccountInfo } from "@azure/msal-browser";
import { filter, map } from "rxjs/operators";

@Component({
  selector: 'app-account-switcher',
  template: `
    <div class="account-info" *ngIf="currentAccount">
      <img [src]="currentAccount.idTokenClaims?.picture" 
           [alt]="currentAccount.name"
           class="profile-picture">
      <span>{{ currentAccount.name }}</span>
      <span class="username">{{ currentAccount.username }}</span>
    </div>
    <div *ngIf="accountChanged" class="account-changed-notice">
      Account changed! Please refresh to see updated data.
      <button (click)="refreshApp()">Refresh</button>
    </div>
  `
})
export class AccountSwitcherComponent implements OnInit {
  currentAccount: AccountInfo | null = null;
  accountChanged = false;

  constructor(private broadcastService: MsalBroadcastService) {}

  ngOnInit() {
    // Monitor account changes
    this.broadcastService.msalSubject$
      .pipe(
        filter(payload => 
          payload.eventType === EventType.ACCOUNT_ADDED ||
          payload.eventType === EventType.ACCOUNT_REMOVED
        ),
        map(payload => payload.payload as AccountInfo)
      )
      .subscribe(account => {
        console.log('Account change detected:', account);
        
        if (this.currentAccount && 
            this.currentAccount.homeAccountId !== account?.homeAccountId) {
          this.accountChanged = true;
        }
        
        this.currentAccount = account;
      });

    // Monitor logout events
    this.broadcastService.msalSubject$
      .pipe(
        filter(payload => payload.eventType === EventType.LOGOUT_SUCCESS)
      )
      .subscribe(() => {
        this.currentAccount = null;
        this.accountChanged = false;
      });
  }

  refreshApp() {
    window.location.reload();
  }
}

Configuration and Module Setup

import { NgModule } from "@angular/core";
import { 
  MsalBroadcastService, 
  MsalBroadcastConfiguration,
  MSAL_BROADCAST_CONFIG 
} from "@azure/msal-angular";

// Configure broadcast service
const broadcastConfig: MsalBroadcastConfiguration = {
  eventsToReplay: 1 // Replay the last event for late subscribers
};

@NgModule({
  providers: [
    { provide: MSAL_BROADCAST_CONFIG, useValue: broadcastConfig },
    MsalBroadcastService
  ]
})
export class AppModule { }

Advanced Event Handling Patterns

import { Injectable } from "@angular/core";
import { MsalBroadcastService } from "@azure/msal-angular";
import { EventMessage, EventType } from "@azure/msal-browser";
import { Observable, merge } from "rxjs";
import { filter, share, startWith } from "rxjs/operators";

@Injectable({
  providedIn: 'root'
})
export class AuthEventService {
  // Successful events stream
  public readonly successEvents$: Observable<EventMessage>;
  
  // Error events stream
  public readonly errorEvents$: Observable<EventMessage>;
  
  // Authentication state changes
  public readonly authStateChanges$: Observable<EventMessage>;

  constructor(private broadcastService: MsalBroadcastService) {
    // Filter success events
    this.successEvents$ = this.broadcastService.msalSubject$.pipe(
      filter(payload => 
        payload.eventType === EventType.LOGIN_SUCCESS ||
        payload.eventType === EventType.ACQUIRE_TOKEN_SUCCESS ||
        payload.eventType === EventType.SSO_SILENT_SUCCESS
      ),
      share()
    );

    // Filter error events
    this.errorEvents$ = this.broadcastService.msalSubject$.pipe(
      filter(payload => 
        payload.eventType === EventType.LOGIN_FAILURE ||
        payload.eventType === EventType.ACQUIRE_TOKEN_FAILURE ||
        payload.eventType === EventType.SSO_SILENT_FAILURE
      ),
      share()
    );

    // Combine login/logout events
    this.authStateChanges$ = this.broadcastService.msalSubject$.pipe(
      filter(payload => 
        payload.eventType === EventType.LOGIN_SUCCESS ||
        payload.eventType === EventType.LOGOUT_SUCCESS
      ),
      share()
    );
  }
}