Identity Server: Using ASP.NET Core Identity

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

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

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

Identity Application

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

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

Create a new Identity Application

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

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

On the next dialog select the Web Application template.

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

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

Adding Identity Server to the Identity App Project

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

Startup Changes

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

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

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

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

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

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

dotnet ef database update

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

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

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

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

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

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

Client Application

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

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

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

    return View();
}

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

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

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

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

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

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

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

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

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

Replace:
@ViewData["apiResult"]

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

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

Wrapping up

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

7 thoughts on “Identity Server: Using ASP.NET Core Identity”

  1. “Now throw that all away” – Sadly this is so true, the official doc’s fall short at the end of that section to transition into the next chapter smoothly.

      1. I have been trying to figure out all the steps that are needed to integrate IS4’s Quickstart into a project. I’ve only succeeded once when I was trying their quickstart tutorials but it turned out very messy. I was wondering if you ever documented the steps and if you would be willing to share them?

        1. All the work I have done with IS4 is already documented in this set of blog posts and the associated GitHub repo. I would start with the repo and see if the Identity App project has what you are looking for.

  2. Hi Eric;
    Firstly thank you for your blog. This blog was written in July 2017. I’m reading it now on May 29, 2018 where I’m sure lots of things have changed on IS4 and docs.

    My question is, has the status of docs and IS4 changed since you integrated (and documented) ASP Core Identity into IS4?

    My next question is, when someone uses ASP Identity, they use a series of Identity classes and methods for dealing with getting users or saving or getting roles and dealing with stores or doing authentication and so forth.
    But when we integrate ASP Identity into IS4, do we still use all those classes and methods for storage routine? It’s unclear in IS4 docs what we use from IS4 and what we use ASP Identity when merged.

    Third question is, ASP Identity gives us lots of help controllers and views for registrations, Forgot password and etc. Do we use all those, except the login and logout that is provided by IS4? So the user will be doing the registration or forgot password right on IS4 UI and not on our MVC application like we used?

    Thank you in advance.
    ..Ben

    1. I haven’t checked on the IS4 docs in a while. My guess is that if anything they have improved.

      From my understanding IS4 takes over a lot of the things you are asking about that is provided by ASP.NET’s Identity. In fact, you can use ASP.NET’s Identity in your IS4 project.

      It has been a while since I have worked with IS4 and to get a more solid answer I recommend reaching out to someone on the IS4 team or the community via something like StackOverflow. I am happy to help where I can!

  3. Pingback: ASP.NET Core 2.1 at CMAP August 2018 | Wake Up And Code!

Leave a Comment

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

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