Identity Server: Usage from Angular

This post is a continuation of a series of posts that follow my initial looking into using IdentityServer4 in ASP.NET Core with an API and an Angular front end. The following are the related posts.

Identity Server: Introduction
Identity Server: Sample Exploration and Initial Project Setup
Identity Server: Interactive Login using MVC
Identity Server: From Implicit to Hybrid Flow
Identity Server: Using ASP.NET Core Identity
Identity Server: Using Entity Framework Core for Configuration Data
Identity Server: Usage from Angular (this post)

This post is finally going to add login from Angular in the Client Application. It has been a long time coming and will be a starting point, based on a few examples I found which I will list at the end. The starting point of the code can be found here.

API Application

In order for the Client Application to be able to call the API Application, there are some changes needed to allow cross-origin resource sharing. For more details check out this post only the basics will be covered here. First, add the following NuGet package.

  • Microsoft.AspNetCore.Cors

Next, in the ConfigureServices function of the Startup class add AddCors before AddMvc. The following is the full function. This allows calls to the API Application from the Client Application which is running on localhost on port 5002.

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy("default", policy =>
        {
            policy.WithOrigins("http://localhost:5002")
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
    });

    services.AddMvc();
}

Then, in the Configure function add app.UseCors(“default”); to make sure the default policy defined above is enforced. The following is the full function.

public void Configure(IApplicationBuilder app, 
                      IHostingEnvironment env, 
                      ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    app.UseCors("default");

    app
      .UseIdentityServerAuthentication(
      new IdentityServerAuthenticationOptions
    {
        Authority = "http://localhost:5000",
        RequireHttpsMetadata = false,
        ApiName = "apiApp"
    });

    app.UseMvc();
}

Identity Application

The Identity Application doesn’t have a lot of changes, but some of the configuration between it and the Client Application is what took me the bulk of time getting the code for this post setup and going.

If you are keeping up with the series then last you will know last week all the configuration data was moved to a database using Entity Framework Core. This is a bit of a problem now that I need to add a new client and the configuration data doesn’t any associated UI. To work around this I just added the new client to the Config class in the GetClients function and then deleted the existing database and let Entity Framework recreate it based on the new seed data. Not optimal, but I didn’t want to complicate things by adding a UI for the client setup. The following is the new client.

new Client
{
    ClientId = "ng",
    ClientName = "Angular Client",
    AllowedGrantTypes = GrantTypes.Implicit,
    AllowAccessTokensViaBrowser = true,
    RequireConsent = true,

    RedirectUris = { "http://localhost:5002/callback" },
    PostLogoutRedirectUris = { "http://localhost:5002/home" },
    AllowedCorsOrigins = { "http://localhost:5002" },

    AllowedScopes =
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "apiApp"
    },

}

There are a few things in this configuration that took me some time to get right. First of all best, I have been able to tell with this style of application implicit flow is the way to go which is handled by using a GrantTypes.Implicit for the AllowedGrantTypes.

The next issues I ran was a cross-origin resource sharing issue. Thankfully IdentityServer makes it easy to specify what origins should be allowed using the AllowedCorsOrigins property. In this example, we want to allow requests from the URL of our Client Application which is http://localhost:5002.

The last issue I had on was with the URIs I had set. The configuration in IdentityServer needs to exactly match the setup in the Client Application or you will have issues. I also had trouble trying to use the raw base address (http://localhost:5002) as the PostLogoutRedirectUris so look out for that as well.

Client Application

In the client application open the package.json file and add the following the dependencies section.

"oidc-client": "1.3.0",
"babel-polyfill": "6.23.0"

I also updated the typescript version to 2.3.4. Be cautious when changing the version of typescript as there is an issue with Angular and typescript 2.4.x at the moment.

At this point in the process, I had to find some resources on how to continue. The following are the ones I leaned on most.

ANGULAR OPENID CONNECT IMPLICIT FLOW WITH IDENTITYSERVER4
ASP.NET Core & Angular2 + OpenID Connect using Visual Studio Code
Repo for the previous link
Repo for with example Angular OidcClient

Getting this part of the application working involved a lot of changes and instead of going in depth on everything I am going to recommend just copying in the following files for the finished example code and dig more into them after you get an example working. Here is the list of files.

  • ClientApp/ClientApp/app/components/callback/callback.component.ts
  • ClientApp/ClientApp/app/components/services/ – whole directory
  • ClientApp/ClientApp/boot-server.ts – related to a typescript error only if needed

With the above files in place, we will now focus on using the functionality they provide to log in and protect routes. To begin  app.module.client.tsapp.module.server.ts and app.module.shared.ts all need the next set of changes. I haven’t tried it yet, but I bet this change could just be made in the shared file and used in the other two. Add the following imports.

import { AuthService } from './components/services/auth.service';
import { GlobalEventsManager } from './components/services/global.events.manager';
import { AuthGuardService } from './components/services/auth-guard.service';

Next, add the same three items to the array of providers (or add one if it doesn’t exist). The following is an example from the shared file.

 @NgModule({
     bootstrap: sharedConfig.bootstrap,		     
     declarations: sharedConfig.declarations,		     
     imports: [
         ServerModule,
         ...sharedConfig.imports
     ],
     providers: [
        AuthService, AuthGuardService, GlobalEventsManager 
     ]
 })

Finally, in the shared file change any routes that you would like to require the user to be logged in to be like the following which utilizes the canActivate of the route.

{ path: 'fetch-data', 
  component: FetchDataComponent, 
  canActivate:[AuthGuardService]  }

In the navmenu.component.html which is the UI for the navigation menu add the following two options to the unordered list.

<li *ngIf="!_loggedIn" [routerLinkActive]="['link-active']">
    <a (click)="login()" [routerLink]="['/login']">
       <span class="glyphicon glyphicon-user"></span> Login
    </a>
</li>
<li *ngIf="_loggedIn" [routerLinkActive]="['link-active']">
    <a (click)="logout()" [routerLink]="['/logout']">
       <span class='glyphicon glyphicon-log-out'></span> Logout
    </a>
</li>

The user will only ever see one of the above options based on being logged in or not which is what the *ngIf is doing.

The navigation view model (navmenu.component.ts) changed a bit more. The following is the complete file.

import { Component } from '@angular/core';
import { AuthService } from '../services/auth.service'
import { GlobalEventsManager } from '../services/global.events.manager'

@Component({
    selector: 'nav-menu',
    templateUrl: './navmenu.component.html',
    styleUrls: ['./navmenu.component.css']
})
export class NavMenuComponent {
    public _loggedIn: boolean = false;

    constructor (
      private _authService: AuthService,
      private _globalEventsManager: GlobalEventsManager) {
          _globalEventsManager.showNavBarEmitter.subscribe((mode)=>{
            // mode will be null the first time it is created, so you need to igonore it when null
            if (mode !== null) {
                console.log("Global Event, sent: " + mode);
                this._loggedIn = mode;
            }
        });
  }

  public login(){
      this._authService.startSigninMainWindow();
  }

  public logout(){
      this._authService.startSignoutMainWindow();
  }
}

New imports were added for the AuthService and GlobalEventsManager which get injected into the constructor of the class. The class also contains a _loggedIn property to track if the user is logged in or not. Finally, functions were added for login and logout to go with the two new links shown in the navigation.

Wrapping up

With the above, the Client Application can now log a user in and out utilizing IdentityServer from Angular. There are a lot of details in the files we just copied, but with a working sample, it is much easier to examine/customize how the process is being handled. Check back next week to see how to call the API Application from the Angular part of the Client Application.

The completed code can be found here.

15 thoughts on “Identity Server: Usage from Angular”

  1. Hi Eric,

    Your articles have been invaluable – thanks.

    Just for anyone who comes across this – my setup is such that I want the angular APP to be calling the API project directly (ie, rather than calling APIs hosted within the same project which then call the API project).

    To test, I moved the SampleDataController to the ApiApp project and added [Authorize] attribute and changed the fetchdata.component.ts to call the ApiApp endpoint using AuthService.AuthGet instead of http.

    This didn’t work initially. I found that in the endSigninMainWindow method on AuthService.ts, I needed to add a line of code to set the _currentUser on the AuthService.

    this._mgr.signinRedirectCallback().then((user) => {
    console.log(“signed in”);
    this._currentUser = user;

    1. Houssam, if you check out this issue on GitHub it deals with what might be happening in your case if you are using server-side rendering.

      If you are not doing server-side rendering then you might want to check out silent renewal.

    2. The same happens for me. I simply run the 0.7.0 version locally, logged in as a user from Angular frontend and then hit F5. The user is gone. Can it be fixed please?

        1. I don’t have any server-side rendering. Pure Angular 4 app communicates with oidc authentication system.

          I grabbed your code, run it (0.7.0) and F5 didn’t work. Can you please try yourself?

          1. I will but I’m afraid it won’t be in time to help if you have a time pressure. Will be next week at a minimum. If you find a fix please share or submit a PR.

        2. Also that doesn’t work if I open the same page being logged in in another browser tab. It simply puts me to login page. The user is null from UserManager.

  2. Scott Kirsten-Roger

    This is the best example that I can find with clear delineation between Identity, API and Client. Is there a way for the client app to have the account information such as the login page and registering page? The point of separating the Identity out as a service is so that we can have multiple client apps authenticate through that one identity app but the login UI pages work much better in angular and can be rebranded very easily for different parts of a business.

    1. Thank you, Scott!

      It is my understanding that keeping the login and registration in the Identity app is part of the value that it brings. It allows multiple apps to share users and remove your client applications from having to deal with that. If you are managing the full user account in the client application then is the identity application even needed?

      I hear what you are saying about the rebranding of the login. I’m not saying it is impossible, but I just don’t know the proper way to handle. I would recommend posting an issue on the Identity Server GitHub. If you do get an answer please come back and share what you find out.

      1. Scott Kirsten-Roger

        Thank you for the quick reply, you confirmed what I has assumed about keeping everything in one place. I did find a hook within then fantastic angular-auth-oidc-client package that would let me create my own login page in angular and send that information off to the identity server. I just have to figure out handling the password reset and login failure, which shouldn’t be tremendously difficult. From your ClientApp in the auth.service.ts one just has to add a line before “this.oidcSecurityService.authorize();” that will set the username and password (or anything else in the identity app’s LoginViewModel class) :

        login() {
        console.log(‘start login’);
        this.oidcSecurityService.setCustomRequestParameters({ ‘Email’: myusername’, ‘Password’: ‘mypassword’ });
        this.oidcSecurityService.authorize();
        }

        1. Scott Kirsten-Roger

          My mistake, the above code does not work, I must have had a token already in my browser that allowed authorization inside. I was unable to delete my comment. So sorry.

Leave a Reply to Blake Cancel Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.