Gadget Fest Docs Help

Customer

The Customer App is the public-facing application for end users of the GadgetFest platform.

Bootstrapping

The Customer App uses a sequential initialization process to ensure configuration is properly loaded before the application starts:

// app.config.ts let globalState: GlobalState; const provideApp = async () => { const injector = inject(Injector); const appInfo = await injector.get(InitService).init(); globalState = GlobalStateFactory(appInfo); await injector.get(AuthService).init(); }; export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), provideHttpClient(withInterceptorsFromDi(), withFetch()), { provide: GLOBAL_STATE, useFactory: () => globalState, }, ...MSALProviderFactory(), provideAppInitializer(async () => await provideApp()), ], };

The bootstrapping process uses a dedicated InitService to:

  1. Load festival-specific information based on the hostname

  2. Fetch custom HTML, CSS, and configuration for the festival

  3. Initialize the global state with the loaded configuration

  4. Initialize authentication after configuration is loaded

// init.service.ts @Injectable({ providedIn: 'root', }) export class InitService { private _initialized = false; async init() { if (this._initialized) { const errorMessage = 'App already has been initialized.'; console.error(errorMessage); throw new Error(errorMessage); } this._initialized = true; return await loadAppInfo(); } }

This approach ensures proper initialization order and prevents race conditions between configuration loading and application services.

State Management

The Customer App uses NgRx Signals for state management with a factory-based global store implementation:

// global.store.ts export const GLOBAL_STATE = new InjectionToken<GlobalState>('GLOBAL_STATE'); export const GlobalStateFactory: (appInfo: AppInfo) => GlobalState = (appInfo: AppInfo) => ({ auth: { account: null, }, appInfo, }); export const GlobalStore = signalStore( { providedIn: 'root' }, withState(() => inject(GLOBAL_STATE)), withComputed(({ auth }) => ({ isAuthenticated: computed(() => !!auth.account()), })), withMethods(store => ({ updateAuthAccount(account: AccountInfo | null): void { patchState(store, state => ({ auth: { ...state.auth, account } })); }, })), );

Key aspects of the state management:

  • Uses NgRx Signals for reactive state management

  • State is created with a factory function after app initialization

  • Global state is provided through an injection token

  • Includes computed properties for derived state

  • Provides methods for updating state in a controlled manner

Authentication

The authentication implementation is carefully sequenced to ensure configuration is loaded before authentication is initialized.

Authentication Service

The Customer App implements a centralized AuthService that handles all authentication operations:

@Injectable({ providedIn: 'root', }) export class AuthService { readonly msal = inject(MsalService); readonly msalBroadcast = inject(MsalBroadcastService); readonly globalStore = inject(GlobalStore); async init() { await firstValueFrom(this.msal.initialize().pipe(take(1))); this.setAccount(await this.msal.instance.handleRedirectPromise()); this.listenToAuthChanges(); } // Other methods... }

The Authentication Service provides:

  • Initialization and redirect handling

  • Login and logout operations

  • User profile management

  • Authentication state tracking

  • Policy-based redirects for password reset and profile editing

MSAL Configuration

The MSAL configuration is set up to dynamically obtain settings from the GlobalStore:

// msal.config.ts function MSALInstanceFactory(): IPublicClientApplication { const config = inject(GlobalStore).appInfo.config(); const authConfig = environment.msalConfig.getAuthConfig( config.azureClientId, config.azureAuthorityDomainName, 'B2C_1_susi', ); return new PublicClientApplication({ auth: { clientId: authConfig.clientId, authority: authConfig.authority, redirectUri: `${window.location.origin}`, postLogoutRedirectUri: `${window.location.origin}`, knownAuthorities: [authConfig.authorityDomain], }, cache: { cacheLocation: BrowserCacheLocation.SessionStorage, }, // Configuration options... }); } export function MSALProviderFactory(): Array<Provider | EnvironmentProviders> { return [ // MSAL providers... ]; }

This approach ensures that:

  • Configuration is available before MSAL is initialized

  • Azure configuration values are properly injected

  • Authentication is consistently configured across the application

Authentication Policies

The Customer App supports multiple authentication policies:

  • Sign-up/Sign-in (B2C_1_susi): Primary authentication flow

  • Password Reset (B2C_1_reset_password): Dedicated flow for password recovery

  • Profile Editing (B2C_1_edit_profile): User profile management

Authentication Flow

  1. Initialization:

    • The application loads configuration through the InitService

    • MSAL is initialized with the loaded configuration

    • Authentication service handles redirect callbacks

    • Listeners for authentication events are established

  2. Authentication:

    • Users trigger login via redirect-based authentication

    • Azure B2C presents the appropriate authentication interface

    • Upon successful authentication, the user is redirected back with tokens

  3. Token Management:

    • Access tokens are stored in session storage

    • Tokens are automatically attached to API requests

    • Token refresh happens transparently when needed

  4. Authentication Events:

    • The system listens for login and logout events

    • Updates the authentication state accordingly

    • Triggers appropriate UI updates based on authentication status

Route Protection

The Customer App uses Angular route guards to protect authenticated routes:

export const authGuard: CanActivateFn = async () => { const globalStore = inject(GlobalStore); const router = inject(Router); return !globalStore.isAuthenticated() ? router.createUrlTree(['/landing']) : true; };

This ensures that authenticated routes are only accessible to logged-in users, redirecting unauthenticated users to the landing page.

10 July 2025