
Standalone Angular: provideOAuthClient and Bootstrapping
Learn how to use provideOAuthClient for bootstrapping OAuth2/OIDC in standalone Angular applications without NgModules.
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:
- Retrieves the current access token from storage (usually
sessionStorageorlocalStorage). - 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:
- Bootstrap:
bootstrapApplicationis called withprovideOAuthClient. - Injector Creation: The root injector is created, and
OAuthServiceis instantiated with the provided config. - App Component Instantiation: The
AppComponentis created. - View Initialization: The component’s view is rendered.
- 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
- Race Conditions: If HTTP requests fire before
OAuthServiceinitialization completes, the interceptor may fail to attach tokens. Use guards or resolvers to delay rendering until authentication is verified. - Missing
redirectUri: Incorrect or missingredirectUriconfiguration can cause silent renewal failures. Ensure this matches the exact callback URL registered in your IdP. - Security Risks: Storing tokens in
localStorageexposes them to XSS attacks. PrefersessionStorageor in-memory storage where possible, and follow security best practices outlined in the angular-oauth2-oidc documentation.
Practical Takeaways
provideOAuthClientreplacesOAuthModule.forRoot()in standalone applications.- Always use guards/resolvers for auth-required routes to prevent race conditions.
- Verify
redirectUrimatches 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
OAuth 2.0 Refresh Tokens in Angular: Silent Refresh with useRefreshTokens
Learn how to implement automatic silent refresh in Angular using userefreshtoken and setupautomaticsilentrefresh for secure session management.
Angular OAuth2/OIDC: loadDiscoveryDocumentAndTryLogin
Learn how to use loadDiscoveryDocumentAndTryLogin and strict discovery document validation in Angular for secure OAuth2/OIDC authentication.
The AuthConfig Reference: Every Property That Matters
A complete reference for Angular-OAuth2-OIDC AuthConfig properties, covering requireHttps, remoteOnly, and nonceStateSeparator for secure Angular authentication.