Login has always seemed like an after thought. As a solo developer, projects grow from shiny new, interesting features and technologies. Identity, authentication (logon) and authorization (roles) are tacked on later. Identity is boring.
Yet identity seems to be the critical foundation on which the rest of the application relies, and it is really, really difficult to get right (as evidenced by nearly daily reports of website hacking). I have never written any mission critical applications (e.g. medical devices) or those requiring tight security (e.g. patient privacy, banking). But I still need a reasonably secure login. In the 2000-2010, I used asp built in asp identity solutions and later asp.net MVC. They have instant, built-in support for identity databases in the framework, as well as external authentication (e.g. Google, Facebook, Microsoft). There are even anti-forgery tokens, sterilization of input data (to prevent sql injection attacks) and other security measures. Easy to use – click a checkbox or dropdown on the new MVC project, and it just works. Magic.
Server and client development in 2016 seems to be cluttered by utilities/services, that for a price, will replace programming challenges with magic boxes: key, challenging functionality wrapped in shiny packages with catchy names. I send data in and get data out. What goes on inside is mysterious. Libraries from Nuget or NPM do this as well (e.g. newtonsoft, lodash), but at least with libraries I understand the basics of what occurs inside.
In contrast, cloud computing (e.g. Azure, AWS) despite all its great conveniences including spinning-up 1 or 50 servers throughout the world, is in large part composed of magic boxes. For my needs, “owning” the whole server still makes sense. And the costs of adding magic boxes, cloud computing, OAuth, Firebase… could quickly add-up.
So, in the last month I delved into identity server 4 (built on asp.net core). In the recommended configuration, Identity Server is given its very own asp.net core project. It can use MVC controller/views to login in a user and access their roles/claims. Identity server defines clients with scopes (e.g. openId, Profile, “api”) as well as users.
What is incredible is that the primary developers, Dominick Baier and Brock Allen have set-up an entire ecosystem for identity, along with great tutorials, documentation and many samples and starter projects. I’ve worked through the tutorials and have used the Quickstarts starter project #6 – which in turn uses asp.net identity database and it works well. Using external providers (e.g. Google, Microsoft, Facebook) also works, but I am still trying to reconcile username/password standard logins with multiple external providers. In effect, how to keep a consistent set of claims for the same user who authenticates today with a username, tomorrow via Google, and the next day via Microsoft. Kevin Dockx covers some approaches to this in his Pluralsight course.
Testing in postman is fairly straight forward using this workflow. You post to the [identityserver_URL]/connect/token with body data (x-www-form-urlencoded) with client_id, client_secret, grant_type, scope, username and password. These are all defined in identityServer config except for username and password. However the grant_type text is tricky. With trial and error, I found “password” for resourceOwnerFlow and “client_credentials” for clientCredentialsFlow; Then I found that valid grant types are listed at “//localhost:5000/.well-known/openid-configuration”. Once you post to the token service, an access token is returned and that can be copied into the api call header to allow authorization. The access token parts can be visualized by pasting it at the jwt.io site.
Everything worked well until I attempted deployment. Then I became stuck on an error – which started as a 500 – internal server error. Once I added logging to file (NLog Extensions – look for the resulting log files at c:\temp\nlog…), I found IDX10803: Unable to obtain configuration; it could not open the /.well-known/openid-configuration. This worked on the dev machine with localhost:5000, but on the server, I needed to use a full URL. This was easy enough to configure using appSettings.production.json, but it was not finding the file. After spending hours, it turned out that I was trying to use //myUrl/identityServer and it would not work – the URL was not being found. Instead, I needed to use https or http//myUrl/IdentityServer.
One additional issue that also took me several hours to figure out. The IdentityServer example projects use “services.AddMvcCore”, not “services.AddMvc”. As I learned, AddMvcCore is a barebones subset of the framework. This worked fine until I started to add additional functionality, such as swashbuckler.swagger, a great Api helper utility, and while following examples, I could not get it to work. Finally, once I changed the configuration in startup to “services.AddMvc”, all worked.
There are multiple road blocks to understanding IdentityServer, OAuth2 and OpenID in Asp.Net. As of November 2016, Pluralsight has 2 courses. Both use Asp.net (not core) and Identity Server 3 (not 4), but the overall concepts, flows/grants, clients, scopes, users – are the same as for the newer versions. I started by watching the “OpenId and Oauth2 Strategies for Angular and Asp.Net” – and this is a very thorough and in depth but quite overwhelming; rather than an introduction, it is more of a “everything you ever wanted to know about IdentityServer”. I then watched “Using Oauth to Secure Your Asp.Net Api” which was more geared at an introductory level and easier for me to get my head around. In retrospect, I would watch this first before the other. He does however recommend using ResourceOwnerPassword for Xamarin Mobile apps authenticating with identity server and this may be insecure due to transfer of client username and password; I think authentication flow may be better for this. ResourceFlow appears to be ok for api since the API is on the same server as the Identity Server.
It took me a while to understand that access-tokens are just for access. They do not include user claims (e.g. username, email…). To get user claims, you need an id-token or you can tack on claims to the access token, but this requires the ResourceOwner flow. In addition, understanding how roles fit into the new claims world was not intuitive, but is explained here.
It took me days to figure out how to use Asp.net core identity (and EF/SQL database) with Identity Server. Most of this is well layed out here. But what took me the longest time, was in getting the username (user email) and role (e.g. admin) to be passed as claims to the client Api. I created an Identity Server client with AllowedGrantType = “ResourceOwnerPasswordAndClientCredentials” flow to do this. Then create a custom scope that uses the following Claims (as described here):
Claims = new List<ScopeClaim>
As an alternative, you can add “IncludeAllClaimsForUser = true” to the custom scope, but it adds additional claims I do not need.
In the Api, I added the follow to Startup.cs Configure. This stops default Microsoft jwt mapping that tends to mess a few things up:
I also added the following lines in the Api to the IdentityServer setup section in Configure:
Authority = authority,
ScopeName = "myscope",
NameClaimType = System.Security.Claims.ClaimTypes.Name,
RoleClaimType = System.Security.Claims.ClaimTypes.Role
Now that the Api can identify the username, I can use that in database fields to record the author of entries. I created a separate UserRepository within the Api, that calls the asp.net identity database to obtain additional user info and can be joined to the other tables. This is a bit clunky however. When the api held both a “notes” table and a “user table”, note rows had a author foreign key that pointed to a user in the user table. Therefore a query for notes could automatically include information about the user. However, with the user table in another database, there needs to be 2 queries and a join.
As an alternative, I could create a SqlServer View inside the notes database, from the table in the User database (using specific column names instead of the *):
CREATE VIEW [dbo].[vUserMirror]
AS SELECT * FROM [users].dbo.[aspnetusers];
The users view could then be linked via foreign key to the notes, and a notes query could once again include user info. The tough part with this is how to create this automatically with migrations. Another option is to create a data project that holds all the data files including identity. Then client projects could use the data project and each could extend ApplicationUserDbContext with their own database implementations. In this way different client projects could share the same central identity database. However, it would seem fragile as more tables were added for different client projects – the database would grow, migrations for one client project might break the data for another project. Still need to work with this. I have searched for examples online for how to solve this and have not found a solution.
The second problem with this is that now I have two DbContexts inside the project, and migrations are no long as straight forward. Add-migration inside of Package Manager Console will give a cryptic error: “Unrecognized option ‘–build-base-path'”. However, if the full non-powershell command is issued (“dotnet ef migrations add mymigration”), the error will now be “More than one DbContext was found. Specify which one to use. Use the ‘-Context’ parameter for PowerShell commands and the ‘–context’ parameter for dotnet commands.”. So, with a project containing more than one DbContexts, use:
dotnet ef migrations add myMigrattion -c myDbContext
dotnet ef database update -c myDbContext
I’ve been watching as Identity Server 4 rapidly nears a full 1.0 release (as of early November it is in RC3). I am amazed at the productivity and efficiency of Dominick Baier and Brock Allen, and the others working with them. It seems every time I access their github repo, they made their last changes, just hours before. Their documentation pages are stellar as are their samples. I wish their London SDD Conference Workshop allowed for online subscription viewing.