Image by clement127 | Some Rights Reserved
In the previous post in this series we learned how the most basic authentication and authorization elements fit together in an OWIN-based Web Api application. We have seen how to authenticate a user using an Authentication Server embedded within our application, and how to add an elementary claim to use with the [Authorize] attribute.
To this point, we have been avoiding using the ready-built Identity framework, and instead we have been focusing on understanding how these pieces interrelate. We will continue this approach (for now) here, by adding some concrete authorization models to our application, and a persistence layer to store important user data.
Once again, we will be doing most of this “from scratch,” in a pretty minimal fashion. I want to explore the relationships between project components without too many distractions. So we’re not attempting to design the optimal auth system here or demonstrate the latest best practices. But hopefully we will come away with a better understanding of how a fully developed authentication/authorization system such as Identity works in the context of our application. Understanding THAT gives empowers us to utilize tools like Identity more effectively.
- Adding Auth Models to the Minimal Web Api
- Adding The Models to the ApplicationDbContext
- Tying the Models Together – The User Store
- Initialize the Database with User Data
- Find the User and Authenticate the Token Request
- The Api Client Application
- Running the Application with an Authenticated User
- Improper Authentication – Invalid Credentials
- Insufficient Authorization
- Additional Resources and Items of Interest
From the Ground Up
In this series of posts we started with concepts, and are building slowly build from there.
- Part I (last post) – We will examine the basic OAuth Resource Owner Flow model for authentication, and assemble to most basic components we need to implement authentication using this model. We will not be concerning ourselves with the cryptographic requirements of properly hashing passwords, or persisting user information to a database. We will also not be using Identity, instead implementing security using the basic components available in the Microsoft.Owin libraries.
- Part II (this post) – We will mock up some basic classes needed to model our user data, and a persistence model to see how storage of user data and other elements works at a fundamental level.
- Part III – We will replace our mock objects with Identity 2.0 components to provide the crypto and security features (because rolling your own crypto is not a good idea).
Source Code for Examples
We are building up a project over a series of posts here. In order that the source for each post make sense, I am setting up branches that illustrate the concepts for each post:
On Github, the branches of the Web Api repo so far look like this:
- Branch: Master – Always the most current, includes all changes
- Branch: auth-minimal – We start here in this post. In the last post, we implemented an example of token-based authentication/authorization. We have not yet added persistence or and authorization models, just seen how things worked. To review what we did, see ASP.NET Web Api: Understanding OWIN/Katana Authentication/Authorization Part I: Concepts.
- Branch: auth-db – The code we build up in the course of this post.
The code for the API client application is in a different repo, and the branches look like this:
- Branch: Master – Always the most current, includes all changes
- Branch: owin-auth – Added async methods, and token-based authentication calls to the Web Api application. This is where we left the code in the last post.
Adding Auth Models to the Minimal Web Api
We’ll be starting from where we left off in the last post. Recall that We had set up a basic embedded authorization server in our application which would process HTTP POST requests made by a client to the token endpoint, validate the user credentials/password received, and return an access token. From there, the client could submit the access token with subsequent requests to authenticate, and access whichever resources are available for the given identity and/or role.
If we review our existing code for the ApplicationOAuthServerProvider
, we see in the GrantOwnerResourceCredentials()
method that we are performing a mock credentials check. In order to keep things simple, we just checked to see if the password submitted matched the string literal “password” and moved on:
The Existing GrantOwnerResourceCredentials Method:
public override async Task GrantResourceOwnerCredentials( OAuthGrantResourceOwnerCredentialsContext context) { // DEMO ONLY: Pretend we are doing some sort of REAL checking here: if (context.Password != "password") { context.SetError( "invalid_grant", "The user name or password is incorrect."); context.Rejected(); return; } // Create or retrieve a ClaimsIdentity to represent the // Authenticated user: ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType); identity.AddClaim(new Claim("user_name", context.UserName)); identity.AddClaim(new Claim(ClaimTypes.Role, "Admin")); // Identity info will ultimatly be encoded into an Access Token // as a result of this call: context.Validated(identity); }
In reality, we would most likely check to see if there was a user in our backing store which matched whatever credentials were submitted, and then also check to see if the password submitted was valid. But not by checking against a plain text representation from our backing store!
In order to flesh out this method, we need to model our authorization objects, and we need to persist some user data in our database.
First, let’s add some basic models. Add a new code file to the Models folder in the project, and then add the following code:
The AuthModels.cs Code File:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; // Add usings: using System.Data.Entity; using System.ComponentModel.DataAnnotations; using System.Security.Claims; namespace MinimalOwinWebApiSelfHost.Models { public class MyUser { public MyUser() { Id = Guid.NewGuid().ToString(); Claims = new List<MyUserClaim>(); } [Key] public string Id { get; set; } public string Email { get; set; } public string PasswordHash { get; set; } public ICollection<MyUserClaim> Claims { get; set; } } public class MyUserClaim { public MyUserClaim() { Id = Guid.NewGuid().ToString(); } [Key] public string Id { get; set; } public string UserId { get; set; } public string ClaimType { get; set; } public string ClaimValue { get; set; } } public class MyPasswordHasher { public string CreateHash(string password) { // FOR DEMO ONLY! Use a standard method or // crypto library to do this for real: char[] chars = password.ToArray(); char[] hash = chars.Reverse().ToArray(); return new string(hash); } } }
Above, we see a few basic models. We expect to have a user representation, and we do, in the form of the MyUser
class. While you may have been expecting to see a MyRole
class, we have instead opted to carry on with the claims implementation we were using in our original project. Therefore, we have added a MyUserClaim
class instead. We’ll discuss this further shortly.
Finally, we have that odd-looking MyPasswordHasher
class. As you may have guessed from the comment in the code, we are really only going to mock a proper hashing mechanism here. As before, we’re going to keep things simple for our example. In reality, one would apply a proven crypto library to this task, and proven, tried and true methods for properly hashing a password. Or, of course, use a library for such things, like Identity.
Adding The Models to the ApplicationDbContext
Now that we have our auth-related entity models, we can add them to the existing ApplicationDbContext
so that they can be modeled in the database, and we can access the data they represent from the context.
Recall that we set this particular example application up to use a local, file-based database (SQL CE) however, everything we are doing here would work just fine with SQL Server as well.
Add the Auth-Related Models to the ApplicationDbContext:
public class ApplicationDbContext : DbContext { public ApplicationDbContext() : base("MyDatabase") { } static ApplicationDbContext() { Database.SetInitializer(new ApplicationDbInitializer()); } public IDbSet<Company> Companies { get; set; } public IDbSet<MyUser> Users { get; set; } public IDbSet<MyUserClaim> Claims { get; set; } }
Tying the Models Together – The User Store
For our simple model set, and to keep concept straightforward, we are going to implement a simple MyUserStore
class, and add sufficient functionality to get our application working and no more.
Add the following class (I added this to the AuthModels.cs file, but you can add it in its own if you want):
The UserStore Class:
public class MyUserStore { ApplicationDbContext _db; public MyUserStore(ApplicationDbContext context) { _db = context; } public async Task AddUserAsync(MyUser user, string password) { if (await UserExists(user)) { throw new Exception( "A user with that Email address already exists"); } var hasher = new MyPasswordHasher(); user.PasswordHash = hasher.CreateHash(password).ToString(); _db.Users.Add(user); await _db.SaveChangesAsync(); } public async Task<MyUser> FindByEmailAsync(string email) { var user = _db.Users .Include(c => c.Claims) .FirstOrDefaultAsync(u => u.Email == email); return await _db.Users .FirstOrDefaultAsync(u => u.Email == email); } public async Task<MyUser> FindByIdAsync(string userId) { return await _db.Users .FirstOrDefaultAsync(u => u.Id == userId); } public async Task<bool> UserExists(MyUser user) { return await _db.Users .AnyAsync(u => u.Id == user.Id || u.Email == user.Email); } public async Task AddClaimAsync(string UserId, MyUserClaim claim) { var user = await FindByIdAsync(UserId); if(user == null) { throw new Exception("User does not exist"); } user.Claims.Add(claim); await _db.SaveChangesAsync(); } public bool PasswordIsValid(MyUser user, string password) { var hasher = new MyPasswordHasher(); var hash = hasher.CreateHash(password); return hash.Equals(user.PasswordHash); } }
In the code above, we have assembled a few basic methods to deal with persisting and retrieving User information. Note in the AddUserAsync()
method, we perform some minimal validation (make sure a user with the same email address does not already exist). Also, see that we use our super-secret, super-secure MyPasswordHasher
to hash, salt, re-hash, etc. our user password, and then we persist the hashed value (NEVER the clear-text password). In other words, at no point are we saving the user-submitted clear-text password to disk, anywhere.
Similarly, we provide a simple PasswordIsValid()
method which again uses the MyPasswordHasher
class to compare the hash of the password submitted with that of a user record (which for now, would be submitted as an argument after being previously retrieved elsewhere in our code).
The MyUserStore
class provides simplistic examples of how one might implement some of this. There is minimal validation and exception handling here. This class works well for our example, and to demonstrate the concepts we are dealing with, but is not likely how you would do this in a production application.
Initialize the Database with User Data
Now all we really need to do is update our ApplicationDbInitializer
to seed the database with some initial user data. Recall, we had already set this up (in the same code file as the ApplicationDbContext
) to seed our Company
table with some starting data. Update the code as follows. You will also need to add System.Security.Claims
to the using statements at the top of your code file:
Update ApplicationDbInitializer to Seed Application with Initial User Data:
public class ApplicationDbInitializer : DropCreateDatabaseAlways<ApplicationDbContext> { protected async override void Seed(ApplicationDbContext context) { context.Companies.Add(new Company { Name = "Microsoft" }); context.Companies.Add(new Company { Name = "Apple" }); context.Companies.Add(new Company { Name = "Google" }); context.SaveChanges(); // Set up two initial users with different role claims: var john = new MyUser { Email = "john@example.com" }; var jimi = new MyUser { Email = "jimi@Example.com" }; john.Claims.Add(new MyUserClaim { ClaimType = ClaimTypes.Name, UserId = john.Id, ClaimValue = john.Email }); john.Claims.Add(new MyUserClaim { ClaimType = ClaimTypes.Role, UserId = john.Id, ClaimValue = "Admin" }); jimi.Claims.Add(new MyUserClaim { ClaimType = ClaimTypes.Name, serId = jimi.Id, ClaimValue = jimi.Email }); jimi.Claims.Add(new MyUserClaim { ClaimType = ClaimTypes.Role, UserId = john.Id, ClaimValue = "User" }); var store = new MyUserStore(context); await store.AddUserAsync(john, "JohnsPassword"); await store.AddUserAsync(jimi, "JimisPassword"); } }
As we see above, we have taken advantage of the methods exposed on our new MyUserStore
class to add two users, along with appropriate claims, to the database.
Also recall we are deriving our initializer from DropDatabaseCreateAlways
so that the database will be re-created and re-seeded each time we run the application.
Find the User and Authenticate the Token Request
All that’s left to do now is update our GrantResourceOwnerCredentials()
method to avail itself of our new user entities and data to perform its function.
Validate and Authenticate a User in GrantResourceOwnerCredentials() Method:
public override async Task GrantResourceOwnerCredentials( OAuthGrantResourceOwnerCredentialsContext context) { // Retrieve user from database: var store = new MyUserStore(new ApplicationDbContext()); var user = await store.FindByEmailAsync(context.UserName); // Validate user/password: if(user == null || !store.PasswordIsValid(user, context.Password)) { context.SetError( "invalid_grant", "The user name or password is incorrect."); context.Rejected(); return; } var identity = new ClaimsIdentity(context.Options.AuthenticationType); foreach(var userClaim in user.Claims) { identity.AddClaim(new Claim(userClaim.ClaimType, userClaim.ClaimValue)); } context.Validated(identity); }
Here, we retrieve a user record from our store (if there is a record for the user credentials in the request), and then we create a new ClaimsIdentity
for that user, much the same as before. This time, however, we also have a record of the various claims for this user, and we add those as well.
In this case, we really only have the user’s name, and the role(s) our application recognizes for the user, but we could implement a more complex claims model if we needed. For now, we will stick with user name and roles, because the default authorization scheme, using the [Authorize]
attribute, is pre-configured to work with user names and roles. We will look at customizing this in a later post.
The Api Client Application
We can leave our Api Client application pretty much as-is at the moment. If you don’t have the client application set up, you can pull down the source for the project from the Github repo. Make sure to checkout the branch owin-auth (not master!).
Recall that we has set up our application to request a token from our Api, and then make some Api calls to the CompaniesController:
Abbreviated Client Code Showing the Token Request:
static async Task Run() { // Create an http client provider: string hostUriString = "http://localhost:8080"; var provider = new apiClientProvider(hostUriString); string _accessToken; Dictionary<string, string> _tokenDictionary; try { // Pass in the credentials and retrieve a token dictionary: _tokenDictionary = await provider.GetTokenDictionary( "john@example.com", "JohnsPassword"); _accessToken = _tokenDictionary["access_token"]; // Write the contents of the dictionary: foreach (var kvp in _tokenDictionary) { Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value); Console.WriteLine(""); } // Create a company client instance: var baseUri = new Uri(hostUriString); var companyClient = new CompanyClient(baseUri, _accessToken); // ... a bunch of code calling to API and writing to console... } catch (AggregateException ex) { // If it's an aggregate exception, an async error occurred: Console.WriteLine(ex.InnerExceptions[0].Message); Console.WriteLine("Press the Enter key to Exit..."); Console.ReadLine(); return; } catch (Exception ex) { // Something else happened: Console.WriteLine(ex.Message); Console.WriteLine("Press the Enter key to Exit..."); Console.ReadLine(); return; } }
The only thing we have changed in the above code is the password we are passing in with the token request – we have changed it to match the password for the user record we created in our Seed()
method.
Running the Application with an Authenticated User
If we run our Web Api application, and then run the client, everything should work swimmingly. The Web Api application spins up the same as it always has, and the client output should look familiar:
Console Output from Client Application:
Everything looks the same as it did when we wrapped up the previous post, because we haven’t changed anything the affects how the client application does its job. We’ve only changed the internals of our Web Api so that the embedded authorization server now knows how to retrieve user data from our database in order to authenticate a user, and perform a basic authorization check against the roles available to that user.
Let’s see what happens when things go wrong.
Improper Authentication – Invalid Credentials
First, let’s see what happens if we try to request a token with the wrong password. In the client application, change the password we are using in our token request to something other than “JohnsPassword”:
Using Incorrect Password for Client Token Request:
// Pass in the credentials and retrieve a token dictionary: _tokenDictionary = await provider.GetTokenDictionary( "john@example.com", "SomePassword"); _accessToken = _tokenDictionary["access_token"];
If we run the client again, we see all is not well:
Running the Client with Invalid Credentials:
In this case, were get back an “Invalid Grant” because the client could not properly authenticate with the credentials provided.
On the other hand, things look a little different is we request a token for a user which can be authenticated, but who is not authorized access to the resource requested.
Insufficient Authorization
Recall that in our Web Api application, we protected the CompaniesController
resource using the [Authorize]
attribute, and we restricted access to users in the role “Admin”:
The CompaniesController is Protected Using [Authorize]:
[Authorize(Roles="Admin")] public class CompaniesController : ApiController { // ... blah blah Controller Methods etc... }
Also recall that we seeded two users in our database. The user “jimi” does not have a claim for the “Admin” role, but instead claims the “User” role. Let’s change the code in our client application to request an access token for “jimi” instead, and then see what happens.
Change Client Token Request for Alternate User:
// Pass in the credentials and retrieve a token dictionary: _tokenDictionary = await provider.GetTokenDictionary( "jimi@example.com", "JimisPassword"); _accessToken = _tokenDictionary["access_token"];
Running the client application now produces a slightly different result:
Running the Client with Valid Credentials but Insufficient Authorization:
Unlike previously, we did not receive an invalid grant error, because the user credentials were properly authenticated. However, the user does not possess the proper Role claim in our system to access the protected resource.
In reality, the default implementation of [Authorize]
limits our ability to leverage claims to the fullest extent. [Authorize]
recognizes claims for user names, and roles. What if we want more granular control over our application permissions?
We’re not going to go into that in this post. However, keep this in mind, as leveraging Claims, and customizing authentication using claims instead of simple roles can become important for more complex application which require fine-grained control of permissions.
What Next?
In this post we created a “quick and dirty” implementation which performs some very basic authentication and authorization for our application.
In the real world, we would definitely tend to some critical details, such as proper crypto for hashing passwords. We would also probably want to beef up our design by applying some common patterns of abstraction. Notice, we have coded everything here directly to the implementation class. Also, we have rather tightly coupled our logical processing to our persistence model.
Lastly, we have put in place only the most rudimentary validation and exception handling.
We could go down a long road exploring how to better separate our persistence mechanism from our authentication logic, and more effectively handling exceptions and errors. However, those details are often application-specific, and/or require a long, long post.
Instead, we could now take everything we have learned, and pull in some ready-made components which already provide all of this, and more.
If the work we have done so far has been beginning to look a little familiar, that is no accident.
In the next post, we will implement our own authentication and authorization using the Identity 2.1 Framework.
NEXT: Understanding OWIN/Katana Authentication/Authorization Part III: Adding Identity
Additional Resources and Items of Interest
- ASP.NET Web Api: Understanding OWIN/Katana Authentication/Authorization Part I: Concepts
- ASP.NET Web Api 2.2: Create a Self-Hosted OWIN-Based Web Api from Scratch
- ASP.NET: Understanding OWIN, Katana, and the Middleware Pipeline
- ASP.NET Web Api and Identity 2.0 – Customizing Identity Models and Implementing Role-Based Authorization
- ASP.NET Identity 2.0: Introduction to Working with Identity 2.0 and Web API 2.2
Articles by others I have found invaluable:
- Derek Bailey: Don’t Do Role-Based Authorization Checks; Do Activity-Based Authorization Checks
- Taiseer Judeh’s Blog
Comments
Imdadhusen
AuthorThank you very much for writing entire series on Web API, OWIN, Authentication and Authorisation. This is really helped me a lot to understand the fundamental and how it work with nice code explanation.
Could you please help me how this concept is fit in my case.
Sorry for being dense! I have App Server (REST API using APS.Net MVC 5, Entity Framework 6) and Web Server (purely Single Page Architecture using Backbone.js, Require.js and Underscore.js). Now i wanted understand how can i call Middlewear from Web Server to get benefit of OWIN and Katana.
Your help would highly appreciated!
Again thank you for sharing nice articles with us.
Imdadhusen
Joao Matos Silva
AuthorHi John,
On my company we actually did a very similar approach, The only difference is that we tried to have a context per request. We also used a Ioc container managing it…
The problem we faced is that, for example, the AuthenticationTokenProvider.Create is called already on the "End_Request" state, so every IoC that we tried has already called dispose on their resources.
I tryed to raise that discussion here (https://katanaproject.codeplex.com/discussions/574273), but I haven't got any answer. Any clues?
Mark
AuthorGreat articles, very clear and helpful thanks!!
John Atten
Author@RD –
I'll have to play with it. To use SQL Server in general you would really only need to change the connection string, and maybe remove the SQL CE package. In other words, re-target EF towards SQL Server.
FOr the Azure deploy/Work Role piece, I haven;t actually done this yet, so I'll have to update you. That will likely be a post of its own…
RD
AuthorThanks John. I have one more question regarding Part III, I was reading this,
http://www.strathweb.com/2015/01/migrating-asp-net-web-api-mvc-6-exploring-web-api-compatibility-shim/
Of course, with Q2 expected delivery of Visual Studio 2015 (which prob. means June 2015), I'm not counting on a Web API Shim at this point.
But I was just creating some Tables in Azure using SQL Server 2014 Management Studio and I'd really like to implement this beyond CE to SQL Server.
A few posts back you wrote "If so, most of the following will work just as well if you pull down the standard Entity Framework package and work against SQL Server"
I'd love to do exactly that. Any chance you can include some comments on what adaptations would be required to use SQL Server 2014 on Azure and the Worker Roles in your Part III?
Thanks much as always. – RD
John Atten
Author@RD –
Yes, Taiseer has put out some great content in this same area.
My goal has been to build up slowly, so that by the time we bring identity into the picture, the fundamental concepts are better understood.
Deploying to Azure is definitely doable, in that one can deploy to w "worker role" in this self-hosted configuration.
And yes, MySql or Mongo are options, and of course, one could also start with SQLite until the need to scale up arose.
RD
AuthorJohn,
Very much enjoy all your articles: Love the ground up approach.
I assume in Part III you'll use Identity 2.1 like this article by Taiseer someone you mentioned I think you appreciate,
http://bitoftech.net/2015/02/03/asp-net-identity-2-accounts-confirmation-password-user-policy-configuration/
Also, any heads up on Deploying this on Azure would be great. With the pipeline it seems very flexible to plug-in MySql, possibly MongoDB so that one can plan out optimal cost efficiency; for anyone with [i]dreams[/i] of a startup it sure seems crucial.
Everyone has a different approach, but in my mind, your philosophy on approaching complex technology is really the best and I'm grateful for it.
{{RD}}