2017

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: Deploy to Azure Read More »

Pass ASP.NET Core Appsettings Values to Angular

As part of getting my set of Identity Server 4 sample applications to run in Azure, I needed a way in the Client Application to pass some configuration values from appsettings.json to the Angular front end that could be used both during server-side rendering and client-side rendering. This application is using JavaScriptServices. This solution may need tweaking if your application isn’t using JavaScriptServices. The code for the client application can be found here.

Settings

In this example, we need to pass the address of our Identity Server and API from appsettings.json to Angular. The following is the settings file for this example.

{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "IdentityServerAddress": "http://localhost:5000",
  "ApiAddress": "http://localhost:5001/"
}

Providing Configuration Data to Angular

In this application, Angular is loaded from the index action of the home controller. This view can be found in the Views/Home folder in the Index.cshtml file. The following is the file before any changes.

@{
    ViewData["Title"] = "Home Page";
}

<app asp-prerender-module="ClientApp/dist/main-server">Loading...</app>

<script src="~/dist/vendor.js" asp-append-version="true"></script>
@section scripts {
    <script src="~/dist/main-client.js" asp-append-version="true"></script>
}

The first change needed is to inject the configuration data using ASP.NET Core’s DI system. Add the following two lines at the top of the file.

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration

Now the configuration data from the application is available to this view. Next, we need to pull a couple of values out of the configuration data and pass it to the Angular application. To do this we are going to use the asp-prerender-data tag helper. You can read more about it in the official docs. The idea is you construct an object which is then serialized and stored in params.data. In our example, we are passing the URLs for the Identity and API Applications.

<app asp-prerender-module="ClientApp/dist/main-server"
     asp-prerender-data='new {
    apiUrl = Configuration["ApiAddress"],
    identityUrl = Configuration["IdentityServerAddress"]
}'>Loading...</app>

The above is creating a new object with an apiUrl property and an identityUrl property. The following is the full completed view for reference.

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
    ViewData["Title"] = "Home Page";
}

<app asp-prerender-module="ClientApp/dist/main-server"
     asp-prerender-data='new {
    apiUrl = Configuration["ApiAddress"],
    identityUrl = Configuration["IdentityServerAddress"]
}'>Loading...</app>

<script src="~/dist/vendor.js" asp-append-version="true"></script>
@section scripts {
    <script src="~/dist/main-client.js" asp-append-version="true"></script>
}

Angular Server-Side Boot

When Angular gets prerendered on the server-side it runs the code in the boot.server.ts file. This is where we will set up the providers needed on for the server side prerender. This is the bit that I missed for the longest time when trying to get this example going. I kept trying to find a way to add the providers in the app.module.server.ts file. Add any providers you need to the providers constant. For example, the following is passing URLs for an API and Identity Server in addition to the defaults provided by JavaScriptServices.

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: 'API_URL', useValue: params.data.apiUrl },
    { provide: 'IDENTITY_URL', useValue: params.data.identityUrl }
];

Lower in the same file we can pass through the configuration values to the client side render as globals on the window object. To do this add a globals property to the object being passed to the resolve call.

return new Promise<RenderResult>((resolve, reject) => {
    zone.onError.subscribe((errorInfo: any) => reject(errorInfo));
    appRef.isStable.first(isStable => isStable).subscribe(() => {
        // Because 'onStable' fires before 'onError', we have to delay slightly before
        // completing the request in case there's an error to report
        setImmediate(() => {
            resolve({
                html: state.renderToString(),
                globals: {url_Config: params.data}
            });
            moduleRef.destroy();
        });
    });
});

The above will have the URLs as part of a single object, but you could have each URL as its own property if you prefer.

Angular Client-Side

Now that the server-side has providers for API URL and Identity URL we need to provide the client-side with the same capabilities. These changes will be in the app.module.browser.ts file. The first step is to add providers for each.

providers: [
    { provide: 'ORIGIN_URL', useFactory: getBaseUrl },
    { provide: 'API_URL', useFactory: apiUrlFactory },
    { provide: 'IDENTITY_URL', useFactory: identityUrlFactory },
    AppModuleShared
]

Next, we need functions to return the URLs from the url_Config property of the window object which the following two functions do.

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

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

Wrapping Up

With the above, you can now use your configuration values from ASP.NET Core and pass them through to your Angular application. In hindsight, the process is pretty simple, but getting to that point took me much longer to figure out than I would like to admit. I hope this post saves you some time!

Pass ASP.NET Core Appsettings Values to Angular Read More »

Swagger and Swashbuckle: Disable Try It Out

In last week’s post, I walked through adding Swagger support to an ASP.NET Core 2 API using the Swashbuckle. One of the awesome things about Swashbuckle is it provides an integration with swagger-ui.

Try it out!

One of the features of the UI integration is the ability to invoke an end point using the “Try it out!” button. This feature is awesome during development but may not be something you want to allow, depending on the use case, for a production end point.

Disable Try it out!

I tried googling lots of different things to find the option to disable the “Try it out” button and had a really hard time finding an answer. It didn’t help that I want the button text to be “Try it now” for some reason. Thankfully it truly was a failure on my part and there is a provided way to disable “Try it out” and it is much more flex able than what I was initially looking for.

In the Configure function of the Startup class find the call to app.UseSwaggerUI. Adding c.SupportedSubmitMethods(new string[] {}); will completely disable “Try it out”. The following is the full call to app.UseSwaggerUI just to provide context.

app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Contacts API V1");
    c.SupportedSubmitMethods(new string[] {});
});

The great thing about the way this is set up if you can allow some actions and not others. For example, say you wanted to allow get actions but disable the rest. The following would allow for that.

c.SupportedSubmitMethods(new [] {"get"});

One word of caution the above is case sensitive and if you use Get instead of get “Try it out” will remain disabled.

Swagger and Swashbuckle: Disable Try It Out Read More »

Swagger and Swashbuckle with ASP.NET Core 2

This post is going to be very similar to a post from last December which can be found here. A lot has changed since then and this post is going to add Swagger to an existing ASP.NET Core application using Swashbuckle much like the one from last year. The starting point of the code can be found here.

What is Swagger?

Swagger is a specification used to document an API. As I am sure we all know API documentation tends to get out of date fast and a lot of times is a low priority.  Swagger aims to help solve that problem using a format that is both human and machine readable which can be maintained in either JSON or YAML. The documentation can be auto generated using a tool like Swashbuckle which provides away to keep your consumers up to date. Check out this post by the Swagger team for the full introduction.

What is Swashbuckle?

Swashbuckle provides auto generation of Swagger 2.0, a UI, etc. The project takes all the pain out of getting going with Swagger as well as providing tools and hooks for using and customizing Swagger related items. The full description can be found here.

Adding Swashbuckle

Using your favorite method of NuGet interaction, add the Swashbuckle.AspNetCore NuGet package to your project. Personally, I have gotten where I edit the csproj file to add new packages. If that is your style you would need to add the following package reference.

<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" />

This one package provides all the functionality we will need.

Wiring up Swashbuckle

Now that the Swashbuckle package is installed, there are a few changes that are needed in the Startup class to get everything wired up. First, in the ConfigureServices function, the Swagger generator needs to be added to DI.

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new Info { Title = "Contacts API", Version = "v1"});
});

AddSwaggerGen allows for configuration of options, but here we are just setting a name and a minimal amount of information.

In the Configure function Swagger needs to be added to the request pipeline in order to expose the Swagger generated data. I placed this after UseMvc.

app.UseSwagger();

At this point, the Swagger generated JSON would be available at {yourBaseUrl}/swagger/v1/swagger.json. To take a step further let’s expose the UI that comes with Swashbuckle. Add the following just below app.UseSwagger().

app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Contacts API V1");
});

Now a UI based on your API is available at {yourBaseUrl}/swagger with zero extra work on your part. The following is the UI for the post contact route in the example project.

As you can see the UI provides a great view of your API as well as ways to try it out and the potential responses that should be expected from a call.

Controller Considerations

All of this wonderful functionality doesn’t come for free of course. In order for Swashbuckle to pick up your routes, your controller will need to use attribute based routing instead of depending on convention based routing.

In order for Swashbuckle to know the return types and of your controller actions may need to have some attributes added. This won’t be required if your action return a specific type, but if you are returning an IActionResult you should attribute your action with all the ProducesResponseType you need to cover the results of your action. For example, the following is the action definition for the Post in the screen shot above.

[HttpPost]
[ProducesResponseType(typeof(Contact), 200)]
[ProducesResponseType(typeof(IDictionary<string, string>), 400)]
[ProducesResponseType(typeof(void), 400)]
[ProducesResponseType(typeof(void), 404)]
[ProducesResponseType(typeof(void), 409)]
public async Task<IActionResult> PostContact([FromBody] Contact contact)

Wrapping up

Swashbuckle makes it easy to add Swagger to a project. I feel that it also provides a huge value for anyone trying to consume an API. It is of course not a magic bullet and communication with your API consumers about API changes will still be critical.

Microsoft’s docs have a great walk through which can be found here. It does more in-depth on customizing your setup and as far as modifying the look of the UI. I also recommend checking out the GitHub page for the project which can be found here.

The finished code can be found here.

Swagger and Swashbuckle with ASP.NET Core 2 Read More »

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: API Migration to ASP.NET Core 2 Read More »

All Migrations are not Created Equal

While trying to deploy my sample Identity Server set of applications to Azure I got the following error when the Entity Framework migrations attempted to run.

System.Data.SqlClient.SqlException (0x80131904): Column 'Id' in table 'AspNetRoles' is of a type that is invalid for use as a key column in an index

This was not something I would get when attempting to run locally, but it failed every time when using SQL Azure. Long store short is that the migrations that were trying to be applied were created when I was using Sqlite as a backing store (UseSqlite).

I deleted all the migrations and recreated them with the app being aware that it would be running on SQL Server (UseSqlServer) and all worked as it should. It makes total sense that the migrations would vary based on the data provider being used, but not something I had really thought about. Not something I will forget again.

All Migrations are not Created Equal Read More »

Create a .NET Standard Library with Visual Studio 2017

It has been almost a year since I wrote this post on creating a .NET Standard Library which was before Visual Studio 2017 was released. This post is going to cover the same basic idea, but for the new tooling which has vastly simplified the process.

.NET Standard Library

The .NET Standard Library specifies what .NET APIs are available based on the version of the .NET Standard Library being implemented. The following is a comparison to portable class libraries that really helped me understand the difference. This was pulled from the .NET Standard Library link above.

.NET Standard Library can be thought of as the next generation of Portable Class Libraries (PCL). The .NET Standard Library improves on the experience of creating portable libraries by curating a standard BCL and establishing greater uniformity across .NET runtimes as a result. A library that targets the .NET Standard Library is a PCL or a “.NET Standard-based PCL”. Existing PCLs are “profile-based PCLs”.

The .NET Standard Library and PCL profiles were created for similar purposes but also differ in key ways.

Similarities:

  • Defines APIs that can be used for binary code sharing.

Differences:

  • The .NET Standard Library is a curated set of APIs, while PCL profiles are defined by intersections of existing platforms.
  • The .NET Standard Library linearly versions, while PCL profiles do not.
  • PCL profiles represents Microsoft platforms while the .NET Standard Library is agnostic to platform.

Create a .NET Standard Library

In Visual Studio click File > New > Project.

This will launch the New Project dialog. Find the .NET Standard templates and select Class Library.

Now that the library has been created right click on the project and go to properties. On the Application tab, there is an option to for Target framework which currently defaults to .NETStandard 1.4. Depending on your platform your library needs to support will decide which version of the .NET Standard you need to use.

See the chart on this page for help with picking a version of the standard. The lower the version of the standard you target the more platforms your library will run on, but keep in mind the lower the version the smaller the API surface that is available.

Wrapping up

This process is so much easier than it was in Visual Studio 2015. The tooling around .NET Core and .NET Standard has gotten so much better. This type of library is the future and I highly recommend using .NET Standard when you have the option over portable class libraries.

Create a .NET Standard Library with Visual Studio 2017 Read More »

Unable to create an object of type ‘ApplicationDbContext’. Add an implementation of ‘IDesignTimeDbContextFactory

Forgive the long title, but this is an issue I have been fighting trying to upgrade an Identity Server 4 project to ASP.NET Core 2. There is an issue on GitHub dedicated to this exact error which can be found here. Before you go down the route of trying all the suggestions in the issue take a moment and make sure that nothing in the Startup class is doing anything that would try to hit the database with Entity Framework.

There is a nice section in the official migration docs titled “Move database initialization code” which I seemed to have missed. So before you head down any rabbit holes like I did make sure this isn’t what is causing your need to add an implementation of IdesignTimeDbContextFactory.

As stated in the migration docs move database related code out of the Configure function of the Startup class and into the Main function. The following is the example of this from the docs.

var host = BuildWebHost(args);

using (var scope = host.Services.CreateScope())
{
    var services = scope.ServiceProvider;

    try
    {
        // Requires using RazorPagesMovie.Models;
        SeedData.Initialize(services);
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred seeding the DB.");
    }
}

host.Run();

This will keep Entity Framework tooling from accidentally running code you didn’t expect.  With version 2 all the code in the Configure function gets run.

Unable to create an object of type ‘ApplicationDbContext’. Add an implementation of ‘IDesignTimeDbContextFactory‘ Read More »

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: External Authentication using Twitter Read More »

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 »