[Dev Tip] ASP.NET Web Api: Understanding OWIN/Katana Authentication/Authorization Part II: Models and Persistence


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.

Image by clement127  |  Some Rights Reserved

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.

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:

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 theGrantOwnerResourceCredentials() 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 MyUserclass. 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 MyUserStoreclass, 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 ourCompany table with some starting data. Update the code as follows. You will also need to addSystem.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:

client-with-authneticated-user

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:

client-with-invalid-password

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:

client-with-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.

Additional Resources and Items of Interest

Articles by others I have found invaluable:

REF: http://typecastexception.com/post/2015/01/25/ASPNET-Web-Api-Understanding-OWINKatana-AuthenticationAuthorization-Part-II-Models-and-Persistence.aspx

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s