Integrating Angular 17 with .NET Core 8 for Token Management
Written on
Preface
This installment is part of our in-depth series on developing a Talent Management SPA using Angular 17 and .NET Core 8. For an overview of the series and access to previous entries, please consult the table of contents, which is structured to assist you through each phase of creating a robust Talent Management SPA.
Introduction
In this sixth tutorial, we will explore the integration of Angular login and token refresh functionalities with the powerful Duende IdentityServer. Prepare for a thorough authentication approach that employs well-known protocols such as OpenID Connect and OAuth 2.0. Don't worry; we’ll provide an excellent Angular library that simplifies the login implementation.
SPA Architecture Overview
The architecture of our Talent Management demo application is based on the Clients, API Resources, and Token Service (CAT) model, which emphasizes strong integration and loose coupling. The components interact via HTTP to exchange and validate JSON Web Tokens (JWT).
Components Interaction:
- Clients: The Angular app utilizes the OpenID Connect (OIDC) protocol to request JWTs from the Token Service. The obtained JWT is included in the header for API requests.
- API Resources: The .NET Core REST API decodes the JWT to verify the client’s identity and authorize access to requested resources.
- Token Service: The Duende IdentityServer generates JWTs for the Angular client and validates them when requested by the REST API.
In this post, we'll delve into the intricate relationship between Angular 17 and the Duende IdentityServer 6. We will analyze the source code responsible for the login and token refresh processes, enhancing your understanding of the system. The demo project includes ready-to-use code snippets that you can easily adapt for your own projects!
Source Code Overview
This blog series features a demo application called Talent Management. For setup instructions, refer to the post titled "Talent Management SPA with Angular 17 and .NET Core 8 | Setting Up the Project" to download and run the demo locally.
Prerequisites
- Understanding Token Service concepts and technologies (e.g., Duende IdentityServer).
- Familiarity with the "Building a Talent Management SPA with Angular 17 and .NET Core 8" tutorial series.
Acknowledgments
This tutorial was developed utilizing tools and methodologies from several high-quality open-source projects and resources, including:
- manfredsteyer/angular-oauth2-oidc: A popular NPM package with over 1.7K stars on GitHub, designed for OIDC and OAuth 2.0 support in Angular.
- DuendeSoftware/IdentityServer: The leading open-source solution for securing enterprise SPA/.NET WebAPI by Duende Software.
- Duende.IdentityServer4.Admin: An excellent Admin UI for Duende IdentityServer and ASP.NET Core Identity by Jan Škoruba, ideal for learning high-quality C# coding.
Table of Contents
In this blog post, we'll cover the following topics regarding user authentication and login functionality in your Angular application:
- Simplifying user login with the angular-oauth2-oidc library
- Angular configuration settings to connect with IdentityServer
- Implementing Login with PKCE (Proof Key for Code Exchange) in Angular
- Implementing token refresh in Angular
By the end of this post, you’ll have a solid understanding of how to implement user authentication and login functionality in your Angular application using the angular-oauth2-oidc library and IdentityServer.
Part 1: Simplifying User Login with the Angular-OAuth2-OIDC Library
Implementing user authentication and login in a web application can often be complicated and lengthy. However, the angular-oauth2-oidc library can streamline this process considerably.
This library is a widely used open-source solution for integrating OAuth2 and OpenID Connect (OIDC) in Angular apps. It offers an intuitive API for managing user authentication and access token handling.
With this library, developers can easily support various OAuth2 and OIDC providers, including Google, Microsoft, and Okta. It also offers multiple authentication flows, including Authorization Code Flow, Implicit Flow, and Resource Owner Password Credentials Flow.
A significant advantage of using the angular-oauth2-oidc library is its ability to manage many common challenges associated with user authentication and access token management. This includes managing token expiration and refresh, as well as ensuring that access tokens are only transmitted to secure endpoints.
Implementation Steps:
- Install the angular-oauth2-oidc library via npm.
- Configure the library with necessary settings such as client ID and redirect URI.
- Create a service for handling authentication and access token management using the library's API.
- Implement the login and logout functionalities in your application by invoking the appropriate service methods.
In summary, the angular-oauth2-oidc library significantly simplifies the implementation of user authentication and login in Angular applications. With its user-friendly API, support for popular providers, and built-in management for common authentication challenges, it’s an excellent choice for developers seeking to add user authentication to their applications.
Part 2: Angular Configuration Settings to Communicate with IdentityServer
For the Angular application to interact with IdentityServer, specific configuration settings must be defined. These settings generally include the IdentityServer issuer URL, client ID, and redirect URIs, among others.
Configuration settings are typically stored in an environment file, such as environment.ts or environment.prod.ts, allowing different settings for development and production environments.
The angular-oauth2-oidc library provides an AuthConfig interface that outlines the required configuration settings for the OAuth2 flow.
Key Configuration Settings:
- issuer: The URL of the IdentityServer's issuer, which is essential for obtaining the discovery document containing necessary metadata.
- clientId: The registered client ID with IdentityServer, identifying the Angular application.
- redirectUri: The URI to which IdentityServer will redirect the user post-authentication.
- scope: The scopes requested by the Angular application, determining the level of access to user resources.
- silentRefreshRedirectUri: The URI for silent refreshes of the access token, typically an HTML page loaded as an iframe.
- sessionChecksEnabled: Enabling session checks ensures the user remains authenticated before making API calls.
By defining these settings, the Angular application can securely communicate with IdentityServer and acquire access tokens for protected APIs.
Source Code Walkthrough
The configuration settings for Angular's interaction with IdentityServer are located in the auth-config.ts file. Update this file to point to your IdentityServer. Below is a sample code snippet:
import { AuthConfig } from 'angular-oauth2-oidc';
import { environment } from '@env/environment';
export const authConfig: AuthConfig = {
issuer: environment.oidc.issuer,
clientId: environment.oidc.clientId,
responseType: environment.oidc.responseType,
redirectUri: environment.oidc.redirectUri,
postLogoutRedirectUri: environment.oidc.postLogoutRedirectUri,
silentRefreshRedirectUri: environment.oidc.silentRefreshRedirectUri,
scope: environment.oidc.scope,
useSilentRefresh: environment.oidc.useSilentRefresh,
silentRefreshTimeout: environment.oidc.silentRefreshTimeout,
timeoutFactor: environment.oidc.timeoutFactor,
sessionChecksEnabled: environment.oidc.sessionChecksEnabled,
showDebugInformation: environment.oidc.showDebugInformation,
clearHashAfterLogin: environment.oidc.clearHashAfterLogin,
nonceStateSeparator: environment.oidc.nonceStateSeparator,
};
This snippet imports AuthConfig from the angular-oauth2-oidc library and the environment object from the environment.ts file. The authConfig constant is assigned an object of type AuthConfig, where each property is set according to the environment object’s values.
Part 3: Implementing Login with PKCE in Angular
In Angular, logging in using PKCE (Proof Key for Code Exchange) is a secure authentication method utilizing the OAuth 2.0 framework.
PKCE Flow Overview
- Users log in to an identity provider (IdP) with their credentials.
- The IdP generates an authorization code and returns it to the client application (Angular).
- The client sends a request to the authorization server, including the authorization code and a code verifier (a randomly generated string that is hashed).
- The authorization server validates the authorization code and code verifier, and if they match, it issues an access token and refresh token.
The PKCE flow adds an extra layer of security compared to traditional OAuth 2.0 flows, helping to prevent various attack types, such as authorization code interception.
Source Code Walkthrough
In the Talent Management demo application, the login option is located in the upper right corner of the UI.
When the Login menu is selected, it triggers the login function in the header.component.ts source file. Here’s a snippet of that code:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '@app/core/auth/auth.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
menuHidden = true;
isAuthenticated: Observable;
constructor(private authService: AuthService, private router: Router) {
this.isAuthenticated = authService.isAuthenticated$;}
ngOnInit() {}
toggleMenu() {
this.menuHidden = !this.menuHidden;}
login() {
this.authService.login();}
logout() {
this.authService.logout();}
get username(): string | null {
return this.authService.identityClaims ? (this.authService.identityClaims as any)['name'] : null;}
}
The login() method calls the login() method from the AuthService, initiating the login process.
AuthService Overview
Here's a snippet from the auth.service.ts file:
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { OAuthErrorEvent, OAuthService } from 'angular-oauth2-oidc';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
export class AuthService {
private isAuthenticatedSubject$ = new BehaviorSubject(false);
public isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();
constructor(private oauthService: OAuthService, private router: Router) {
// Event handling and initialization code...}
public login(targetUrl?: string) {
this.oauthService.initLoginFlow(targetUrl || this.router.url);}
public logout() {
this.oauthService.logOut();}
}
This service manages authentication, storing the authenticated state using observables that can be subscribed to.
Part 4: Implementing Token Refresh in Angular
Authentication tokens are issued by the server upon user login and are used for identifying the user in future requests. However, these tokens generally have an expiration time, after which a new token must be obtained.
Token Refresh Mechanism
In Angular, token refresh typically involves sending a request to the server to obtain a new token using the existing one. Successful requests return new tokens for subsequent use.
Angular offers several methods for handling token refresh, including interceptors, which can modify outgoing requests and incoming responses. Interceptors can intercept expired token errors and initiate the token refresh process.
Source Code for Token Refresh
The silentRefreshRedirectUri setting in environment.ts specifies the HTML page that supports automatic token refresh.
Here’s a snippet of code from the silent-refresh.html file:
<script>
const checks = [/[?|&|#]code=/, /[?|&|#]error=/, /[?|&|#]token=/, /[?|&|#]id_token=/];
function isResponse(str) {
let count = 0;
if (!str) return false;
for (let i = 0; i < checks.length; i++) {
if (str.match(checks[i])) return true;}
return false;
}
let message = isResponse(location.hash) ? location.hash : '#' + location.search;
(window.opener || window.parent).postMessage(message, location.origin);
</script>
This script enables silent token refresh through an iframe, allowing for a seamless user experience without requiring re-authentication.
Viewing Token Refresh in Action
To observe token refresh in your Angular application:
- Open the application in your browser and access the developer console (F12).
- Navigate to the "Network" tab.
- Log in and wait for the access token to expire.
- Look for a request labeled "silent-refresh.html" in the Network tab to verify if a new token has been issued.
The first video titled "NET 8 Angular Authentication with Identity and Refresh Tokens" provides insights into implementing secure authentication practices.
The second video "Refresh Token in Angular 17 & .NET 8 (Series Part 15)" elaborates on the refresh token mechanism in Angular 17.
Frequently Asked Questions
- How to log out? Call the logout() function in the auth.service.ts file.
- Why is token silent refresh not functioning? Ensure that src/silent-refresh.html is included in the architect/build/options/assets section of angular.json.
- Which other authentication systems are supported? The angular-oauth2-oidc library also supports Keycloak, Azure AD, Auth0, and Okta.
Recommended Content
- What is PKCE?
- What is OIDC?
- Differences between cookie, session, and token
- How to integrate OIDC into Angular and .NET?
- Understanding CORS in SPA
- LocalStorage and SessionStorage in browsers
- CAT architecture pattern for modern SPA/Mobile apps
Summary
This blog post serves as a comprehensive guide for developers aiming to implement secure authentication in Angular applications using Duende IdentityServer. It outlines the steps required to configure an Angular application for communication with Duende IdentityServer utilizing PKCE, thus enabling user login and JWT acquisition.
In the next blog entry, we’ll explore Auth Guard and Role Guard to demonstrate how to effectively utilize JWT for access control.