Two-factor Authentication in ASP.NET Core 2

This post was going to be an update of the SMS using Twilio Rest API in ASP.NET Core post a made a year or so ago, but once I got the example project created I noticed that the default template has removed SMS as an option for two-factor authentication in favor of authenticator auth support.

This post is going to explore the setup of this new two-factor authentication style. In a follow-up post, I may look at adding back support for SMS as the second factor as that seems like a more common scenario albeit a less secure one.

Project Setup

To create a new Razor Pages application using individual authentication I used the following command from a command prompt. Make sure you are in the directory you want the files to end up. The only reason I mention this is I just spend the last 5 minutes cleaning up my user directory because I forgot to change directories.

dotnet new razor --auth Individual

Next, I move up a directory and used the following command to add a new solution file.

dotnet new sln -n TwoFactorAuth

Finally, I used the following command to add the new project to the solution file. If you aren’t using Visual Studio then you don’t have to have the solution file.

dotnet sln add TwoFactorAuth\TwoFactorAuth.csproj

The project after the above can be found here.

Add a QR Code

Strictly speaking, a QR code isn’t required, but it is much better for users so they don’t have to enter a 32 character key into their authenticator application manually. I followed the official docs to enable QR code generation.

The first step is to download a Javascript QR Code library. I just used the one recommended in the docs, but any would work. This link will take you to a zip download. Open the download and copy qrcode.js and qrcode.min.js into a new qrcode directory inside of your project’s wwwroot/lib/ directory.

Next, find your project’s EnableAuthenticator.cshtml file and update the scripts section at the bottom of the page to the following.

@section Scripts {
    @await Html.PartialAsync("_ValidationScriptsPartial")
    
    <script type="text/javascript" src="~/lib/qrcode/qrcode.js"></script>
    <script type="text/javascript">
        new QRCode(document.getElementById("qrCode"),
            {
                text: "@Html.Raw(Model.AuthenticatorUri)",
                width: 150,
                height: 150
            });
    </script>

Enabling Two-Factor Authentication

The above changes are all that is required, and now we are going to walk through what it looks like for the user. Once logged in click on your user in in the upper right after of the site to open user management.

Next, click the Two-factor authentication link.

Then, click Configure authenticator app link.

This will land you on the page with the QR code.

Now using the authenticator application of your choice you can scan the QR code. Once scanned your authenticator application will display a code. Enter the code in the Verification Code box and click Verify. If all goes well you will be taken to a page that lists a set of recovery code for use if you can’t use your authenticator application for some reason.

Wrapping Up

I think it is awesome how easy the ASP.NET Core team has made the use two-factor authentication. With this being built into the templates it pushes all in the right direction. I do wish they would have left SMS as an option, but hopefully, it wouldn’t be hard to put back in.

The code in the final state can be found here.

Identity Server: Migration to version 2.1 and Angular HTTP Changes

Version 2.1 of Identity Server 4 was released a few weeks and this post is going to cover updating my sample project to the latest version. The starting point of the code can be found here. We are going to tackle this in sections as there are updates needed for an ASP.NET Core Update, Identity Server Update, and some broken bits in Angular.

ASP.NET Core Update

The sample projects were all on ASP.NET Core version 2.0.0. For each project right-click and select Edit ProjectName.csproj. Make the following change.

Before:
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />

After:
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.3" />

Identity Server Update

Right-click the Identity App project and select Edit IdentityApp.csproj. Next, make the following changes.
Before:
<PackageReference Include="IdentityServer4.EntityFramework" Version="2.0.0" />

After:
<PackageReference Include="IdentityServer4.EntityFramework" Version="2.1.0" />

Next, need to add a couple of Entity Framework migrations to see if there were any data changes with the following commands from a command prompt in the Identity App project directory.

dotnet ef migrations add Configration21 -c ConfigurationDbContext -o Data/Migrations/IdentityServer/Configuration
dotnet ef migrations add PersistedGrant21 -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrant

Turns out that there were no data changes for this version so if you are on version 2.0.0 you can skip this step.

Angular Issues

I’m not sure how I didn’t hit this issue on the last update post, but the Client App needs to be changed to use the new Angular HttpClient. I got the following error when trying to run the client application.

An unhandled exception occurred while processing the request.

NodeInvocationException: No provider for PlatformRef!
Error: No provider for PlatformRef!
at injectionError
After some digging, I tracked the issue down to using HttpModule instead of HttpClientModule. To make this transition we need to make a few changes. In the app.module.shared.ts make the following changes to the imports section.
Before:
import { HttpModule } from '@angular/http';

After:
import { HttpClientModule } from '@angular/common/http';

Next, in the imports array make the following change.

Before:
HttpModule

After:
HttpClientModule

Next, in the webpack.config.vendor.js fille add the following to the vendor array.

'@angular/common/http'

The last changes are to the auth.service.ts and they are extensive so instead of going through them I’m just going to post the full class after all the changes.

import { Injectable, Component, OnInit, OnDestroy, Inject } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/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: HttpClient,
        @Inject('ORIGIN_URL') originUrl: string,
        @Inject('IDENTITY_URL') identityUrl: string
    ) {
        const openIdImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration();
        openIdImplicitFlowConfiguration.stsServer = identityUrl;
        openIdImplicitFlowConfiguration.redirect_url = originUrl + 'callback';
        openIdImplicitFlowConfiguration.client_id = 'ng';
        openIdImplicitFlowConfiguration.response_type = 'id_token token';
        openIdImplicitFlowConfiguration.scope = 'openid profile apiApp';
        openIdImplicitFlowConfiguration.post_logout_redirect_uri = originUrl + 'home';
        openIdImplicitFlowConfiguration.forbidden_route = '/forbidden';
        openIdImplicitFlowConfiguration.unauthorized_route = '/unauthorized';
        openIdImplicitFlowConfiguration.auto_userinfo = true;
        openIdImplicitFlowConfiguration.log_console_warning_active = true;
        openIdImplicitFlowConfiguration.log_console_debug_active = false;
        openIdImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = 10;

        this.oidcSecurityService.setupModule(openIdImplicitFlowConfiguration);

        if (this.oidcSecurityService.moduleSetup) {
            this.doCallbackLogicIfRequired();
        } else {
            this.oidcSecurityService.onModuleSetup.subscribe(() => {
                this.doCallbackLogicIfRequired();
            });
        }
    }

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

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

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

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

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

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

    private doCallbackLogicIfRequired() {
        if (typeof location !== "undefined" && window.location.hash) {
            this.oidcSecurityService.authorizedCallback();
        }
    }

    get(url: string): Observable<any> {
        return this.http.get<any>(url, { headers: this.getHeaders() });
    }

    put(url: string, data: any): Observable<any> {
        const body = JSON.stringify(data);
        return this.http.put<any>(url, body, { headers: this.getHeaders() });
    }

    delete(url: string): Observable<any> {
        return this.http.delete<any>(url, { headers: this.getHeaders() });
    }

    post(url: string, data: any): Observable<any> {
        const body = JSON.stringify(data);
        return this.http.post<any>(url, body, { headers: this.getHeaders() });
    }

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

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

        if (token === '') return headers;

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

With all those changes made run the following two commands in a command prompt in the Client App project directory.

node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js
node node_modules/webpack/bin/webpack.js

Wrapping up

This post ended up being more about Angular than Identity Server, but it is nice to have everything upgraded to the latest and working.

The files in the completed can be found here.

Auth0: Usage from Angular

This post is a continuation of my exploration of using Auth0 with ASP.NET Core with an API and an Angular front end. I recommend you start with the first post if you are new to Auth0.

This post is going to add a login from Angular in the Client Application as well as accessing the API once logged in. The starting point of the code can be found here.

API Application

To give us an endpoint in the API to call let’s move the SimpleDataController class from the ClientApp/Controllers directory to ApiApp/Controllers directory. Also, remember to adjust the namespace to reflect this move.

To remove some complication we are going to add a CORS policy to the API Application to allow all CORS request. This wouldn’t necessarily something I would recommend for a production application. For more information on CORS check out this post.

To add the CORS policy open the Startup class and add the following to the ConfigureServices function which adds a CORS policy to DI that allows all calls through.

services.AddCors(options =>
{
    options.AddPolicy("default", policy =>
    {
        policy.AllowAnyOrigin()
            .AllowAnyHeader()
            .AllowAnyMethod();
    });
});

Next, in the Configure function, CORS needs to be added to the HTTP pipeline with the policy added above. The following is the full function body for reference, but only the CORS line is new.

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

app.UseCors("default");
app.UseAuthentication();
app.UseMvc();

Note that the API isn’t currently using Authorization at all and won’t be for this post.

Client Application

Instead of redoing a lot of the same work that I covered in some of my posts on using Identity Server and Angular together I decided to copy the ClientApp directory and use it as a starting point. The rest of the client changes are going to assume the same starting point which means we will be using angular-auth-oidc-client instead of the Auth0 client to do the Open ID Connect bits.

Configuration values from appsettings.json

We need some setting from the ASP.NET Core part of the client application. First, add the following new settings to the appsettings.json file.

"ApiAddress": "http://localhost:50467/api/",
"ClientId": "yourAngularClientId"

Next, Index.cshtml needs to be changed to provide some prerender data. The following shows the new data being passed into the Angular application (this post covers this in detail).

<app asp-prerender-module="ClientApp/dist/main-server"
     asp-prerender-data='new {
    apiUrl = Configuration["ApiAddress"],
    identityUrl = $"https://{Configuration["Auth0:Domain"]}",
    clientId = Configuration["ClientId"]
}'>Loading...</app>

Now that the prerender values are being passed in we need to handle them in the boot.server.ts file in the createServerRenderer export. The following is the full list of values.

export default createServerRenderer(params => {
    const providers = [
        { provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
        { provide: APP_BASE_HREF, useValue: params.baseUrl },
        { provide: 'BASE_URL', useValue: params.origin + params.baseUrl },
        { provide: 'ORIGIN_URL', useValue: params.origin + params.baseUrl },
        { provide: 'API_URL', useValue: params.data.apiUrl },
        { provide: 'IDENTITY_URL', useValue: params.data.identityUrl },
        { provide: 'CLIENT_ID', useValue: params.data.clientId },
        { provide: 'URL_CONFIG', useValue: params.data}
    ];

Down a bit in the file, the setImmediate call needs to be changed to the following.

setImmediate(() => {
    resolve({
        html: state.renderToString(),
        globals: {url_Config: params.data}
    });

Next, in the app.module.browser.ts file we need functions for getting the config values as well as the associated providers. The following is the full file without the imports.

@NgModule({
    bootstrap: [AppComponent],
    imports: [
        BrowserModule,
        AppModuleShared
    ],
    providers: [
        { provide: 'ORIGIN_URL', useFactory: getBaseUrl },
        { provide: 'API_URL', useFactory: apiUrlFactory },
        { provide: 'IDENTITY_URL', useFactory: identityUrlFactory },
        { provide: 'CLIENT_ID', useFactory: clientIdFactory },
        AppModuleShared
    ]
})
export class AppModule {
}

export function getBaseUrl() {
    return document.getElementsByTagName('base')[0].href;
}

export function apiUrlFactory() {
    return (window as any).url_Config.apiUrl;
}

export function identityUrlFactory() {
    return (window as any).url_Config.identityUrl;
}

export function clientIdFactory() {
    return (window as any).url_Config.clientId;
}

Finally, in the auth.service.ts file we need to inject the new configuration values. The following is the constructor that takes in the new values as well as uses them in the set up of the OpenIDImplicitFlowConfiguration.

constructor(public oidcSecurityService: OidcSecurityService,
    private http: HttpClient,
    @Inject('ORIGIN_URL') originUrl: string,
    @Inject('IDENTITY_URL') identityUrl: string,
    @Inject('CLIENT_ID') clientId: string
) {
    const openIdImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration();
    openIdImplicitFlowConfiguration.stsServer = identityUrl;
    openIdImplicitFlowConfiguration.redirect_url = originUrl + 'callback';
    openIdImplicitFlowConfiguration.client_id = clientId;
    openIdImplicitFlowConfiguration.response_type = 'id_token token';
    openIdImplicitFlowConfiguration.scope = 'openid profile apiApp';
    openIdImplicitFlowConfiguration.post_logout_redirect_uri = originUrl + 'home';
    openIdImplicitFlowConfiguration.forbidden_route = '/forbidden';
    openIdImplicitFlowConfiguration.unauthorized_route = '/unauthorized';
    openIdImplicitFlowConfiguration.auto_userinfo = true;
    openIdImplicitFlowConfiguration.log_console_warning_active = true;
    openIdImplicitFlowConfiguration.log_console_debug_active = false;
    openIdImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = 10;

    this.oidcSecurityService.setupModule(openIdImplicitFlowConfiguration);

    if (this.oidcSecurityService.moduleSetup) {
        this.doCallbackLogicIfRequired();
    } else {
        this.oidcSecurityService.onModuleSetup.subscribe(() => {
            this.doCallbackLogicIfRequired();
        });
    }
}

Make sure in the Auth0 client setup you allow the callback listed above or you will run into issues.

Wrapping Up

Working on this post further convinced me that Open ID Connect is the way to go. I basically took an implementation that was meant for Identity Server and used it with Auth0.

Make note that the reason the API is unsecured at this point is that I had an issue getting angular-auth-oidc-client to play nicely with Auth0 for some reason. I doubt it is an issue with either product just some sort of missing configuration on my part. This is the part of the post that I sunk so much time on. In the end, I decided to just skip it for now. If anyone does get that setup work I would love to see the sample so I know what I was doing wrong.

The completed code can be found here.

Auth0: Introduction and Initial Project Setup

As I started my exploration of Identity Server I listed a few alternatives that provided the same type of functionality, but as a Software as a Service. This series of posts will be covering one of the options I mentioned, Auth0.

The big selling points for Auth0, and other services like it, are that it removes you from having to worry about Auth/User Management and get to the part of your applications that bring value to your customers. As with Identity Server, Auth0 can use OpenID Connect (as well as a lot of other protocols), single sign-on and API Access Control.

Sign-up

The first step in getting started is to sign up for a new account. Here is a link to the sign-up page (not an affiliate link). You can use a username and password or a social login. I’m going the social route using GitHub.

After user creation then there are a couple of setup steps. The first is to choose a tenant domain and region.

Click next and on the second step, there are questions about what the account is going to be used for. In my case, it is a personal, developer, who is just playing around.

Click Create Account to finish the creation of your account which will then land you on the account dashboard page.

Auth0 Setup

Client

From the Auth0 Dashboard click the New Client button. Give the client a name, TestMvc in my case, and select Regular Web Applications as the type.

In a follow-up post I will be covering the Single Page Web Application, but for this post, we are going to be using MVC since it tends to be simpler. The next page defaults to a framework selection which seems to be a guide to getting going for the framework you select. We are going to skip that and click on the Settings tab.

On the settings page, we need to fill in a value for Allowed Callback URLs. The sample client should use http://localhost:50774/signin-auth0. Click the Save Changes button.

API

While we are doing some setup on the Auth0 site we are going to go ahead and set up our API as well. Click the APIs menu option on the left menu.

Then click the Create API button. In the dialog enter a Name and Identifier and click the Create button.

Sample Solution Structure and Setup

The sample solution for this post has two projects.

  • ApiApp – Backend application and is a resource that is will require authorization to access. The API is 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) application. Note for this post we will be using MVC and not Angular. A future post will deal with the Angular side.

The sample solution with the two projects already added can be found here. If you are using the sample solution feel free to skip the next two sub-sections as they are going over how the projects were created.

To start, add a directory to contain the solution.

API Application

Inside the solution directory, create an ApiApp directory. From the command line in the ApiApp directory run the following command to create a new ASP.NET Core application using the Web API template.

dotnet new webapi
Client Application

Inside the solution directory, create a ClientApp directory. From the command line in the ClientApp directory run the following command to create a new ASP.NET Core application using the Angular template which as of this writing outputs an Angular 4 application.

dotnet new angular

After generation is done run the following to make the all the NPM packages that are required get installed.

npm install
Solution

Inside the solution directory, let’s create a solution file for use with Visual Studio. Run the following command to create a solution file named AspNetCoreAngularAuth0.sln.

dotnet new sln --name AspNetCoreAngularAuth0

Next, run the following two commands to add the API and Client projects to the solution.

dotnet sln add ApiApp/ApiApp.csproj
dotnet sln add ClientApp/ClientApp.csproj

Securing the API Application

Open the appsettings.json file and add a section for Auth0. We are going to need to store the Auth0 domain (tenant domain from sign up) and API Identifier (from the creation of the API at Auth0).  The following is the full file from the API project with the new Auth0 section.

{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "Auth0": {
    "Domain": "yourTenantDomain.auth0.com",
    "ApiIdentifier": "http://localhost:50467/"
  }
}

Next, in the ConfigureServices function of the Startup class add the following to add authentication using the JWT Bearer scheme to the DI system.

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

}).AddJwtBearer(options =>
{
    options.Authority = $"https://{Configuration["Auth0:Domain"]}/";
    options.Audience = Configuration["Auth0:ApiIdentifier"];
});

In the Configure function add the following line before app.UseMvc() to add authentication to the HTTP pipeline for the API application.

app.UseAuthentication();

The last step in the API for this post is to add a controller that will require authentication. The following is the full code for the AuthTestController that was added to the Controllers directory.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace ApiApp.Controllers
{
    [Route("api/[controller]")]
    [Authorize]
    public class AuthTestController : Controller
    {
        [HttpGet]
        public string Get()
        {
            return "Congratulations you are authenticated";
        }
    }
}

Client Application

In the Client Application open the appsettings.json and add the following setting related to Auth0. This is the full file so the logging section was existing.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "Auth0": {
    "Domain": "yourTenantDomain.auth0.com",
    "ClientId": "yourClientId",
    "ClientSecret": "yourClientSecret",
    "CallbackUrl": "http://localhost:50774/signin-auth0",
    "ApiIdentifier": "yourApiIdentifier"
  }
}

If you are going to be checking in your code into a publically accessible source control I recommend you use user secrets instead of appsettings.json. You can read more about user secrets here.

Next, in the ConfigureServices function of the Startup class add the following. I’m not going to go over this code line by line the gist is it is setting up the application to using cookies and Open ID Connect for authentication.

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options =>
{
    options.Authority = $"https://{Configuration["Auth0:Domain"]}";
    options.ClientId = Configuration["Auth0:ClientId"];
    options.ClientSecret = Configuration["Auth0:ClientSecret"];

    options.ResponseType = "code";
    options.Scope.Clear();
    options.Scope.Add("openid");

    options.CallbackPath = new PathString("/signin-auth0");

    options.ClaimsIssuer = "Auth0";

    options.Events = new OpenIdConnectEvents
    {
        OnRedirectToIdentityProviderForSignOut = (context) =>
        {
            var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";

            var postLogoutUri = context.Properties.RedirectUri;
            if (!string.IsNullOrEmpty(postLogoutUri))
            {
                if (postLogoutUri.StartsWith("/"))
                {
                        // transform to absolute
                        var request = context.Request;
                    postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
                }
                logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
            }

            context.Response.Redirect(logoutUri);
            context.HandleResponse();

            return Task.CompletedTask;
        },
        OnRedirectToIdentityProvider = context =>
        {
            context.ProtocolMessage.SetParameter("audience", Configuration["Auth0:ApiIdentifier"]);
            return Task.FromResult(0);
        }
    };
});

Note that the OnRedirectToIdentityProvider bit is related to getting access to the API.

In the Configure function add the following line before app.UseMvc to add authentication to the HTTP pipeline.

app.UseAuthentication();
Testing Setup

In order to provide a way to test login, logout, and API access without using the Angular portion of the client app, remember that will be a future post, I add an AuthTestController to the Controllers directory with the following.

public class AuthTestController : Controller
{
    public async Task<IActionResult> Index()
    {
        ViewBag.ApiResults = "Not Called";

        var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = 
		     new AuthenticationHeaderValue("Bearer", await HttpContext.GetTokenAsync("access_token"));
        ViewBag.ApiResults = await (await client.GetAsync("http://localhost:50467/api/authtest"))		                     .Content.ReadAsStringAsync();

        return View();
    }

    public async Task Login(string returnUrl = "/")
    {
        await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { RedirectUri = returnUrl });
    }

    [Authorize]
    public async Task Logout()
    {
        await HttpContext.SignOutAsync("Auth0", new AuthenticationProperties
        {
            RedirectUri = Url.Action("Index", "Home")
        });
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
}

Sorry for the formatting, but that sample has long of verbose statements. Nothing crazy going on in this file. There is an Index action that attempts to call the API and then returns a view. The Login and Logout functions do what they say and were pull right from the official docs.

The associated Index.cshtml file was added to the Views/AuthTest directory with the following.

@{
    ViewBag.Title = "Auth Test";
}

@if (User.Identity.IsAuthenticated)
{
    <a asp-controller="AuthTest" asp-action="Logout">Logout</a>
}
else
{
    <a asp-controller="AuthTest" asp-action="Login">Login</a>
}

<p>
    @ViewBag.ApiResults
</p>

This view just shows a link to login or logout and shows the results of the API call. It is ugly but is enough to prove the setup is working.

Wrapping Up

Getting up and running was much fast with Auth0 and would be true of any SASS option I’m sure. It also helped that I have more of an idea of what is going on after all the posts I did on Identity Server. Another positive is Auth0 has some great docs. I used the ASP.NET Core and Web API ones a lot to get this sample application up and running.

Next steps are to get this setup running in the Angular client which should be my next post. The finished code for this post can be found here.

 

Identity Server: Deploy to Azure

This post is going to cover taking the existing set of applications we have been using to learn about Identity Server and deploying them to Azure. The starting point of the code can be found here.

Prep Work

The applications as they stand from the link above are not ready to be pushed to Azure most due to some configuration changes that are needed. We will go through each of the applications and take the hard-coded values and move them to appsettings.json.

API Application Configuration

The API application needs two configuration values for the address of the Identity Application and the address of the Client Application. The following two lines need to be added to the application’s appsettings.json file.

"IdentityServerAddress": "http://localhost:5000",
"ClientAddress": "http://localhost:5002"

Then in the Startup class, the values need to be used. The Identity Server address is used in the JWT Bearer setup.

Before:
o.Authority = "http://localhost:5000";

After:
o.Authority = Configuration["IdentityServerAddress"];

Then the Client address is used in the CORS setup.

Before:
policy.WithOrigins("http://localhost:5002")

After:
policy.WithOrigins(Configuration["ClientAddress"])
Identity Application Configuration

The Identity application needs a configuration value for the address of the address of the Client Application. The following line needs to be added to the application’s appsettings.json file.

"ClientAddress": "http://localhost:5002"

Next, the Config class needs a reference to configuration passed into the GetClients function.

public static IEnumerable<Client> GetClients(IConfiguration configuration)

Next, the references to http://localhost:5002 need to be replaced with the value from the configuration. The following is one example.

Before:
RedirectUris = { "http://localhost:5002/signin-oidc" },

After:
RedirectUris = { $"{configuration["ClientAddress"]}/signin-oidc" },
Identity Application Entity Framework

As part of publishing this set of applications, this example is going to use Azure SQL and right now the application is set up to use SQLite. In the Startup class replace UseSqlite with UseSqlServer. The following is an example of one of the needed replacements.

Before:
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

After:
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

When switching database providers make sure to delete and recreate your database migrations. I didn’t to begin with and it cost me a lot of time in changing down a strange error which this post covers.

Client Application Configuration

The Client application needs two configuration values for the address of the Identity Application and the address of the API Application. The following two lines need to be added to the application’s appsettings.json file.

"IdentityServerAddress": "http://localhost:5000",
"ApiAddress": "http://localhost:5001/"

Then in the Startup class, the Identity Server Address needs to be used in the AddOpenIdConnect call.

Before:
options.Authority = "http://localhost:5000";

After:
options.Authority = Configuration["IdentityServerAddress"];

Next, the configuration values need to be passed to the Angular application. This process ended up being harder to figure out that I had anticipated and turned into a full blog post on its own. See this post for the details. The code for all the changes will also be on GitHub in case you need to the the diff for the client application.

Publish to Azure

Right-click on the Identity Application and select Publish.

This will show the Publish screen which provides the option to publish to Azure. We don’t have an existing App Service so we are going to create a new one. This page in the official docs explains all the options available on the publish screen. Click the publish button to continue.

The next screen that shows is the Create App Service Screen. I used all the default values and created a new Resource Group and App Service Plan. Keep in mind that the resource group and plan will be reused for the remaining two applications we are looking deploy. The only thing that will change between the applications on this screen will be the App Name.

The services tab looks like the following.

Next in the additional resources box lets hit the plus button next to SQL Database since our group of applications is going to need somewhere to store data. This will take us to the Configure SQL Database screen.

Since I don’t already have a SQL Server setup I am going to hit the New button to add one. That results in the next screen where you enter a name for the server as well as a username and password. After entering the required information click OK.

This will put you back on the configure database screen with most of it filled out. Make sure to set the database name you want to use.

Finally back on the Create App Service screen, you will see all the resources that you selected and configured. When you are satisfied with what you see click the Create button and let Azure do its magic.

When it is done you will see the profile now listed on the Publish page.

The above needs to be repeated for both the API and Client Applications, but using the Resource Group and App Service plan created above. Each profile should use a unique application name.

Identity Application Azure Configuration

The Identity Application needs access to the database that we created above. This means we need to set the DefaultConnection. The first step is to determine what the connection string should be. On the Azure Portal in your list of resources select the SQL database that we created above.

On the next page copy the provided connection string. Now navigate to the Identity App Service and under the Settings section select Application settings. Scroll down and find the Connection strings section and enter the copied value as the DefaultConnection.

Just above the Connection strings section we also need to enter a few values in the App settings section. For the Identity Application, we need the Twitter key and secret as well as the address of the client application. The following is a screenshot minus the actual values.

For the ClientAddress use the URL found in the Overview of the Client App’s App Service page.

API Application Azure Configuration

From the list of resources select the API App’s App Service page and in the Settings section select Application settings. In the App settings section add values for IdentityServerAddress and ClientAddress. As with the ClientAddress above the URLs for each application can be found on their respective App Service pages.

Client Application Azure Configuration

From the list of resources select the Client App’s App Service page and in the Settings section select Application settings. In the App settings section add values for IdentityServerAddress and ApiAddress.

Wrapping Up

At this point, you should be able to load up the application at the client address provided by Azure and have a working application. Overall the deployment to Azure was pretty easy. Getting the applications prepared to be deployed was a bit more challenging and sent me down a couple of rabbit holes. The code in its final state can be found here.

Identity Server: API Migration to ASP.NET Core 2

After writing the basic migration guide from ASP.NET Core 1.1.x to 2.0 I embarked on the task of upgrading the rest of the projects I have on GitHub. For the most part, it has been a pretty smooth transition. This post is going cover the differences that I hit while converting an API that is part of my IdentityServer sample project. This assumes that you have already followed my other migration post which can be found here.

Package Changes

The source of this conversion being different is that the IdentityServer4.AccessTokenValidation NuGet package is not currently supported on ASP.NET Core 2. Token validation can be done using bits provided by the framework. This is the recommended path suggested by the IdentityServer team as posted on this issue. Longer term you may want to switch back if you have a need for more features not provided by the Microsoft implementation as pointed out in this issue.

As for the actual change, just remove the reference to IdentityServer4.AccessTokenValidation from your project using the NuGet UI, Package Manager Console, or by editing the csproj file.

Startup

All the rest of the changes are in the Startup class. First, in the Configure function app.UseIdentityServerAuthentication gets replaced with app.UseAuthentication.

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

After:
app.UseAuthentication();

In the ConfigureServices function is now where JWT Bearer options are set up. First, we have to add the type of authentication the API is going to use and then the options for JWT Bearer are set, which will match the settings that were being used before with the IdentityServer package.

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = 
                               JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = 
                               JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
    o.Authority = "http://localhost:5000";
    o.Audience = "apiApp";
    o.RequireHttpsMetadata = false;
});

Wrapping up

With the above, your API can run on ASP.NET Core 2 and still verify authorization using IdentityServer4. My IdentityServer sample project is taking the longest to update so I would expect at least one or two more posts on the process as each of the projects gets upgraded.

Identity Server: External Authentication using Twitter

This post is going to cover adding authentication using Twitter to the same project that has been used in all of my IdentityServer examples. The same basic idea would apply to almost any third party authentication setup so this should give you a good starting point for any integration. The starting point of the code can be found here.

Create Twitter App

Before any code changes create a new application on Twitter via this page. Click Create New App to begin the process.

On the Create an application page enter all the requested information. Note that the website won’t allow a localhost address. If you don’t have a real address for your application just enter a random URL as I did here. When finished click Create your Twitter application.

Now that we have an application click on the Keys and Access Tokens tab. We will need both the Consumer Key and Consumer Secret when we get to the Identity Application.

Identity Application Changes

Now that we have a Twitter application ready to go let us dive into the changes needed to the Identity Application. The first step is to add a reference to Microsoft.AspNetCore.Authentication.Twitter via NuGet.

Next in the ConfigureServices function of the Startup class after app.UseIdentityServer() add the following.

app.UseTwitterAuthentication(new TwitterOptions
{
    AuthenticationScheme = "Twitter",
    DisplayName = "Twitter",
    SignInScheme = "Identity.External",
    ConsumerKey = Configuration["Authentication:Twitter:ConsumerKey"],
    ConsumerSecret = Configuration["Authentication:Twitter:ConsumerSecret"]
});

The first three options should a straight forward enough. The next two are the values from the Twitter application I mentioned above. In this example, I am storing the values using User Secrets which get pulled out of configuration. For more details on how to set up secrets, you can see this post.

The above are all the changes required. The Identity Application will now allow users to auth using Twitter.

Logging in using Twitter

As you can see below the login page now has a button for Twitter.

When the user chooses to log in using Twitter they are shown the following page where they must approve access to their Twitter account from your application.

If this is the first time a user has logged in with Twitter they will be prompted to enter an email address to finish registration.

Wrapping up

As you can see adding external authentication is super simple. Check out the Microsoft Docs on Twitter Auth (ASP.NET Core 2.0 so look out for differences if you are not on the preview bits) and IdentityServer Docs on External Auth for more information.

The finished code can be found here.

 

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