RESTful API with authentication using Web API and JWT - Part 2

Introduction

We will be creating a RESTful (REST like) HTTP service using Web API feature of the ASP.NET framework.

The purpose of this code is to develop the Restaurent API, using Microsoft Web API with (C#),which authenticates and authorizes some requests, exposes OAuth2 endpoints, and returns data about meals and reviews for consumption by the caller. The caller in this case will be Postman, a useful utility for querying API’s.

Note: Anytime you are stuck you can directly download the code from here on GitHub or ask in the comments below.

Pre-requisite

  1. Basic knowledge Web API with C#
  2. Visual Studio
  3. Postman
  4. Part 1

Authentication and Authorization Using OAuth and JSON Web Tokens (JWT)

We will open up an OAuth endpoint to client credentials and return a token which describes the users claims. For each of the users roles we will add a claim (which will be used to control which views the user has access to on the client-side). We use OWIN to add our OAuth configuration into the pipeline. Add a new class to the project called Startup.cs and add the following code

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(Meals.Service.Startup))]

namespace Meals.Service
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureOAuth(app);
        }
    }
}

Notice that Startup is a partial class. I’ve done that because I want to keep this class as simple as possible, because as the application becomes more complicated and we add more and more middle-ware, this class will grow exponentially. You could use a static helper class here, but the preferred method from the MSDN documentation seems to be leaning towards using partial classes specifically.

Under the App_Start folder add a new class called Startup.OAuth.cs and add the following code

using Meals.Service.Core;
using Meals.Service.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.DataHandler.Encoder;
using Microsoft.Owin.Security.Jwt;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Configuration;

namespace Meals.Service
{
    public partial class Startup
    {
        public void ConfigureOAuth(IAppBuilder app)
        {
            var issuer = ConfigurationManager.AppSettings["issuer"];
            var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]);
            app.CreatePerOwinContext(() => new MealsContext());
            app.CreatePerOwinContext(() => new MealUserManager());
            app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = new[] { "Any" },
                IssuerSecurityKeyProviders = new IIssuerSecurityKeyProvider[] {
                    new SymmetricKeyIssuerSecurityKeyProvider(issuer, secret)
                }
            });
            app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/oauth2/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
                Provider = new CustomOAuthProvider(),
                AccessTokenFormat = new CustomJwtFormat(issuer)
            });
        }
    }
}

OAuth secrets

Notice the code in the above file

var issuer = ConfigurationManager.AppSettings["issuer"];
var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]);
  • Issuer - a unique identifier for the entity that issued the token (not to be confused with Entity Framework’s entities)
  • Secret - a secret key used to secure the token and prevent tampering

Split these values out into their own configuration file called keys.config and add a reference to that file in the main Web.config. I do this so that I can exclude just the keys from source control by adding a line to my .gitignore file.

To do this, open Web.config and change the <appSettings> section as follows

<appSettings file="keys.config">
</appSettings>

Now add a new file to your project called keys.config and add the following code

<appSettings>
  <add key="issuer" value="http://localhost:56228/"/>
  <add key="secret" value="IxrAjDoa2FqElO7IhrSrUJELhUckePEPVpaePlS_Xaw"/>
</appSettings>

We made use of OWIN to manage instances of objects for us, on a per request basis. The pattern is comparable to IoC, in that you tell the “container” how to create an instance of a specific type of object, then request the instance using a Get<T> method.

OWIN context

The first time we request an instance of BooksContext for example, the lambda expression will execute and a new BooksContext will be created and returned to us. Subsequent requests will return the same instance.

Note: The life-cycle of object instance is per-request. As soon as the request is complete, the instance is cleaned up.

Bearer Authentication/ Authorization

We used the following code to enable bearer authentication

app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
            {
                AuthenticationMode = AuthenticationMode.Active,
                AllowedAudiences = new[] { "Any" },
                IssuerSecurityKeyProviders = new IIssuerSecurityKeyProvider[] {
                    new SymmetricKeyIssuerSecurityKeyProvider(issuer, secret)
                }
            });

The key takeaway of this code;

State who is the audience (we’re specifying “Any” for the audience, as this is a required field but we’re not fully implementing it). State who is responsible for generating the tokens. Here we’re using SymmetricKeyIssuerSecurityTokenProvider and passing it our secret key to prevent tampering. We could use the X509CertificateSecurityTokenProvider, which uses a X509 certificate to secure the token. This code adds JWT bearer authentication to the OWIN pipeline.

Enabling OAuth

We need to expose an OAuth endpoint so that the client can request a token (by passing a user name and password).

app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
            {
                AllowInsecureHttp = true,
                TokenEndpointPath = new PathString("/oauth2/token"),
                AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
                Provider = new CustomOAuthProvider(),
                AccessTokenFormat = new CustomJwtFormat(issuer)
            });

Some important notes with this code;

  • We’re going to allow insecure HTTP requests whilst we are in development mode. You might want to disable this using a #IF Debug directive so that you don’t allow insecure connections in production.
  • Open an endpoint under /oauth2/token that accepts post requests.
  • When generating a token, make it expire after 30 minutes (1800 seconds).
  • We will use our own provider, CustomOAuthProvider, and formatter, CustomJwtFormat, to take care of authentication and building the actual token itself.
  • We need to write the provider and formatter next.

Formatting the JWT

Create a new class under the Identity folder called CustomJwtFormat.cs.

namespace Meals.Service.Identity
{
    using Microsoft.Owin.Security;
    using Microsoft.Owin.Security.DataHandler.Encoder;
    using System;
    using System.Configuration;
    using System.IdentityModel.Tokens;
    using System.IdentityModel.Tokens.Jwt;
    using Thinktecture.IdentityModel.Tokens;

    public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
    {
        private static readonly byte[] _secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]);
        private readonly string _issuer;

        public CustomJwtFormat(string issuer)
        {
            _issuer = issuer;
        }

        public string Protect(AuthenticationTicket data)
        {
            if (data == null)
            {
                throw new ArgumentNullException(nameof(data));
            }

            var issued = data.Properties.IssuedUtc;
            var expires = data.Properties.ExpiresUtc;
            var securityKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(_secret);
            var signingCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);

            return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(_issuer, "Any", data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingCredentials));
        }

        public AuthenticationTicket Unprotect(string protectedText)
        {
            throw new NotImplementedException();
        }
    }
}

Custom OAuth Provider

Now we want to authenticate the user, create CustomOAuthProvider in Identity folder

using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Meals.Service.Core;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;

namespace Meals.Service.Identity
{
    public class CustomOAuthProvider : OAuthAuthorizationServerProvider
    {
        public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });

            var user = context.OwinContext.Get<MealsContext>().Users.FirstOrDefault(u => u.UserName == context.UserName);
            if (!context.OwinContext.Get<MealUserManager>().CheckPassword(user, context.Password))
            {
                context.SetError("invalid_grant", "The user name or password is incorrect");
                context.Rejected();
                return Task.FromResult<object>(null);
            }

            var ticket = new AuthenticationTicket(SetClaimsIdentity(context, user), new AuthenticationProperties());
            context.Validated(ticket);

            return Task.FromResult<object>(null);
        }

        public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            context.Validated();
            return Task.FromResult<object>(null);
        }

        private static ClaimsIdentity SetClaimsIdentity(OAuthGrantResourceOwnerCredentialsContext context, IdentityUser user)
        {
            var identity = new ClaimsIdentity("JWT");
            identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            identity.AddClaim(new Claim("sub", context.UserName));

            var userRoles = context.OwinContext.Get<MealUserManager>().GetRoles(user.Id);
            foreach (var role in userRoles)
            {
                identity.AddClaim(new Claim(ClaimTypes.Role, role));
            }

            return identity;
        }
    }
}

As we’re not checking the audience, when ValidateClientAuthentication is called we can just validate the request. When the request has a grant_type of password, which all our requests to the OAuth endpoint will have, the above GrantResourceOwnerCredentials method is executed. This method authenticates the user and creates the claims to be added to the JWT.

Testing

Now it’s time to build the code and test it. If your code doesn’t build, please check with the GitHub version here

Open Postman and hit your Meals endpoint

You should be able to get data of meals with it’s reviews this means are service is up and running.

Get Data

Authenticating Endpoints

Add a new file to the App_Start folder, called FilterConfig.cs and add the following code

namespace Meals.Service
{
    using System.Web.Http;

    public class FilterConfig
    {
        public static void Configure(HttpConfiguration config)
        {
            config.Filters.Add(new AuthorizeAttribute());
        }
    }
}

To restrict access to all endpoints (except the OAuth endpoint) to requests that have been authenticated add this code from Global.asax.cs

GlobalConfiguration.Configure(FilterConfig.Configure);

But if you wish to restrict access to selected endpoints methods then you can add the following code before each method

[Authorize(Roles = "Administrator")]

Multiple roles can be added seperated by a comma (‘,’).

And to restrict for all roles, just add

[Authorize]

Generating Token

Make a POST request to the OAuth endpoint, and include the following;

Headers

  • Accept application/json
  • Accept-Language en-us
  • Audience Any

Body

  • username administrator
  • password administrator123
  • grant_type password

Token

Make sure you set the message type as x-www-form-urlencoded

Now in our code we have restriced the delete review method in ReviewsController.cs for users with Administrator role.

To test this, generate the token as mentioned above and pass it in the Authorization header as a Bearer oken while hitting the endpoint http://localhost:62996 /api/reviews/2 in a DELETE method

e.g.

Authorization Bearer eyJ0eXAiOiJ...RWZQ

Debugging Issues

If you are having issues running the code you can directly download the code from here on GitHub or ask in the comments below.