Simple Web Token (SWT) as OAuth 2.0 Bearer Token for ASP.NET Web API

So, you have made the decision to use SWT token as bearer token to access OAuth 2.0 protected ASP.NET Web API. If you are not that specific about SWT and any access token is okay, head out to DotNetOpenAuth. To the best of my knowledge and belief, DNOA does not support SWT tokens but if that is no concern, DNOA is the best path to take – no point in reinventing the wheel. If you are keen on using SWT as bearer token through OAuth 2.0, do stick around.

I have covered Simple Web Token in one of my previous posts. I’ll limit this post to getting a token from the token issuer, an ASP.NET Web API, in this case and using the token to access other ASP.NET Web APIs. OAuth defines four grant types, of which one is client credentials, which I’ll cover here. Basic sequence is below.

Sequence

Client uses basic authentication to pass along user ID and password in the HTTP Auth header through basic scheme to the Authorization Server, which is basically the token issuer. Transport layer security (read HTTPS) is a must for this part. Authorization server authenticates the credentials and issues an access token. In our case, authorization server is an ASP.NET Web API controller (TokenIssuer) and access token is a SWT. It is important to note that this request is a HTTP POST and the entity body has two parameters scope and grant_type with values respectively http://localhost and client_credentials. Former is just for illustration and latter denotes the grant type of client credentials. Here is the code for the token issuer ASP.NET Web API.

public class TokenIssuerController : ApiController
{
    public HttpResponseMessage Post(string grant_type, string scope)
    {
        var headers = Request.Headers;
        if (header != null && headers.Authorization != null)
        {
            if (String.IsNullOrWhiteSpace(grant_type) ||
                        String.IsNullOrWhiteSpace(scope))
            {
                return new HttpResponseMessage<TokenResponse>(
                    new TokenResponse()
                    {
                        Error = OAuthError.INVALID_REQUEST
                    },
                    HttpStatusCode.BadRequest);
            }
            else if (grant_type != "client_credentials")
            {
                return new HttpResponseMessage<TokenResponse>(
                    new TokenResponse()
                    {
                        Error = OAuthError.UNSUPPORTED_GRANT_TYPE
                    },
                    HttpStatusCode.BadRequest);
            }

            Encoding encoding = Encoding.GetEncoding("iso-8859-1");

            string credentials = encoding.GetString(
                                    Convert.FromBase64String(
                                         headers.Authorization.Parameter));
            string[] parts = credentials.Split(':');

            string userName = parts[0];
            string password = parts[1];

            if (true) // TODO - The actual user ID password check
            {
                var token = new SimpleWebToken("http://localhost")
                {
                    Audience = "self", // TODO - Set it based on user Id
                };
                token.AddClaim(ClaimTypes.Name, "badri");
                token.AddClaim(ClaimTypes.Email, "badri@nowhere.com");
                token.AddClaim(ClaimTypes.Role, "Developer");
                token.AddClaim(ClaimTypes.Role, "Developer2");

                var tokenResponse = new TokenResponse()
                {
                    AccessToken = token.ToString(),
                    TokenType = "bearer",
                    ExpiresIn = 600
                };

                return new HttpResponseMessage<TokenResponse>(
                              tokenResponse, HttpStatusCode.OK);
        }
        else
        {
            var response = new HttpResponseMessage<TokenResponse>(
                new TokenResponse()
                {
                    Error = OAuthError.INVALID_CLIENT
                },
                HttpStatusCode.Unauthorized);

            response.Headers.WwwAuthenticate.Add(
                new AuthenticationHeaderValue("Basic"));

            return response;
        }

    }
    else
    {
        var response = Request.CreateResponse(HttpStatusCode.Unauthorized);
        response.Headers.WwwAuthenticate.Add(
                        new AuthenticationHeaderValue("Basic"));

        return response;
    }
}

public static class OAuthError
{
    public const string INVALID_REQUEST = "invalid_request";
    public const string INVALID_CLIENT = "invalid_client";
    public const string INVALID_GRANT = "invalid_grant";
    public const string UNAUTHORIZED_CLIENT = "unauthorized_client";
    public const string UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type";
    public const string INVALID_SCOPE = "invalid_scope";
}

We are basically leveraging ASP.NET Web API’s ability to convert an object into JSON. We use TokenResponse class for this purpose. Here is the code. Please note that I’m not handling content negotiation here. What if the client specifically asks for XML instead of the default JSON? That is something for you to think about!

[DataContract]
public class TokenResponse
{
    [DataMember(Name="access_token", EmitDefaultValue=false)]
    public string AccessToken { get; set; }

    [DataMember(Name = "token_type", EmitDefaultValue = false)]
    public string TokenType { get; set; }

    [DataMember(Name = "expires_in", EmitDefaultValue = false)]
    public int ExpiresIn { get; set; }

    [DataMember(Name = "scope", EmitDefaultValue = false)]
    public string Scope { get; set; }

    [DataMember(Name = "error", EmitDefaultValue = false)]
    public string Error { get; set; }
}

That is about token issuance part. Now to work off the token, we can create a DelegatingHandler, just like what we did with Basic and Digest authentication. Handler will pull the token off the header and validate. I don’t think there is any value in repeating the code for that part, since it is very straight forward. Two important things, though. [1] Once the token JSON is pulled off the header, it can be parsed back to TokenResponse object using for example, Newtonsoft.Json.JsonConvert. [2] If the validation fails or if the request is not in correct format, we have to respond by sending error parameters in the response header, like this.

if (request.Headers.Authorization == null ||
        (request.Headers.Authorization.Scheme == SCHEME &&
            String.IsNullOrWhiteSpace(
                    request.Headers.Authorization.Parameter)))
{
    return new AuthenticationHeaderValue(
                    SCHEME, "error=\"invalid_request\"");
}
else if (!isTokenValid)
{
    return new AuthenticationHeaderValue(
                    SCHEME, "error=\"invalid_token\"");
}
else
{
    return new AuthenticationHeaderValue(SCHEME);
}

For basic and digest authentication, we used Internet Explorer as test harness. Both basic and digest schemes are part of HTTP spec and browsers are good in dealing with HTTP. For bearer scheme though, we have to write our own test harness but it is no big deal.

string token = String.Empty;

using (HttpClient client = new HttpClient())
{
    string creds = String.Format("{0}:{1}", "badri", "badri");
    byte[] bytes = Encoding.ASCII.GetBytes(creds);
    var header = new AuthenticationHeaderValue("Basic",
                               Convert.ToBase64String(bytes));
    client.DefaultRequestHeaders.Authorization = header;

    var postData = new List<KeyValuePair<string, string>>();
    postData.Add(new KeyValuePair<string, string>
                       ("grant_type", "client_credentials"));
    postData.Add(new KeyValuePair<string, string>
                       ("scope", "http://localhost"));

    HttpContent content = new FormUrlEncodedContent(postData);

    token = client.PostAsync("http://myserver/api/tokenissuer", content)
                     .Result.Content.ReadAsStringAsync().Result;
}

using (HttpClient client = new HttpClient())
{
    byte[] bytes = Encoding.ASCII.GetBytes(token);
    var header = new AuthenticationHeaderValue("Bearer",
                                Convert.ToBase64String(bytes));
    client.DefaultRequestHeaders.Authorization = header;

    var response = client.GetAsync("http://myserver/api/products")
                             .Result
                             .Content.ReadAsStringAsync().Result;
}
Advertisements

2 thoughts on “Simple Web Token (SWT) as OAuth 2.0 Bearer Token for ASP.NET Web API

  1. Hi, there great tutorial, but i have one question:

    where did you get the class SimpleWebToken ?

    if (true) // TODO – The actual user ID password check
    {
    var token = new SimpleWebToken(“http://localhost”)
    {
    Audience = “self”, // TODO – Set it based on user Id
    };
    token.AddClaim(ClaimTypes.Name, “badri”);
    token.AddClaim(ClaimTypes.Email, “badri@nowhere.com”);
    token.AddClaim(ClaimTypes.Role, “Developer”);
    token.AddClaim(ClaimTypes.Role, “Developer2”);

    Thanks.

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