IdentityServer4

Identity Server: Changing Angular OpenID Connect Clients

Thanks to Andrew Stegmaier opening this issue on the repo that goes with my IdentityServer exploration I was made aware of a certified OpendID Connect client specifically written for Angular (4+). The angular-auth-oidc-client was created by damienbod. This post is going to cover the transition to this new client. The starting point of the code can be found here. All the changes discussed in this post take place in the ClientApp project.

Package Changes

In package.json the following changes need to be made using your package manager of choice or manually changing the fill and doing a restore.

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

Add:
"angular-auth-oidc-client": "^1.3.1"

App Module Changes

Both app.module.client.ts and app.module.server.ts got a little cleanup to remove the duplicate provider code. The following lines were deleted from both files.

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

The providers array moved to using providers imported from app.module.shared.ts.

Before:
providers: [
    AuthService, AuthGuardService, GlobalEventsManager
]

After:
providers: [
    ...sharedConfig.providers
]

Next, in app.module.shared.ts the following imports were removed.

import { CallbackComponent } from './components/callback/callback.component';
import { GlobalEventsManager } from './components/services/global.events.manager';

Then, the following import for the OpenId Connect client was added.

import { AuthModule } from 'angular-auth-oidc-client';

In the declarations array CallbackComponent was removed. In the imports array AuthModule.forRoot() was added. The route for CallbackComponent was removed and the canActivate condition was removed from the fetch-data route. Finally, the providers section is reduced to only the AuthService. That was a lot of changes, so I am including the full finished class below.

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { AppComponent } from './components/app/app.component'
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
import { CounterComponent } from './components/counter/counter.component';
import { UnauthorizedComponent } from './components/unauthorized/unauthorized.component';

import { AuthModule } from 'angular-auth-oidc-client';
import { AuthService } from './components/services/auth.service';

export const sharedConfig: NgModule = {
    bootstrap: [ AppComponent ],
    declarations: [
        AppComponent,
        NavMenuComponent,
        CounterComponent,
        FetchDataComponent,
        HomeComponent,
        UnauthorizedComponent
    ],
    imports: [
        AuthModule.forRoot(),
        RouterModule.forRoot([
            { path: '', redirectTo: 'home', pathMatch: 'full' },
            { path: 'home', component: HomeComponent },
            { path: 'unauthorized', component: UnauthorizedComponent },
            { path: 'counter', component: CounterComponent },
            { path: 'fetch-data', component: FetchDataComponent  },
            { path: '**', redirectTo: 'home' }
        ])
    ],
    providers: [ AuthService ]
};

File Deletions

The following files were completely removed. Some of them may come back in a different form, but for the moment the functions they were handling are being dealt with in a different way.

auth-guard.service.ts
callback.component.ts
global.events.manager.ts

Auth Service

The AuthService class was pretty much rewritten since it is at the core of the interaction with the OpenId Connect client. It still contains pretty much all the functionality as before just using the new client. The following is most of the class. I removed all of the HTTP calls except for get to save space.

import { Injectable, Component, OnInit, OnDestroy } from '@angular/core';
import { Http, Headers, RequestOptions, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { Subscription } from 'rxjs/Subscription';

import { OidcSecurityService, OpenIDImplicitFlowConfiguration } from 'angular-auth-oidc-client';

@Injectable()
export class AuthService implements OnInit, OnDestroy {
    isAuthorizedSubscription: Subscription;
    isAuthorized: boolean;

    constructor(public oidcSecurityService: OidcSecurityService,
        private http: Http) {

        const openIDImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration();
        openIDImplicitFlowConfiguration.stsServer = 'http://localhost:5000';

        openIDImplicitFlowConfiguration.redirect_url = 'http://localhost:5002/callback';
        // The Client MUST validate that the aud (audience) Claim contains its client_id value registered at the Issuer identified by the iss (issuer) Claim as an audience.
        // The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client.
        openIDImplicitFlowConfiguration.client_id = 'ng';
        openIDImplicitFlowConfiguration.response_type = 'id_token token';
        openIDImplicitFlowConfiguration.scope = 'openid profile apiApp';
        openIDImplicitFlowConfiguration.post_logout_redirect_uri = 'http://localhost:5002/home';
        openIDImplicitFlowConfiguration.start_checksession = true;
        openIDImplicitFlowConfiguration.silent_renew = true;
        openIDImplicitFlowConfiguration.startup_route = '/home';
        // HTTP 403
        openIDImplicitFlowConfiguration.forbidden_route = '/forbidden';
        // HTTP 401
        openIDImplicitFlowConfiguration.unauthorized_route = '/unauthorized';
        openIDImplicitFlowConfiguration.log_console_warning_active = true;
        openIDImplicitFlowConfiguration.log_console_debug_active = false;
        // id_token C8: The iat Claim can be used to reject tokens that were issued too far away from the current time,
        // limiting the amount of time that nonces need to be stored to prevent attacks.The acceptable range is Client specific.
        openIDImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = 10;

        this.oidcSecurityService.setupModule(openIDImplicitFlowConfiguration);
    }

    ngOnInit() {
        this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe(
            (isAuthorized: boolean) => {
                this.isAuthorized = isAuthorized;
            });

        if (window.location.hash) {
            this.oidcSecurityService.authorizedCallback();
        }
    }

    ngOnDestroy(): void {
        this.isAuthorizedSubscription.unsubscribe();
    }

    authorizedCallback() {
        this.oidcSecurityService.authorizedCallback();
    }

    getIsAuthorized(): Observable<boolean> {
        return this.oidcSecurityService.getIsAuthorized();
    }

    login() {
        console.log('start login');
        this.oidcSecurityService.authorize();
    }

    logout() {
        console.log('start logoff');
        this.oidcSecurityService.logoff();
    }

    get(url: string, options?: RequestOptions): Observable<Response> {
        if (options) {
            options = this.setRequestOptions(options);
        }
        else {
            options = this.setRequestOptions();
        }
        return this.http.get(url, options);
    }

    private setRequestOptions(options?: RequestOptions) {
        if (options) {
            this.appendAuthHeader(options.headers);
        }
        else {
            options = new RequestOptions({ headers: this.getHeaders(), body: "" });
        }
        return options;
    }

    private getHeaders() {
        let headers = new Headers();
        headers.append('Content-Type', 'application/json');
        this.appendAuthHeader(headers);
        return headers;
    }

    private appendAuthHeader(headers: Headers) {       
        const token = this.oidcSecurityService.getToken();

        if (token == '') return;

        const tokenValue = 'Bearer ' + token;
        headers.append('Authorization', tokenValue);
    }
}

It doesn’t show the best in the world here so be sure and check it out on GitHub. All the IdentityServer configuration is done in the constructor using the OpenIDImplicitFlowConfiguration class.

Navigation Component

The NavMenuComponent class now needs some changes to match the new AuthService. First, the following change to the imports.

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

After:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { AuthService } from '../services/auth.service';

The AuthService class now provides the ability to subscribe to changes in the user’s authorization. To handle the subscription and unsubscription the class will implement both OnInit and OnDestroy. Here is the new class declaration.

export class NavMenuComponent implements OnInit, OnDestroy

Next, here is the implementation of ngOnInit which handles the subscription to the change in isAuthorized.

ngOnInit() {
    this.isAuthorizedSubscription = 
            this.authService.getIsAuthorized().subscribe(
                   (isAuthorized: boolean) => {
                      this.isAuthorized = isAuthorized;
                    });

    if (window.location.hash) {
        this.authService.authorizedCallback();
    }
}

Then, ngOnDestroy handles the unsubscription.

ngOnDestroy(): void {
    this.isAuthorizedSubscription.unsubscribe();
}

The class level variable for _loggedIn is replaced with the following two variables.

isAuthorizedSubscription: Subscription;
isAuthorized: boolean;

The constructor has been greatly simplified and now only takes an instance of the AuthService.

constructor(public authService: AuthService) {
}

Finally, the login and logout functions have changed to match the new function names in the AuthService class.

public login() {
    this.authService.login();
}

public logout() {
    this.authService.logout();
}

Navigation Component UI

In the navmenu.component.html file, a couple of tweaks are required based on the new variable names used above. The first set is related to showing either Login or Logout.

Before:
<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>

After:
<li [routerLinkActive]="['link-active']">
    <a *ngIf="!isAuthorized" (click)="login()" [routerLink]="['/login']">
        <span class="glyphicon glyphicon-user"></span> Login
    </a>
</li>

<li [routerLinkActive]="['link-active']">
    <a *ngIf="isAuthorized" (click)="logout()">
        <span class='glyphicon glyphicon-log-out'></span> Logout</a>
</li>

The final change in this file was to make the link to fetch-data only show if the user is logging instead of sending the user to an unauthorized view.

Before:
<a [routerLink]="['/fetch-data']">
    <span class='glyphicon glyphicon-th-list'></span> Fetch data
</a>

After:
<a *ngIf="isAuthorized" [routerLink]="['/fetch-data']">
    <span class='glyphicon glyphicon-th-list'></span> Fetch data
</a>

Fetch Data Component

The final changes for the conversion to the new client are in the fetchdata.component.ts and they are only needed because of a rename of the HTTP Get helper in the AuthService.

Before:
authService.AuthGet(apiUrl + 'SampleData/WeatherForecasts').subscribe(result => {

After:
authService.get(apiUrl + 'SampleData/WeatherForecasts').subscribe(result => {

Wrapping Up

This change took a lot of changes, but in the long run, it is going to be a better choice since the new client is focused on Angular. Another great thing about this client is they are looking into ways to handle the first load not remembering the user is logged in due to server side rendering (issue #36).

The finished code for this post can be found here.

Identity Server: Changing Angular OpenID Connect Clients Read More »

Identity Server: Redirect When Route Requires Logged in User

This post is going to continue where the series on IdentityServer4 left off, but I am not officially making it part of the series. There may be a few posts like this where I improve on the example applications from the series. The starting code for this post can be found here.

All the changes in the post are in the Client Application from the sample linked above. I did some cleanup on a couple of files so if you are looking for the differences keep in mind most of the changes are a result of the cleanup.

Unauthorized Component

The first step is to add a new component that will be shown to the user when they navigate to a page that requires them to be logged in but they are not. Add a unauthorized.component.html file to the ClientApp/app/components/unauthorized/ directory with the following contents.

<h2>
    Login is required to access this area
</h2>
<div>
    <button type="button" 
            class="btn btn-primary" 
            (click)="login()">Login</button>
    <button type="button" 
            class="btn btn-default" 
            (click)="goback()">Back</button>
</div>

This will tell the user they need to log in and provide a login button and a button to go back to the previous page. Next, add a unauthorized.component.ts file to the same directory. This class will handle the clicks from the view.

import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { AuthService } from '../services/auth.service';

@Component({
    selector: 'app-unauthorized',
    templateUrl: 'unauthorized.component.html'
})
export class UnauthorizedComponent implements OnInit {

    constructor(private location: Location, private service: AuthService) {

    }

    ngOnInit() {
    }

    login() {
        this.service.startSigninMainWindow();
    }

    goback() {
        this.location.back();
    }
}

This class is using the AuthService for login and Angular’s Location class to move back to the previous page.

New Component Usage

Now that this new component exists it needs to set up in app.module.shared.ts. First, add an import.

import { UnauthorizedComponent } from './components/unauthorized/unauthorized.component';

Next, add to the declarations array.

declarations: [
    AppComponent,
    NavMenuComponent,
    CounterComponent,
    FetchDataComponent,
    HomeComponent,
    CallbackComponent,
    UnauthorizedComponent
]

Finally, add unauthorized to the routes array.

RouterModule.forRoot([
    { path: '', redirectTo: 'home', pathMatch: 'full' },
    { path: 'home', component: HomeComponent },
    { path: 'callback', component: CallbackComponent },
    { path: 'unauthorized', component: UnauthorizedComponent },
    { path: 'counter', component: CounterComponent },
    { path: 'fetch-data', component: FetchDataComponent, 
                          canActivate:[AuthGuardService]  },
    { path: '**', redirectTo: 'home' }
])

Now that this new component is in place how does it get used? Well, any route that has canActivate:[AuthGuardService] will require the user to be logged in to activate. For example, the fetch-data route above won’t activate unless the user is logged in.

Auth Guard Service

AuthGuardService is an existing class in the project. The following is the full file.

import { Injectable, Component } from '@angular/core';
import { CanActivate, Router } from '@angular/router';

import { AuthService } from './auth.service';

@Injectable()
export class AuthGuardService implements CanActivate {

    constructor(private authService: AuthService, private router: Router) {
    }

    canActivate() {
        if (this.authService.loggedIn) {
            return true;
        }
        else {
            this.router.navigate(['unauthorized']);
        }
    }
}

As you can see in the canActivate function if the user is logged in then the function returns true otherwise, the user is routed to the unauthorized component. Before the changes in this post, this dropped the user back on the home page since the unauthorized component didn’t exist.

Wrapping up

With the changes above the user gets a slightly better experience. Just being dropped on the home page wasn’t very helpful as to why that was happening. This at least lets the user know they need to log in. Another option could be to hide the navigation for the routes they don’t have access to until they log it.

The finished version of the code can be found here.

Identity Server: Redirect When Route Requires Logged in User Read More »

Identity Server: Calling Secured API 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 is going to take the solution from last week, the code can be found here, and add an example of the Client Application (Angular) calling an endpoint on the API Application that requires a user with permissions.

API Application

To provide an endpoint to call with minimal changes this example just moves the SampleDataController from the Client Application to the API Application. The following is the full class.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace WebApplicationBasic.Controllers
{
    [Route("api/[controller]")]
    [Authorize]
    public class SampleDataController : Controller
    {
        private static string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", 
            "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        [HttpGet("[action]")]
        public IEnumerable<WeatherForecast> WeatherForecasts()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5)
                             .Select(index => new WeatherForecast
            {
                DateFormatted = DateTime.Now.AddDays(index).ToString("d"),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            });
        }

        public class WeatherForecast
        {
            public string DateFormatted { get; set; }
            public int TemperatureC { get; set; }
            public string Summary { get; set; }

            public int TemperatureF
            {
                get
                {
                    return 32 + (int)(TemperatureC / 0.5556);
                }
            }
        }
    }
}

Make special note that this class now has the Authorize attribute applied which is the only change that was made when moving the file from the Client Application. This attribute is what will require an authorized user for all the routes this controller services.

Client Application

In the Client Application, the first step is to remove the SampleDataController since it is now in the API Application.

Next, in the app.module.client.ts file, add a new provider which can be used to supply the URL of the API to the rest of the Client Application. Don’t take this as best practices for injecting configuration data it is just an easy way to handle it in this application. The following is the full class without the imports (which haven’t changed) the new item is the API_URL.

@NgModule({
    bootstrap: sharedConfig.bootstrap,
    declarations: sharedConfig.declarations,
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        ...sharedConfig.imports
    ],
    providers: [
        { provide: 'ORIGIN_URL', useValue: location.origin },
        { provide: 'API_URL', useValue: "http://localhost:5001/api/" },
        AuthService, AuthGuardService, GlobalEventsManager
    ]
})
export class AppModule {
}

Now for the changes that need to be made to the FetchDataComponent which is the class that will call the new API endpoint. First, add an import for the AuthService.

import { AuthService } from '../services/auth.service';

Next, there are a couple of changes to the signature of the constructor. The first is to use ‘API_URL’ instead of ‘ORIGIN_URL’. The second is to provide for injection of the AuthService. The following is a comparison between the version of the constructor signature.

Before:
constructor(http: Http, @Inject('ORIGIN_URL') originUrl: string)

After:
constructor(http: Http, @Inject('API_URL') apiUrl: string, authService: AuthService)

The final change is to use authService.AuthGet with the new URL instead of http.get.

Before:
http.get(originUrl + 
         '/api/SampleData/WeatherForecasts').subscribe(result => {
    this.forecasts = result.json() as WeatherForecast[];
});

After:
authService.AuthGet(apiUrl + 
                    '/SampleData/WeatherForecasts').subscribe(result => {
    this.forecasts = result.json() as WeatherForecast[];
});

With the above changes, the user has to be logged in or the API will respond with not authorized for the weather forecasts end point. The Client Application doesn’t have anything to provide the user with the fact they aren’t authorized at the moment, but that is outside the scope of this entry.

So far we haven’t look at the code in the AuthService class, but I do want to explain what the AuthGet function is doing and the related functions for put, delete, and post. These calls are wrappers around the standard Angular HTTP library calls that add authorization headers based on the logged in user. The following is the code of the AuthGet as well as two helper functions the class uses to add the headers.

AuthGet(url: string, options?: RequestOptions): Observable<Response> {
    if (options) {
      options = this._setRequestOptions(options);
    }
    else {
      options = this._setRequestOptions();
    }
    return this.http.get(url, options);
}

private _setAuthHeaders(user: User) {
    this._authHeaders = new Headers();
    this._authHeaders.append('Authorization', 
                             user.token_type + " " + user.access_token);
    this._authHeaders.append('Content-Type', 'application/json');
}

private _setRequestOptions(options?: RequestOptions) {
    if (options) {
      options.headers.append(this._authHeaders.keys[0],
                             this._authHeaders.values[0]);
    }
    else {
      //setting default authentication headers
      this._setAuthHeaders(this._currentUser);
      options = new RequestOptions({ headers: this._authHeaders, 
                                     body: "" });
    }
    return options;
}

Wrapping up

It feels like this application is finally getting to the point where other development could happen if it were more than a demo, which is exciting. My thought on how this could be used for real applications is the Identity Application would stand on its own and be used by many clients. The Client Application with a few more tweaks could be used as a template for Angular applications. The completed code can be found here.

This post finishes up the core of what I set out to learn about IdentityServer, but there could be more related posts as I continue to add some polish to the current implementation of the sample solution.

Identity Server: Calling Secured API from Angular Read More »

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.ts, app.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.

Identity Server: Usage from Angular Read More »

Identity Server: Using Entity Framework Core for Configuration Data

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 (this post)
Identity Server: Usage from Angular

This post is going to take the existing solution this series has been using and switch from using hard coded configuration data, found in the Config class of the Identity Application and moving it to a database using Entity Framework Core. As with prior entries, this will be following the intent of one of the official quick starts for Using Entity Framework Core for configuration data. This post is fairly different just because our example project already uses entity framework so a lot of steps can be skipped. The starting point of the code can be found here. All the changes in this post will be taking place in the Identity Application.

Identity Application

Thankfully the creators of IdentityServer provide a NuGet package that includes all the bits needed to move configuration data and operational data to Entity Framework Core. Start by added the following NuGet package.

  • IdentityServer4.EntityFramework
Startup

With the above NuGet package installed the ConfigureServices function of the Startup class needs to be changed to tell IdentityServer the new place to pull data from. The following is the new version of the AddIdentityServer call updated to use Entity Framework Core.

var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

services
  .AddIdentityServer()
  .AddTemporarySigningCredential()
  .AddAspNetIdentity<ApplicationUser>()
  .AddConfigurationStore(builder =>
    builder
     .UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                   options =>
            options.MigrationsAssembly(migrationsAssembly)))
  .AddOperationalStore(builder =>
    builder
     .UseSqlServer(Configuration.GetConnectionString("DefaultConnection"),
                   options =>
            options.MigrationsAssembly(migrationsAssembly)));

Notice that the following have all been replaced by AddConfigurationStore and AddOperationalStore.

  • AddInMemoryPersistedGrants
  • AddInMemoryIdentityResources
  • AddInMemoryApiResources
  • AddInMemoryClients

The other thing of note is the migrationsAssembly and its usage via options.MigrationsAssembly. This is moving the management of the migrations from the assembly that the contexts are defined to the Identity Application. This is needed in this case since the two contexts in question are defined in a NuGet package.

Migrations

Now that the configuration is done for the new contexts migrations need to be added to them. As always there are two ways to handle this either via the Package Manager Console or from a command prompt. I am going to use the command prompt this round to match the IdentityServer docs. Run the following two commands from the same path as the Identity Application’s csproj file.

dotnet ef migrations add InitConfigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/Configuration

dotnet ef migrations add InitPersistedGrant -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrant

This is the first time I have used the -o argument which controls where the migration is output and following the docs example I am putting the migrations that are for entities outside of the control of the application into a subdirectory. Speaking of the entities being outside of the control of the main application, this means anytime the NuGet package that contains the entity is updated a check will need to be made to see if new migrations are needed.

Database Migrations and Seed Data

Since the DbContext classes that need migrations run are outside of the control our application if automatic migrations must be handled in a different way than with the identity-related context used previously in this series. Following the official docs, I am going to create an InitializeDatabase function that will apply any needed migrations as well as add seed data. To do this I am adding a new IdentityServerDatabaseInitialization class in the Data/IdentityServer directory. The following is the full class.

public static class IdentityServerDatabaseInitialization
{
    public static void InitializeDatabase(IApplicationBuilder app)
    {
        using (var serviceScope = app.ApplicationServices
                                     .GetService<IServiceScopeFactory>()
                                     .CreateScope())
        {
            PerformMigrations(serviceScope);
            SeedData(serviceScope);
        }
    }

    private static void PerformMigrations(IServiceScope serviceScope)
    {
        serviceScope.ServiceProvider
                    .GetRequiredService<ConfigurationDbContext>()
                    .Database
                    .Migrate();
        serviceScope.ServiceProvider
                    .GetRequiredService<PersistedGrantDbContext>()
                    .Database
                    .Migrate();
    }

    private static void SeedData(IServiceScope serviceScope)
    {
        var context = serviceScope
                       .ServiceProvider
                       .GetRequiredService<ConfigurationDbContext>();

        if (!context.Clients.Any())
        {
            foreach (var client in Config.GetClients())
            {
                context.Clients.Add(client.ToEntity());
            }
            context.SaveChanges();
        }

        if (!context.IdentityResources.Any())
        {
            foreach (var resource in Config.GetIdentityResources())
            {
                context.IdentityResources.Add(resource.ToEntity());
            }
            context.SaveChanges();
        }

        if (!context.ApiResources.Any())
        {
            foreach (var resource in Config.GetApiResources())
            {
                context.ApiResources.Add(resource.ToEntity());
            }
            context.SaveChanges();
        }
    }
}

The InitializeDatabase takes an IApplicationBuilder in order to be able to control the lifetime of the two DbContext classes needed. Normally this wouldn’t be needed and the lifetime would be controlled automatically, but since this code is being called from the Startup class instead of during a request (which is how the DI system does auto scoping) the scope is being created by the app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope() call.

The PerformMigrations function pulls the two DbContext objects from the container and applies migrations. Finally in SeedData if the DbSets don’t already contain data then the seed data is pulled from the Config class and saved to the database.

Back to Startup

In the Configure function of the Startup class add the following call to make sure migrations and seed data are run when the application starts.

IdentityServerDatabaseInitialization.InitializeDatabase(app);

Wrapping up

With the above changes, the Identity Application is now using the database for all its persistence. The missing bits are of course UI to manage the related data, but those can be built out as needed. The code in its completed state can be found here.

The next steps for this project will be utilizing IdentityServer from Angular in the Client Application instead of the temporary IdentityController that has had to be used in all the examples so far.

Identity Server: Using Entity Framework Core for Configuration Data Read More »

Identity Server: Using ASP.NET Core Identity

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 (this post)
Identity Server: Using Entity Framework Core for Configuration Data
Identity Server: Usage from Angular

This post is going to cover using ASP.NET Core Identity instead of an in-memory user store like the previous examples. As I write this I am working through the Using ASP.NET Core Identity quick start from the docs. This isn’t going to differ a whole lot from the official docs, but I still want to document it to help solidify everything in my head. The starting point of the code for this post can be found here.

Identity Application

The Identity Application will be where the bulk of the changes happen. Since it is much easier to add IdentityServer to a project than it is to add ASP.NET Core Identity we are going to delete the existing Identity Application project and re-create it with Identity from the start. Right click on the IdentityApp project and click remove.

This removes the project from the solution, but the files also need to be deleted off of disk or use a different name. I chose to rename the old project folder on disk so I could still grab any files I might need.

Create a new Identity Application

Right-click on the solution and select Add > New Project.

On the Add New Project dialog under Visual C# > .NET Core select ASP.NET Core Web Application and enter the name of the project (IdentityApp in this example) and click OK.

On the next dialog select the Web Application template.

Next, click the Change Authentication button and select the Individual User Accounts option.

Click OK on the Change Authentication dialog and then click OK on the template dialog. After a few seconds, the solution will contain a new IdentityApp that is using ASP.NET Core Identity with Entity Framework Core.

Adding Identity Server to the Identity App Project

Using NuGet install the IdentityServer4.AspNetIdentity package which will also install IdentityServer4 which the old project was using. Next, copy the Config class from the old IdentityApp project and delete the GetUsers function.

Startup Changes

In the Startup class at the end of ConfigureServices function add the following.

services.AddIdentityServer()
    .AddTemporarySigningCredential()
    .AddInMemoryIdentityResources(Config.GetIdentityResources())
    .AddInMemoryApiResources(Config.GetApiResources())
    .AddInMemoryClients(Config.GetClients())
    .AddAspNetIdentity<ApplicationUser>();

The only difference between this and the one used with the previous posts is instead of AddTestUsers being used to pull a hard coded list of uses our of the Config class users are pulled from the database using ASP.NET Core Identity using this AddAspNetIdentity<ApplicationUser>() call. Identity Server is very flexible and this is only of the option for an identity store.

Next, in the Configure function add app.UseIdentityServer() after app.UseIdentity().

app.UseStaticFiles();
app.UseIdentity();
app.UseIdentityServer();
app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});
Database

There are a couple of ways to make sure the database is created and migrated when changes happen. One is via the command line in the project directory using the following command.

dotnet ef database update

The way I normally us when at this stage in development is to add code to the DB context to automatically apply migrations. The following is the full ApplicationDbContext class modified to automatically run migrations when the context is constructed.

public sealed class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    private static bool _migrated;

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
        if (_migrated) return;
        Database.Migrate();
        _migrated = true;
    }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
    }
}
Now throw that all away

The above is good to go through to know how things work, but I got to this point and wanted the functionality to be on par with the previous entries. The docs made this sound simple, but it was not simple at all I had a list of at least 30 points of files to be moved and changes made to existing files. I am going to spare you all those details and recommend that you just pull the Identity Application from my GitHub repo for this project instead. I did basically the same thing out of the official samples repo to get things working as they should with contents, errors, and log out.

If you want the auto migrations then make sure and keep the version of the ApplicationDbContext from above.

Client Application

No changes are actually required to the client application, but as in the official docs, I made changes to show hitting the API Application using both a user access token and client credentials. The following is the index action on the IdentityController which has been changed to call two functions one for each type of API access.

[Authorize]
public async Task<IActionResult> Index()
{
    var apiCallUsingUserAccessToken = await ApiCallUsingUserAccessToken();
    ViewData["apiCallUsingUserAccessToken"] 
        = apiCallUsingUserAccessToken.IsSuccessStatusCode 
          ? await apiCallUsingUserAccessToken.Content.ReadAsStringAsync() 
          : apiCallUsingUserAccessToken.StatusCode.ToString();

    var clientCredentialsResponse = await ApiCallUsingClientCredentials();
    ViewData["clientCredentialsResponse"] 
        = clientCredentialsResponse.IsSuccessStatusCode 
          ? await clientCredentialsResponse.Content.ReadAsStringAsync() 
          : clientCredentialsResponse.StatusCode.ToString();

    return View();
}

The following is the function to access the API Application using a user access token.

private async Task<HttpResponseMessage> ApiCallUsingUserAccessToken()
{
    var accessToken 
        = await HttpContext.Authentication.GetTokenAsync("access_token");

    var client = new HttpClient();
    client.SetBearerToken(accessToken);

    return await client.GetAsync("http://localhost:5001/api/identity");
}

Now the function to access the API Application using client credentials.

private async Task<HttpResponseMessage> ApiCallUsingClientCredentials()
{
    var tokenClient 
        = new TokenClient("http://localhost:5000/connect/token", 
                          "mvc", "secret");
    var tokenResponse 
        = await tokenClient.RequestClientCredentialsAsync("apiApp");

    var client = new HttpClient();
    client.SetBearerToken(tokenResponse.AccessToken);

    return await client.GetAsync("http://localhost:5001/api/identity");
}

Finally, Index.cshtml found in the Views/Identity directory has the following change.

Replace:
@ViewData["apiResult"]

With:
<dt>api response called with user access token</dt>
<dd>@ViewData["apiCallUsingUserAccessToken"]</dd>

<dt>api response called with client credentials</dt>
<dd>@ViewData["clientCredentialsResponse"]</dd>

Wrapping up

Now the Identity Application is using ASP.NET Core Identity with Entity Framework Core to store users in the database. The next post will cover moving the items now in the Config class into the database. The completed version of the code can be found here.

Identity Server: Using ASP.NET Core Identity Read More »

Identity Server: From Implicit to Hybrid Flow

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 (this post)
Identity Server: Using ASP.NET Core Identity
Identity Server: Using Entity Framework Core for Configuration Data
Identity Server: Usage from Angularsing MVC

This post is going to cover adding back in the API access that was lost in the last post by changing the MVC client to use a hybrid grant instead of an implicit grant. This post was written while working through Switching to Hybrid Flow and adding API Access back in the official docs.

Identity Application

The changes to the Identity Application are pretty simple and only involve tweaking the settings on the MVC client found in the GetClients function of the Config class. First, change the AllowedGrantTypes from Implicit to HybridAndClientCredentials. Next, a client secret should be added.

ClientSecrets =
{
    new Secret("secret".Sha256())
}

This is, of course, a bad secret, but this is only an example. Next, add “apiApp” to the AllowedScopes and finally add AllowOfflineAccess = true. The following is the full client code.

new Client
{
    ClientId = "mvc",
    ClientName = "MVC Client",
    AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,

    ClientSecrets =
    {
        new Secret("secret".Sha256())
    },

    RedirectUris           = { "http://localhost:5002/signin-oidc" },
    PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

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

Most of the above are straight forward. AllowedGrantTypes is what is moving to the hybrid flow which then needs a client secret to ensure everything is on the up and up. This client should be able to hit the API application so it is added to the allowed scopes. AllowOfflineAccess is less clear to me. According to the docs, it allows the requesting refresh tokens for long-lived API access. This would take some more digging before production to ensure authorization isn’t too long lived.

Client Application

Changes to the client application were pretty minimal as well. First, in the Configure function of the Startup class, the UseOpenIdConnectAuthentication call must pass a few more items. The following is the full set up.

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
    AuthenticationScheme = "oidc",
    SignInScheme = "Cookies",

    Authority = "http://localhost:5000",
    RequireHttpsMetadata = false,

    ClientId = "mvc",
    ClientSecret = "secret",

    ResponseType = "code id_token",
    Scope = { "apiApp", "offline_access" },

    GetClaimsFromUserInfoEndpoint = true,
    SaveTokens = true
});

ClientSecret should match what was set up for the client in the Identity Application. According to the docs setting ResponseType to code id_token means use a hybrid flow. This is another point that I would want to dig more on. Scope is requesting access to the API Application and offline access which is the matching part to the offline access set up in the Identity Application. GetClaimsFromUserInfoEndpoint tells the middleware to go to the user info endpoint to retrieve additional claims after getting an identity token.

Identity Controller

The Index action in the IdentityController ends up being much simpler than it was in the previous posts. The following is the full function.

[Authorize]
public async Task<IActionResult> Index()
{
  var accessToken = 
       await HttpContext.Authentication.GetTokenAsync("access_token");

  var client = new HttpClient();
  client.SetBearerToken(accessToken);

  var apiResponse = 
       await client.GetAsync("http://localhost:5001/api/identity");

  ViewData["apiResult"] = 
       apiResponse.IsSuccessStatusCode 
       ? await apiResponse.Content.ReadAsStringAsync() 
       : apiResponse.StatusCode.ToString();

  return View();
}

In the new version, the token can be retrieved from the HTTP context instead of using the DiscoveryClient and TokenClient like the previous version of this code did. The general idea is the same in both which is to get a token, use the token as part of a request to the API application, and finally display the response in a view.

Identity View

The last set of changes is to the Index.cshtml file in the View/Identity directory which is the view that goes with the Index action of the IdentityController. The view displays the access token, refresh token, results of the API call, and the logged in user’s claims.

@using Microsoft.AspNetCore.Authentication
@{
    ViewData["Title"] = "Identity";
}

<dt>access token</dt>
<dd>@await ViewContext.HttpContext.Authentication.GetTokenAsync("access_token")</dd>

<dt>refresh token</dt>
<dd>@await ViewContext.HttpContext.Authentication.GetTokenAsync("refresh_token")</dd>

@ViewData["apiResult"]

<h3>User claims</h3>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

<form asp-controller="Identity" asp-action="Logout" method="post">
    <button type="submit">Logout</button>
</form>

Wrapping up

Adding back API access was pretty easy and the new setup will make managing other resources pretty simple. The identity space is still pretty new to me but working through the IdentityServer quickstarts are helping get me up to a basic level of knowledge. The finished code for this post can be found here. Come back next week to convert this example to use ASP.NET Core Identity.

Identity Server: From Implicit to Hybrid Flow Read More »

Identity Server: Interactive Login using MVC

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 (this post)
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

As before the end goal will be having authorization happen from Angular, but in the short term, the Client Application is using MVC/Razor for testing and verifications. The code as it stood before this post can be found here. If you are following along with the official docs I wrote this post while working through the Adding User Authentication with OpenID Connect quickstart.

The main point of this post is to add a way for a user to enter their username and password and get access to a page that requires authorization using the OpenID Connect protocol.

Identity Application

To enable this scenario the Identity Application will need MVC added along with some UI that will be used to handle login, permissions, and log off. First, using NuGet install the following two packages.

  • Microsoft.AspNetCore.Mvc
  • Microsoft.AspNetCore.StaticFiles

Next, in the ConfigureServices of the Startup class MVC needs to be added as a service.

services.AddMvc();

Then in the Configure function use static files and use MVC should be added after the use statement for IdentityServer.

app.UseIdentityServer();
app.UseDeveloperExceptionPage();

// Following are adds
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
UI Changes

For the type of flow being used in this sample, the Identity Application will be in control of the login, grant, log out, and related UI. This is not a small amount of thing to get set up properly. Thankfully the IdentityServer team provides a Quickstart UI for use with the in-memory items we are currently using. The files can be downloaded from the repo linked in the previous line or an easier way is to open a Powershell prompt in the same directory of the Identity application as the Startup.cs file and run the following command.

iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1'))

After the download the project will contain a Quickstart folder with the needed controllers, a Views with of course the needed views, and wwwroot will have all the related files that need to be served with the views.

Config Changes

The Config class needs to be changed to return some more in-memory information to make this new process work. The first is to add a new client for MVC to the GetClients function. The following is the full function, but it is the second Client is the new one.

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        new Client
        {
            ClientId = "clientApp",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            // secret for authentication
            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },

            // scopes that client has access to
            AllowedScopes = { "apiApp" }
        },

        // OpenID Connect implicit flow client (MVC)
        new Client
        {
            ClientId = "mvc",
            ClientName = "MVC Client",
            AllowedGrantTypes = GrantTypes.Implicit,

            RedirectUris = { "http://localhost:5002/signin-oidc" },
            PostLogoutRedirectUris = 
                { "http://localhost:5002/signout-callback-oidc" },

            AllowedScopes =
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile
            }
        }
    };
}

Notice that for the OpenID Connect implicit flow there are URLs that are needed that so this flow knows how to call back into the client application. At this point, I haven’t dug into everything that is going on in the client. The ClientId, ClientName, and URLs related properties are pretty clear. I am not 100% on the AllowedGrantTypes and AllowedScopes, but at this point, I am not going to dive into on these two options.

Next, add a GetIdentityResources function matching the following. This fall in the same category as the two properties above, we are using them without fully digging into them.

public static IEnumerable<IdentityResource> GetIdentityResources()
{
    return new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
    };
}

The last change to the Config class is to add a function to return the in-memory users.

public static List<TestUser> GetUsers()
{
    return new List<TestUser>
    {
        new TestUser
        {
            SubjectId = "1",
            Username = "alice",
            Password = "password",

            Claims = new List<Claim>
            {
                new Claim("name", "Alice"),
                new Claim("website", "https://alice.com")
            }
        },
        new TestUser
        {
            SubjectId = "2",
            Username = "bob",
            Password = "password",

            Claims = new List<Claim>
            {
                new Claim("name", "Bob"),
                new Claim("website", "https://bob.com")
            }
        }
    };
}
Startup Changes

The last change in the Identity Application is to add the new in-memory items to the IdentityServer service in the ConfigureServices function. The following is the full function.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddIdentityServer()
        .AddTemporarySigningCredential()
        .AddInMemoryIdentityResources(Config.GetIdentityResources())
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients())
        .AddTestUsers(Config.GetUsers());
}

Client Application

In order to get the client application to play well with the changes in the Identity Application, a few changes need to be made. First, the following NuGet packages need to be installed.

  • Microsoft.AspNetCore.Authentication.Cookies
  • Microsoft.AspNetCore.Authentication.OpenIdConnect

Next, in the Configure function of the Startup class, the application’s middleware pipeline needs some changes. Add the following line to turn off the JWT claim type mapping. This must be done before calling UseOpenIdConnectAuthentication.

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

Now add in the cookie authentication middleware.

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationScheme = "Cookies"
});

The last change is to add OpenID Connect authentication to the pipeline placed after the cookies middleware.

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
    AuthenticationScheme = "oidc",
    SignInScheme = "Cookies",

    Authority = "http://localhost:5000",
    RequireHttpsMetadata = false,

    ClientId = "mvc",
    SaveTokens = true
});

Notice that the URL of the authority is the URL the Identity Application runs on as well as the client ID match the one we set up in the GetClients function of the Config class in the Identity Application.

Identity Controller

Now that the above is set up we can switch over to the IdentityController and add the Authorize attribute to the Index function.

[Authorize]
public async Task<IActionResult> Index()

This means if a user hits the index action of this controller and isn’t logged in they will be presented with the login page and after login, they will be redirected back to the above index action. That whole process is handled by the OpenId Connect Authentication middleware. The first time I tested the flow and it just worked was magical.

 

The final set of changes for this post is going to be added a way to log out. In the IdentityController add a Logout function.

public async Task Logout()
{
    await HttpContext.Authentication.SignOutAsync("Cookies");
    await HttpContext.Authentication.SignOutAsync("oidc");
}
Identity View Changes

The last change is to add a logout button to the Index.cshtml found in the Views/Identity directory. At the bottom of the page, the following was added to call the Logout action.

<form asp-controller="Identity" asp-action="Logout" method="post">
    <button type="submit">Logout</button>
</form>

Wrapping Up

I already liked the idea of IdentityServer before this post, but after playing with it with the changes above it is emphasized how nice it is. I am very happy I am going down this path instead of trying to work this all out on my own. Stay tuned as this exploration will continue in future posts.

The code in the finished state can be found here.

Update

Turns out there is a bug in the code that goes with this example. In the client application’s IdentityConroller the call to get a token is using clientApp instead of mvc for the client ID when requesting a token. With that change, the call to the API Application will fail since the MVC client doesn’t have access to the API scope. Look for next weeks post where API access will be added to the MVC client.

Identity Server: Interactive Login using MVC Read More »

Identity Server: Sample Exploration and Initial Project Setup

This post will be a continuation of my exploration around Identity Server which was started with this post which was more of an overview of the space and my motivations for learning about Identity Server. There were a lot of things that were unclear to me as I first started looking through the samples so this post is going to communicate so of those issues and hopefully clear them up for you as well.

After this post, the follow-up post should be more focused on one thing instead of trying to cover so much information in on go.

The following are all the posts in this series.

Identity Server: Introduction
Identity Server: Sample Exploration and Initial Project Setup (this post)
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

Typical Sample Solution Structure

I started my exploration with the IdentityServer4.Sample repo specifically in the Quickstarts folder. For me, this was a mistake as I didn’t have a good enough grasp on the larger concepts for the code to provide proper guidance. Big surprise here, but using the docs and actually walking through the project creation was very helpful.

The code associated with this blog can be found here. The solution contains three projects.

  • ApiApp – Backend for the application and is a resource that is will require authorization to access. The API will be an ASP.NET Core Web API.
  • ClientApp – Frontend application that will be requesting authorization. This is an ASP.NET Core application that is hosting an Angular (4) app.
  • IdentityApp – This is ASP.NET Core application that is the IdentityServer and will end up authorizing users and issuing tokens for resources.

Identity Application

For the Identity application, create an ASP.NET Core Web Application using the empty template targeting at least ASP.NET Core 1.1. Next, using NuGet install the IdentityServer4 NuGet package.

Now that the IdentityServer4 NuGet package is installed open up the Startup class and add the following to the ConfigureServices function.

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentityServer()
        .AddTemporarySigningCredential()
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients());
}

The above registers IdentityServer with ASP.NET Core as a service available via dependency injection using temporary and in-memory components as a stand in for testing. The Config class used here will be added a bit later.

Next, the Configure function should look like the following.

public void Configure(IApplicationBuilder app, 
                      IHostingEnvironment env, 
                      ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseDeveloperExceptionPage();
    app.UseIdentityServer();
}

The above is a basic Configure function. app.UseIdentityServer() is the only the only bit related to IdntityServer and it is adding it to the application’s pipeline.

The final part of this application is the Config class which is used to define the in memory test resources for this application. As you can see below it is defining both API resources and Clients. In the future theses, items would be pulled from a datastore.

public class Config
{
    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("apiApp", "API Application")
        };
    }

    public static IEnumerable<Client> GetClients()
    {
        return new List<Client>
        {
            new Client
            {
                ClientId = "clientApp",

                // no interactive user, use the clientid/secret for authentication
                AllowedGrantTypes = GrantTypes.ClientCredentials,

                // secret for authentication
                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },

                // scopes that client has access to
                AllowedScopes = { "apiApp" }
            }
        };
    }
}

API Application

For the API application, create an ASP.NET Core Web Application using the Web API template targeting at least ASP.NET Core 1.1. Next, using NuGet install the IdentityServer4.AccessTokenValidation NuGet package.

Ater the above NuGet package installed open up the Startup class. In the Configure function, the IdentityServer middleware needs to be added to the application pipeline before MVC using the app.UseIdentityServerAuthentication function. The following is the full Configure function.

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

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

    app.UseMvc();
}

Notice that the address of the of the authority must be specified (this will need to be the address the Identity Application is running on) as well as the ApiName matches the API Resource we added in the Config class of the Identity Application.

Next, in following the IdentityServer quickstart docs add a new IdentityController to the project. Just to be 100% clear this is just a test endpoint to show how to require authorization on a controller and isn’t something that is required to use IdentityServer. The controller has a single Get that returns the type and value of all the user’s claims.

[Route("api/[controller]")]
[Authorize]
public class IdentityController : Controller
{
    [HttpGet]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}

The [Authorize] attribute on the class is the bit that requires calls to this endpoint to have authorization. Keep in mind that the same attribute can be left off the class level and added to specific functions if the whole controller doesn’t require authorization.

Adding the [Authorize] attribute means that the IdentityServer middleware we added in the Startup class will validate the token associated with the request to make sure it is from a trusted issues and that it is valid for this API.

Client Application

For the Client application, I deviated from the samples a bit. Instead of just creating a new MVC application I used JavaScriptServices to generate an Angular (4) application. If you want detail on how that is done you can check out this post (yes it says Angular 2, but the newest version of JavaScriptServices now outputs Angular 4 and the steps haven’t changed). An Angular application is my end goal and why I made this deviation from the samples.

After the Client application has been created use NuGet to add the IdentityModel package. This package is to make interacting with the Identity Application simpler.

For this first go instead of actually interacting with the Identity Application from Angular I will be using it from MVC instead. The detail of interaction from Angular will come in a later post. The IdentityController is what does the interaction with both the Identity Application and the API Application interactions in this version of the client application. The following is the full IdentityController class.

public async Task<IActionResult> Index()
{
  var discovery = 
      await DiscoveryClient.GetAsync("http://localhost:5000");

  var tokenClient = new TokenClient(discovery.TokenEndpoint, 
                                    "clientApp", 
                                    "secret");
  var tokenResponse = 
      await tokenClient.RequestClientCredentialsAsync("apiApp");

  ViewData["tokenResult"] = tokenResponse.IsError 
                            ? tokenResponse.Error 
                            : tokenResponse.Json.ToString();

  var client = new HttpClient();
  client.SetBearerToken(tokenResponse.AccessToken);

  var apiResponse = 
      await client.GetAsync("http://localhost:5001/api/identity");
 
  ViewData["apiResult"] = apiResponse.IsSuccessStatusCode 
                          ? await apiResponse.Content.ReadAsStringAsync() 
                          : apiResponse.StatusCode.ToString();

  return View();
}

In the above, you can see the IdentityModel in action. Using the DiscoveryClient means the client application only needs to know about the root address of the Identity Application. TokenClient is being used to request a token from the Identity Application for the clientApp using the client secret which in this case is actually the word secret. Keep in mind in a real application secrets should be kept using the ASP.NET Core Secrets manager, see this related post. Also, take note that clientApp and secret are the values that were defined in the Config class of the Identity application.

The rest of the code is taking the token response and making a call to the API application with the response from both of those calls being stored in ViewData for display on the view associated with the controller.

The view is just an Index.cshtml file in the path Views/Identity. The following is the full view.

@{
    ViewData["Title"] = "Identity";
}

@ViewData["tokenResult"]
@ViewData["apiResult"]

It isn’t pretty, but the whole point of this controller and view is just for verification that the three applications are properly communicating with each other.

URL Configuration

In this setup, it is important that the URL for the Identity Application and API Application be fixed so they can be accessed by the client. In a more production level application, these values would at a minimum need to be in configuration. The following is the setup used for this solution.

  • Identity Application – http://localhost:5000
  • API Application – http://localhost:5001
  • Client Application – http://localhost:5002

There are a couple of ways to configure test values. The first is to open the project properties and select the Debug tab and set the App URL.

The second option is to go to the Program class for each project and add a UseUrls to the WebHostBuilder like the following.

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseUrls("http://localhost:5002")
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .Build();

    host.Run();
}

Wrapping up

After going through the above process I now have a much better understanding of how the very basic setup using Identity Server should work. I hope if you made this far you found some helpful bits.

There is a bit of strangeness using Visual Studio to try and launch all three applications and can result in an error message if multiple of the projects are run in debug mode. For the most part, this can be worked around by only debugging one application at a time. It is a bit annoying at the beginning stages, but once an applications gets past that point I imagian that the Identity Application won’t require much debugging.

If there are any questions please leave a comment and I would be happy to try and help. The finished code can be found here. This basic example will be expanded over time and all the related entries can be found in the IdentityServer category.

Identity Server: Sample Exploration and Initial Project Setup Read More »

Duplicate ‘System.Reflection.AssemblyCompanyAttribute’ attribute

As part of my research into IdentityServer4 I forked their samples repo, but I ran into the following issue trying to build the solution for the Client Credentials quickstart.

CS0579    Duplicate ‘System.Reflection.AssemblyCompanyAttribute’ attribute

CS0579    Duplicate ‘System.Reflection.AssemblyConfigurationAttribute’ attribute

CS0579    Duplicate ‘System.Reflection.AssemblyProductAttribute’ attribute

Searching the project each of these attributes only exists only in the AssemblyInfo.cs file found under properties. It turns out the file that is causing the issues only exists as a result of the build process and is found in the \obj\Debug\netcoreapp1.1\{ProjectName}.AssemblyInfo.cs file.

In this case, the client project is a console application so I created a new console application and the template no longer generates an AssemblyInfo.cs file for .NET Core. It turns out that as part of the move back to csproj most of the information that was in the AssemblyInfo.cs can now be set on the project its self. Open the project properties and select the Package tab to see the new settings.

Resolution

I found this issue on GitHub where there were a couple of options to resolve this issue which I am going to cover here plus a third option I tried not mention in the issue.

Option one is to remove the conflicting items from the AssemblyInfo.cs file. For example, the following would allow the project I am working with to build.

Before:
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Client")]
[assembly: AssemblyTrademark("")]

After:
[assembly: AssemblyTrademark("")]

Option two is to edit the csproj and turn the generation of the attributes causing the issues off.

Before:
<PropertyGroup>
  <TargetFramework>netcoreapp1.1</TargetFramework>
  <OutputType>Exe</OutputType>
</PropertyGroup>

After:
<PropertyGroup>
  <TargetFramework>netcoreapp1.1</TargetFramework>
  <OutputType>Exe</OutputType>
  <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
  <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
  <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
</PropertyGroup>

The third option is to total delete the AssemblyInfo.cs file if your project doesn’t need it. This is the option I chose to go with. For a reason, you might want to keep the file around and just use option one see the “AssemblyInfo.cs is partially dead” section of this post by Muhammad Rehan Saeed. Side note: if you are interested in ASP.NET Core and you aren’t following Rehan you really should be.

Wrapping up

This is an issue that you will really only face when converting an older project to .NET Core. The fix is simple enough once you know what is going on. I opened an issue on the IdentityServer samples if you want to track if this has been fixed yet.

Duplicate ‘System.Reflection.AssemblyCompanyAttribute’ attribute Read More »