Skip to content
Ashish.
All posts
Diagram showing the provideOAuthClient function integrating with Angular's standalone application bootstrap process.
6 min readDevelopmentAngular DevelopersFeatured#angular#oauth2#oidc#standalone#authentication#bootstrapping#provideoauthclient

Standalone Angular: provideOAuthClient and Bootstrapping

Learn how to use provideOAuthClient for bootstrapping OAuth2/OIDC in standalone Angular applications without NgModules.

By Ashish SrivastavaPart 8 of angular-oauth2-oidc in Production

In the transition from NgModule-based Angular applications to the standalone component model, the primary challenge for authentication libraries has been how to register global services without a central AppModule. The angular-oauth2-oidc library addresses this via provideOAuthClient, a provider function that configures and injects the OAuthService into the Angular dependency injection (DI) hierarchy. This guide explains the mechanism by which provideOAuthClient bootstraps OAuth2/OIDC flows in standalone applications.

The Dependency Injection Mechanism

Angular’s standalone architecture removes the need for NgModule containers but retains the DI system. In previous versions, developers used OAuthModule.forRoot() in app.module.ts. This function registered the OAuthService and its dependencies into the root injector.

In standalone apps, you use bootstrapApplication from @angular/core. This function accepts an options object, often typed as ApplicationConfig, which contains a providers array. provideOAuthClient is a factory function that returns a provider definition compatible with this array.

import { bootstrapApplication } from '@angular/platform-browser';
import { provideOAuthClient } from 'angular-oauth2-oidc';
import { AppComponent } from './app/app.component';
 
bootstrapApplication(AppComponent, {
  providers: [
    provideOAuthClient({
      // Configuration options
    })
  ]
});

When bootstrapApplication executes, it creates an ApplicationRef and initializes the root injector. The providers array is processed, and provideOAuthClient instantiates the necessary services. Crucially, this happens before the AppComponent is instantiated, ensuring that any component or service that injects OAuthService receives a fully configured instance.

Configuring the OAuth Service

The provideOAuthClient function takes a single, optional argument: an OAuthModuleConfig object, which configures resource-server rules for the HTTP interceptor. The actual OAuth/OIDC settings are supplied separately by calling oauthService.configure() with an AuthConfig object. The mechanism here is straightforward: the library reads these properties and sets the internal state of the service instance.

Key AuthConfig properties include:

  • issuer: The URL of the OpenID Connect Provider (OP).
  • clientId: The client identifier registered with the OP.
  • redirectUri: The URL where the OP redirects after authentication.
  • responseType: Typically 'code' for authorization code flow or 'id_token token' for implicit flow.
  • scope: Space-separated list of scopes (e.g., 'openid profile email').
import { provideOAuthClient, OAuthService } from 'angular-oauth2-oidc';
 
const authConfig = {
  issuer: 'https://accounts.google.com',
  clientId: 'your-client-id.apps.googleusercontent.com',
  redirectUri: window.location.origin + '/callback',
  responseType: 'code',
  scope: 'openid profile email',
};
 
bootstrapApplication(AppComponent, {
  providers: [
    provideOAuthClient()
  ]
}).then(appRef => {
  const oauthService = appRef.injector.get(OAuthService);
  oauthService.configure(authConfig);
});

The library uses the issuer to automatically discover the OIDC metadata endpoint (.well-known/openid-configuration) when loadDiscoveryDocumentAndTryLogin() is called. This discovery process fetches the authorization endpoint, token endpoint, and JWKS URI, eliminating the need to hardcode these URLs.

The HTTP Interceptor Chain

A critical part of the bootstrapping mechanism is the automatic registration of an HTTP interceptor. In NgModule apps, this was handled by OAuthModule.forRoot() registering an HTTP_INTERCEPTORS multi-provider. In standalone apps, provideOAuthClient handles this internally.

The interceptor works by intercepting every outgoing HTTP request. It checks the request URL against the allowedUrls configured in the resource server settings. If the request matches an allowed URL, the interceptor:

  1. Retrieves the current access token from storage (usually sessionStorage or localStorage).
  2. Attaches the Authorization: Bearer <token> header to the request.

Token expiration handling and silent renewal are managed separately: enabling setupAutomaticSilentRefresh() starts an independent timer that renews the token, using a refresh token (if available) or an iframe-based flow, before it expires.

This mechanism ensures that components do not need to manually attach tokens. However, it also means that the OAuthService must be initialized before any HTTP requests are made. If a component makes an HTTP call during the initial render, the interceptor might not have the token yet, leading to authentication errors.

Bootstrapping Workflow and Timing

The bootstrapping process in Angular is synchronous from the perspective of the bootstrapApplication call, but the authentication flow itself is asynchronous. Here is the sequence:

  1. Bootstrap: bootstrapApplication is called with provideOAuthClient.
  2. Injector Creation: The root injector is created, and OAuthService is instantiated with the provided config.
  3. App Component Instantiation: The AppComponent is created.
  4. View Initialization: The component’s view is rendered.
  5. HTTP Requests: Any HTTP calls made by components or services are intercepted.

If your application requires the user to be authenticated before rendering the main view, you should use an Angular CanActivate guard or a resolver. These guards can inject OAuthService and call oauthService.loadDiscoveryDocumentAndTryLogin(). This method performs the discovery document fetch and attempts to process the hash fragment or authorization code from the URL.

import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { OAuthService } from 'angular-oauth2-oidc';
 
export const authGuard: CanActivateFn = () => {
  const oauthService = inject(OAuthService);
  const router = inject(Router);
 
  if (!oauthService.hasValidAccessToken()) {
    router.navigate(['/login']);
    return false;
  }
 
  return true;
};

Conclusion

provideOAuthClient simplifies the integration of OAuth2/OIDC in standalone Angular applications by consolidating configuration and provider registration into a single function call. It leverages Angular’s modern DI system to ensure that OAuthService is available throughout the application lifecycle. By understanding the mechanism of the HTTP interceptor and the timing of the bootstrapping process, developers can avoid common pitfalls such as race conditions during initial load.

For production applications, always ensure that the OAuthService is properly configured with secure storage mechanisms and that the redirectUri matches the registered callback URLs in your identity provider.

Common Pitfalls

  1. Race Conditions: If HTTP requests fire before OAuthService initialization completes, the interceptor may fail to attach tokens. Use guards or resolvers to delay rendering until authentication is verified.
  2. Missing redirectUri: Incorrect or missing redirectUri configuration can cause silent renewal failures. Ensure this matches the exact callback URL registered in your IdP.
  3. Security Risks: Storing tokens in localStorage exposes them to XSS attacks. Prefer sessionStorage or in-memory storage where possible, and follow security best practices outlined in the angular-oauth2-oidc documentation.

Practical Takeaways

  1. provideOAuthClient replaces OAuthModule.forRoot() in standalone applications.
  2. Always use guards/resolvers for auth-required routes to prevent race conditions.
  3. Verify redirectUri matches IdP settings to ensure silent renewal works correctly.

FAQ

Can I use provideOAuthClient with NgModules? No. For NgModule-based applications, continue to use OAuthModule.forRoot() in your AppModule.

How do I handle token refresh? Token refresh is handled automatically once you enable setupAutomaticSilentRefresh(). However, you must ensure that a valid refresh token is stored and accessible to the OAuthService.

Is provideOAuthClient tree-shakable? Yes, as a provider function, it integrates cleanly with Angular's tree-shaking capabilities, ensuring only necessary code is included in the bundle.

Related posts