A pleasant walk through computing

Comment for me? Send an email. I might even update the post!

IdentityServer3 with PKCE Part 4 - Persisting User Data

This series simulates a native application accessing a protected Web API resource, using OAuth2 via IdentityServer3. It demonstrates using Proof Key for Code Exchange (PKCE), and is in four parts:

  1. Build a simple authorization server, consumed by native application.
  2. Build a protected resource.
  3. Persist server configuration to database.
  4. Persist user data to database using Microsoft.Identity and SQL Server.

Important
This series does not create an OpenID Connect (OIDC) server. It is OAuth-only, since the PKCE specification doesn't require OIDC.

Where We're At

We're persisting IdentityServer configuration data, but still using in-memory users. IdentityServer can be extended to validate against an identity store, and supports Microsoft's AspNet.Identity via Entity Framework on SQL Server.

Important IdentityServer is not intended or used to add, edit or delete users. They make that clear. The server's job is only to verify identity. In this sample, there's a klunky method for populating the user database, and there are no API methods for registering a user. The API could be added using controller code similar to what's in the default ASP.NET Web API templates.

AspNetIdentity

References
IdentityServer3.AspNetIdentity Sample
Identity Server 3 using ASP.NET Identity
OAuth2 Walkthrough using Identity Server and ASP.NET

Install these packages.

Install-Package IdentityServer3.AspNetIdentity -Project AuthAndApi
Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.2.1 -Project AuthAndApi

Add a new connection string to web.config, for the AspNet Identity database.

    <add name="IdUsersDb" connectionString="Server=(localDb)\MSSQLLocalDb;AttachDbFilename=|DataDirectory|IdUsers.mdb;Database=IdUsers-ncacpkce;Integrated Security=true;" providerName="System.Data.SqlClient" />

Update Factory.cs

//Add these usings
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity;
using IdentityServer3.AspNetIdentity;
using IdentityServer3.Core.Services;
using IdentityServer3.Core.Services.InMemory;

Change the Configure method, adding code to persist the users, and a new method to populate the database.

        public static IdentityServerServiceFactory Configure(string optionsConnectionString, string usersConnectionString)
        {
            //Configure persisting IdentityServer operational services
            //The database will be created if needed. The default schema is being used.
            var efConfig = new EntityFrameworkServiceOptions
            {
                ConnectionString = optionsConnectionString
            };

            // these two calls just pre-populate the test DB from the in-memory config.
            // clf note: there's probably a better way to do this for a production app.
            ConfigureClients(Clients.Get(), efConfig);
            ConfigureScopes(Scopes.Get(), efConfig);

            var factory = new IdentityServerServiceFactory();
            factory.RegisterConfigurationServices(efConfig);
            factory.RegisterOperationalServices(efConfig);
            factory.ConfigureClientStoreCache();
            factory.ConfigureScopeStoreCache();
            //persist users
            //populate the database. This creates the database, if needed.
            ConfigureUsers(Users.Get(), usersConnectionString);
            //configure IdentityServer to use the database
            factory.UserService = new Registration<IUserService>(UserServiceFactory.Create(usersConnectionString));

            //configure cleanup for expired data
            var cleanup = new TokenCleanup(efConfig); //by default every 60 seconds

            return factory;
        }

        public static void ConfigureUsers(IEnumerable<InMemoryUser> users, string connectionString)
        {
            //create the Identity UserManager
            var context = new IdentityDbContext(connectionString);
            var userStore = new UserStore<IdentityUser>(context);
            var userManager = new UserManager<IdentityUser>(userStore);
            foreach (var user in users)
            {
                //no error checking!
                //only add if doesn't exist
                var test = userManager.FindByName(user.Username);
                if (test != null) { continue; }
                userManager.Create(new IdentityUser(user.Username), user.Password);
                test = userManager.FindByName(user.Username);
                foreach (var claim in user.Claims)
                {
                    if (claim.Type.ToLower() == "email") { test.Email = claim.Value; }
                    userManager.AddClaim(test.Id, claim);
                }
                userManager.Update(test);
            }
        }

And finally, add a new factory class, which is used in the line factory.UserServce =.

   public static class UserServiceFactory
    {
        public static AspNetIdentityUserService<IdentityUser, string> Create(string connectionString)
        {
            var context = new IdentityDbContext(connectionString);
            var userStore = new UserStore<IdentityUser>(context);
            var userManager = new UserManager<IdentityUser>(userStore);
            return new AspNetIdentityUserService<IdentityUser, string>(userManager);
        }
    }

That's it! The Startup class doesn't change. Running the solution creates the new database, and the authentication works as before.

2017-05-06_165055

Wrap Up

In this series, I demonstrated authorizing a mobile application in a secure way. This was necessarily a simple sample. Some natural next steps and questions to answer would be:

  1. How to customize the IdentityServer login and authorization screens
  2. Configure for OpenID Connect3
  3. Show using Google authentication

IdentityServer3 with PKCE Part 3 - Persist IdentityServer Configuration

This series simulates a native application accessing a protected Web API resource, using OAuth2 via IdentityServer3. It demonstrates using Proof Key for Code Exchange (PKCE), and is in four parts:

  1. Build a simple authorization server, consumed by native application.
  2. Build a protected resource.
  3. Persist server configuration to database.
  4. Persist user data to database using Microsoft.Identity and SQL Server.

Important
This series does not create an OpenID Connect (OIDC) server. It is OAuth-only, since the PKCE specification doesn't require OIDC.

Where We're At

Until now, we've been using in-memory configuration data (clients and scopes). But we'd like a way to persist the configuration to a database.

IdentityServer and Entity Framework

References

Entity Framework support for Clients, Scopes, and Operational Data
Operational Data
Clients and Scopes
Caching results for client, scope, and user stores

Add these packages.

Install-Package IdentityServer3.EntityFramework -Project AuthAndApi

Add the App_Data folder. This is required if you want to use the connection string below.

Add a connection string for the configuration database. The sample uses SQL localDb 2016.

<connectionStrings>
    <add name="IdConfigDb" 
         connectionString="Server=(localDb)\MSSQLLocalDb;AttachDbFilename=|DataDirectory|IdConfig.mdb;Database=IdConfig;Integrated Security=true;" 
         providerName="System.Data.SqlClient" />
  </connectionStrings>

Add an IdentityServerConfiguration folder, and a Factory.cs class. The factory class takes care of setting the configuration database source, and also initially populating the database using our in-memory client and scope collections.

Note
This sample doesn't provide a way to edit configuration data. There is a NuGet package that adds web-based configuration administration to a project. I haven't tried it, but here's the link. IdentityServer3.Admin

Factory.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
//Added
using IdentityServer3.Core.Configuration;
using IdentityServer3.EntityFramework;
using IdentityServer3.Core.Models;
using AuthAndApi.IdOptions;
using AuthAndApi.IdUsers;

namespace AuthAndApi.IdentityServerConfiguration
{
    public class Factory
    {
        public static IdentityServerServiceFactory Configure(string optionsConnectionString)
        {
            //Configure persisting IdentityServer operational services
            //The database will be created if needed. The default schema is being used.
            var efConfig = new EntityFrameworkServiceOptions
            {
                ConnectionString = optionsConnectionString
            };

            // these two calls just pre-populate the test DB from the in-memory config.
            // clf note: there's probably a better way to do this for a production app.
            ConfigureClients(Clients.Get(), efConfig);
            ConfigureScopes(Scopes.Get(), efConfig);

            var factory = new IdentityServerServiceFactory();
            factory.RegisterConfigurationServices(efConfig);
            factory.RegisterOperationalServices(efConfig);
            factory.ConfigureClientStoreCache();
            factory.ConfigureScopeStoreCache();
            //Still using in-memory users for now
            factory.UseInMemoryUsers(Users.Get());

            return factory;
        }

        public static void ConfigureClients(IEnumerable<Client> clients, EntityFrameworkServiceOptions options)
        {
            using (var db = new ClientConfigurationDbContext(options.ConnectionString, options.Schema))
            {
                if (!db.Clients.Any())
                {
                    foreach (var c in clients)
                    {
                        var e = c.ToEntity();
                        db.Clients.Add(e);
                    }
                    db.SaveChanges();
                }
            }
        }

        public static void ConfigureScopes(IEnumerable<Scope> scopes, EntityFrameworkServiceOptions options)
        {
            using (var db = new ScopeConfigurationDbContext(options.ConnectionString, options.Schema))
            {
                if (!db.Scopes.Any())
                {
                    foreach (var s in scopes)
                    {
                        var e = s.ToEntity();
                        db.Scopes.Add(e);
                    }
                    db.SaveChanges();
                }
            }
        }
    }
}

Update Startup.cs, replacing the previous IdentityServer configuration that explicitly configured the Factory with in-memory data, with the new call to Factory.Configure.

            //Configure IdentityServer for issuing authentication tokens
            var options = new IdentityServerOptions
            {
                Factory = Factory.Configure("IdConfigDb"),
                //SSL MUST be used in production
#if DEBUG
                RequireSsl = false
#endif
            };
            app.UseIdentityServer(options);

ASIDE!

Running the app at this point, I got an error in Startup.cs that I was missing a dependency.

 

2017-05-06_134432

When I installed the package IdentityServer3.EntityFramework, Newtonsoft.Json version 8 was uninstalled, because the package requires version 9 or greater. The package installer output confirms this.

Successfully uninstalled 'Newtonsoft.Json.8.0.3' from AuthAndApi
Successfully installed 'Newtonsoft.Json 9.0.1' to AuthAndApi

No package requires version 8 or lower, so what gives? For some reason, the solution's web.config assembly binding section wasn't updated properly. It still had this:

     <dependentAssembly>
       <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
       <bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
     </dependentAssembly>

You could manually change the max version number to 9.0.0.0, reinstall the Newtonsoft package which will do it for you.

Update-Package -Reinstall Newtonsoft.Json -Project AuthAndApi

When I run the application, it behaves exactly as before. Checking in the App_Data folder, I see my database was created.

2017-05-06_140602

One difference, though. After the first time running the app, I no longer was prompted to authorize the scope. This might be related to these factory settings:

factory.ConfigureClientStoreCache();
factory.ConfigureScopeStoreCache();

Wrap Up

Our server's configuration is now being persisted to a database. The only thing (for this sample!) left is to persist the user's credentials and claims.

IdentityServer3 with PKCE Part 2 - Protected Resource Server

This series simulates a native application accessing a protected Web API resource, using OAuth2 via IdentityServer3. It demonstrates using Proof Key for Code Exchange (PKCE), and is in four parts:

  1. Build a simple authorization server, consumed by native application.
  2. Build a protected resource.
  3. Persist server configuration to database.
  4. Persist user data to database using Microsoft.Identity and SQL Server.

Important
This series does not create an OpenID Connect (OIDC) server. It is OAuth-only, since the PKCE specification doesn't require OIDC.

Where We're At

We can get a token, but there's nothing to use it on. We've configured IdentityServer with information about our client application (PretendMobile), and we used that information (Client ID, etc) to get a token.

Now we'll add a Web API with a protected method that just returns some claim data. It will be configured to:

  1. Accept the access token our pretend mobile app gets from the authorization server.
  2. Validate that token against the authorization server.

We'll use the same project as the authorization server, but it's important to know that the Web API could be in a completely separate project.

Web API

References
Simplest OAuth Server
Simplest OAuth2 Walkthrough Code

Install the packages for Web API, and being able to accept the IdentityServer tokens. Note IdentityModel version, which is latest for .Net 4.6 (2.x is for .Net Core).

Install-Package Microsoft.AspNet.WebApi.Client -Version 5.2.3 -Project AuthAndApi
Install-Package Microsoft.AspNet.WebApi.Owin -Version 5.2.3 -Project AuthAndApi
Install-Package IdentityModel -Version 1.9.2 -Project AuthAndApi
Install-Package IdentityServer3.AccessTokenValidation -Version 2.15.0 -Project AuthAndApi

Add two things to the OWIN pipeline: accepting access tokens, and using WebApi.

Updated Startup.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
//Added
using IdentityServer3.Core.Configuration;
using Owin;
using AuthAndApi.IdOptions;
using AuthAndApi.IdUsers;
using System.Web.Http;
using IdentityServer3.AccessTokenValidation;

namespace AuthAndApi
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            //Configure IdentityServer for issuing authentication tokens
            var options = new IdentityServerOptions
            {
                Factory = new IdentityServerServiceFactory()
                .UseInMemoryClients(Clients.Get())
                .UseInMemoryScopes(Scopes.Get())
                .UseInMemoryUsers(Users.Get()),
                //SSL MUST be used in production
#if DEBUG
                RequireSsl = false
#endif
            };
            app.UseIdentityServer(options);

            // Configure Web API to accept access tokens from IdentityServer, and require a scope of 'email'
            app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
            {
                //In this case, the authorization server is also the Web API server
                Authority = "http://localhost:27230",
                ValidationMode = ValidationMode.ValidationEndpoint,
                RequiredScopes = new[] { "email" }
            });

            //Configure Web API
            var config = new HttpConfiguration();
            config.MapHttpAttributeRoutes();
            app.UseWebApi(config);
        }
    }
}

Add a Controllers folder, and a controller named Test.

2017-05-06_1140242017-05-06_1141442017-05-06_114210

Create an API method that requires authorization. The method checks for evidence a user authorized (and not just the application).

Note
Code shamelessly stolen from the IdentityServer samples

TestController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
//Added
using System.Security.Claims;

namespace AuthAndApi.Controllers
{
    [Route("test")]
    [Authorize]
    public class TestController : ApiController
    {
        public IHttpActionResult Get()
        {
            var user = User as ClaimsPrincipal;

            var subjectClaim = user.FindFirst("sub");
            if (subjectClaim != null)
            {
                return Json(new
                {
                    message = "OK user",
                    client = user.FindFirst("client_id").Value,
                    subject = subjectClaim.Value,
                    email = user.FindFirst("email").Value
                });
            }
            else
            {
                return Json(new
                {
                    message = "User claim not found for this client_id",
                    client = user.FindFirst("client_id").Value
                });
            }
        }
    }
}

The API server now has a protected resource. Let's prove it requires authorization. Run the application. The console will still get an access token, but we're not doing anything with it, yet. Leave the project running and manually enter this URL into a browser to try to access the protected resource.

http://localhost:27230/test

The server will return something like this:

<Error>
  <Message>Authorization has been denied for this request.</Message>
</Error>

Pretend Mobile App - Access Protected Resource

In the PretendMobile project, add and call a method that access the protected Web API resource. The token is added to the request in the Authorization header.

        static void Main(string[] args)
        {
            //get token
            var response = GetAccessToken();
            Console.WriteLine("Access Token: " + response.AccessToken);
            //use token
            CallProtectedApiResource(response.AccessToken);
            Console.ReadLine();
        }

        static void CallProtectedApiResource(string access_token)
        {
            var client = new HttpClient();
            client.SetBearerToken(access_token);
            Console.WriteLine(client.GetStringAsync("http://localhost:27230/test").Result);
        }

Run the solution. After authorizing, the application successfully gets the protected resource's data.

2017-05-06_121035

Wrap Up

We now have a fully functioning mobile application! Next up, persisting the IdentityServer configuration.