Introduction

The following article and its code are intended to serve as a quickstart for using IdentityServer4 with Angular. The article focuses on the key configuration points that allow Angular to consume the IdentityServer4 OIDC endpoints.

Source Code

The full source code can be found on GitHub.

Configuring IdentityServer4

You need to add the IdentityServer4 package to your ASP.NET Core project.

Startup.cs

In this example IdentityServer4 will support a resource called api, which is going to be used to protect the Web API. The client application that will request this resource is called Angular. It is important to mention that in order for the OIDC login work, we need to properly configure redirect URLs, otherwise IdentityServer4 will block the login attempts.

services.AddIdentityServer()
    .AddAspNetIdentity<IdentityUser>()
    .AddInMemoryIdentityResources(new IdentityResource[]
	{ 
		new IdentityResources.OpenId(), new IdentityResources.Profile()
	})
    .AddInMemoryApiResources(new ApiResource[]
	{
		new ApiResource("api")
	})
    .AddInMemoryClients(new Client[]
    {
        new Client
        {
            ClientId = "Angular",
            AllowedGrantTypes = GrantTypes.Implicit,
            AllowedScopes =
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                "api"
            },
            AllowAccessTokensViaBrowser = true,
            RequireConsent = false,
            RedirectUris = Configuration
				.GetSection("Identity:Clients:Angular:RedirectUris")
				.Get<string[]>(),
            PostLogoutRedirectUris = Configuration
				.GetSection("Identity:Clients:Angular:PostLogoutRedirectUris")
				.Get<string[]>()
        }
    })
    .AddDeveloperSigningCredential();
appsettings.json

Following are the redirect URLs that are going to be used by the Angular application.

{
    "Identity": {
        "Authority": "https://localhost:44359",
        "Clients": {
            "Angular": {
                "RedirectUris": [
                     "https://localhost:4200/assets/login-callback.html",
                     "https://localhost:4200/assets/silent-renew.html"
                ],
                "PostLogoutRedirectUris": [ "https://localhost:4200" ]
            }
        }
    }
}

Configuring the Web API

Add IdentityServer4.AccessTokenValidation to the ASP.NET Core Web API project.

Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = Configuration["Identity:Authority"];
        options.Audience = "api";
    });

Angular

The Angular application will use package oidc-client to support the OpenID Connect login.

Important aspects of the OIDC login are the files login-callback.html and silent-renew.html in the assets folder. Once the login flow is triggered it will load the login-callback.html, which will read the user’s profile and redirect to the Angular app.

auth.service.ts

The auth service is responsible for executing login and logout, and getting the user profile. It will also store the user’s access token. Note that isAuthenticatedSubject is a ReplaySubject<boolean> – this reason for this is that even if multiple components call isAuthenticated() there will be only one userManager.getUser() call.

constructor() {
  this.userManager = new UserManager({
    authority: environment.issuer,
    client_id: 'Angular',
    scope: 'openid profile api',
    response_type: 'id_token token',
    loadUserInfo: true,
    automaticSilentRenew: true,
    redirect_uri: window.location.origin + '/assets/login-callback.html',
    silent_redirect_uri: window.location.origin + '/assets/silent-renew.html',
    post_logout_redirect_uri: window.location.origin
  });
}

isAuthenticated(): Observable<boolean> {
  if (!this.isAuthenticatedSubject) {
    this.isAuthenticatedSubject = new ReplaySubject();
    this.userManager.getUser().then(user => {
      if (user) {
        this.userName = user.profile.name;
        this.token = user.access_token;
        this.isAuthenticatedSubject.next(true);
      } else {
        this.isAuthenticatedSubject.next(false);
      }
    });
  }

  return this.isAuthenticatedSubject.asObservable();
}
interceptor.service.ts

The request interceptor’s main role is to set the access token to all requests that are executed by the app.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  request = request.clone({
    setHeaders: {
      'Authorization': `Bearer ${this.authService.token}`
    }
  });

  return next.handle(request).pipe(
    tap(
      (event: HttpEvent<any>) => { },
      (err: any) => {
        if (err instanceof HttpErrorResponse) {
          if (err.status === 401) {
            this.authService.login();
          }
        }
      }));
}

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *