Coder Perfect

[closed] How to Secure an ASP.NET Web API

Problem

I want to use ASP.NET Web API to create a RESTful web service that third-party developers may use to access the data in my application.

I’ve read a lot about OAuth, and it appears to be the industry standard, but finding a good example with documentation showing how it works (and that actually works!) has proven difficult. It appears to be really challenging (especially for a newbie to OAuth).

Is there a sample that builds and works and demonstrates how to do this?

I’ve downloaded numerous samples:

I’ve also looked at blogs that advocate a basic token-based method (like this) – it sounds like re-inventing the wheel, but it does have the benefit of being conceptually simple.

There appear to be a lot of similar inquiries on SO, but no decent answers.

What is everyone up to in this area?

Asked by Craig Shearer

Solution #1

Update:

For anyone interested in JWT, I’ve added this link to my prior answer on how to utilize JWT authentication for ASP.NET Web API.

We were able to successfully deploy HMAC authentication to a secure Web API. To hmac hash a message, HMAC256 should be used. HMAC authentication employs a secret key for each consumer that both the consumer and the server know. In most circumstances, the consumer’s hashed password is utilized as the secret key.

The message is often constructed from data in the HTTP request, or even bespoke data added to the HTTP header. Examples of message content include:

HMAC authentication might look like this under the hood:

After creating the signature (output of hmac hash), the consumer sends an HTTP request to the web server using the following template:

User-Agent: {agent}   
Host: {host}   
Timestamp: {timestamp}
Authentication: {username}:{signature}

The following is an example of a GET request:

GET /webapi.hmac/api/values

User-Agent: Fiddler    
Host: localhost    
Timestamp: Thursday, August 02, 2012 3:30:32 PM 
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=

The message to hash in order to obtain a signature:

GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n

Example for POST request with query string (signature below is not correct, just an example)

POST /webapi.hmac/api/values?key2=value2

User-Agent: Fiddler    
Host: localhost    
Content-Type: application/x-www-form-urlencoded
Timestamp: Thursday, August 02, 2012 3:30:32 PM 
Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw=

key1=value1&key3=value3

The message to hash in order to obtain a signature

GET\n
Thursday, August 02, 2012 3:30:32 PM\n
/webapi.hmac/api/values\n
key1=value1&key2=value2&key3=value3

Please note that the query string and form data must be in the correct order for the server code to construct the correct message.

When HTTP request comes to the server, an authentication action filter is implemented to parse the request to get information: HTTP verb, timestamp, uri, form data and query string, then based on these to build signature (use hmac hash) with the secret key (hashed password) on the server.

With the username on the request, the secret key is obtained from the database.

The server code then compares the signature on the request to the signature built; if they are equal, authentication is successful; if they are not, authentication fails.

The signature code is as follows:

private static string ComputeHash(string hashedPassword, string message)
{
    var key = Encoding.UTF8.GetBytes(hashedPassword.ToUpper());
    string hashString;

    using (var hmac = new HMACSHA256(key))
    {
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
        hashString = Convert.ToBase64String(hash);
    }

    return hashString;
}

So, how can you avoid a replay attack?

Add a constraint to the timestamp, such as:

servertime - X minutes|seconds  <= timestamp <= servertime + X minutes|seconds 

(servertime: the time the request was received by the server)

Also, save the request’s signature in memory (use MemoryCache, should keep in the limit of time). The following request will be refused if it has the same signature as the prior request.

The demonstration code can be found at https://github.com/cuongle/Hmac.WebApi.

Answered by cuongle

Solution #2

I’d recommend starting with the simplest solutions first; in your case, plain HTTP Basic Authentication + HTTPS might suffice.

If you can’t utilize https or need more advanced key management, you could look into HMAC-based alternatives, which have been offered by others. Amazon S3 (http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html) is a nice example of such an API.

In ASP.NET Web API, I created a blog post regarding HMAC-based authentication. The source for both the Web API service and the Web API client is accessible on Bitbucket. http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/

Here’s a link to a post regarding Web API Basic Authentication: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/

Keep in mind that if you give an API to third parties, you will almost certainly be responsible for delivering client libraries as well. Basic authentication provides a considerable benefit in this situation because it is supported out of the box on most programming platforms. HMAC, on the other hand, isn’t particularly standardized and will necessitate customization. These should be quite simple, but they will still need effort.

PS: Using HTTPS with certificates is also an option. http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/

Answered by Piotr Walat

Solution #3

Have you played around with DevDefined.OAuth?

I used it to employ 2-Legged OAuth to secure my WebApi. I’ve also put it through its paces using PHP clients.

Using this module, adding OAuth support is a breeze. The provider for ASP.NET MVC Web API is implemented as follows:

1) Download DevDefined’s source code. Bittercoder/DevDefined (OAuth): https://github.com/bittercoder/DevDefined OAuthContextBuilder extensibility is now possible with the latest version of OAuth.

2) Create the library and include it in your Web API project as a reference.

3) Create a custom context builder to support building a context from HttpRequestMessage:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Web;

using DevDefined.OAuth.Framework;

public class WebApiOAuthContextBuilder : OAuthContextBuilder
{
    public WebApiOAuthContextBuilder()
        : base(UriAdjuster)
    {
    }

    public IOAuthContext FromHttpRequest(HttpRequestMessage request)
    {
        var context = new OAuthContext
            {
                RawUri = this.CleanUri(request.RequestUri), 
                Cookies = this.CollectCookies(request), 
                Headers = ExtractHeaders(request), 
                RequestMethod = request.Method.ToString(), 
                QueryParameters = request.GetQueryNameValuePairs()
                    .ToNameValueCollection(), 
            };

        if (request.Content != null)
        {
            var contentResult = request.Content.ReadAsByteArrayAsync();
            context.RawContent = contentResult.Result;

            try
            {
                // the following line can result in a NullReferenceException
                var contentType = 
                    request.Content.Headers.ContentType.MediaType;
                context.RawContentType = contentType;

                if (contentType.ToLower()
                    .Contains("application/x-www-form-urlencoded"))
                {
                    var stringContentResult = request.Content
                        .ReadAsStringAsync();
                    context.FormEncodedParameters = 
                        HttpUtility.ParseQueryString(stringContentResult.Result);
                }
            }
            catch (NullReferenceException)
            {
            }
        }

        this.ParseAuthorizationHeader(context.Headers, context);

        return context;
    }

    protected static NameValueCollection ExtractHeaders(
        HttpRequestMessage request)
    {
        var result = new NameValueCollection();

        foreach (var header in request.Headers)
        {
            var values = header.Value.ToArray();
            var value = string.Empty;

            if (values.Length > 0)
            {
                value = values[0];
            }

            result.Add(header.Key, value);
        }

        return result;
    }

    protected NameValueCollection CollectCookies(
        HttpRequestMessage request)
    {
        IEnumerable<string> values;

        if (!request.Headers.TryGetValues("Set-Cookie", out values))
        {
            return new NameValueCollection();
        }

        var header = values.FirstOrDefault();

        return this.CollectCookiesFromHeaderString(header);
    }

    /// <summary>
    /// Adjust the URI to match the RFC specification (no query string!!).
    /// </summary>
    /// <param name="uri">
    /// The original URI. 
    /// </param>
    /// <returns>
    /// The adjusted URI. 
    /// </returns>
    private static Uri UriAdjuster(Uri uri)
    {
        return
            new Uri(
                string.Format(
                    "{0}://{1}{2}{3}", 
                    uri.Scheme, 
                    uri.Host, 
                    uri.IsDefaultPort ?
                        string.Empty :
                        string.Format(":{0}", uri.Port), 
                    uri.AbsolutePath));
    }
}

4) Create an OAuth provider using this tutorial: http://code.google.com/p/devdefined-tools/wiki/OAuthProvider. You may use this code in your AuthorizationFilterAttribute attribute in the last step (Accessing Protected Resource Example):

public override void OnAuthorization(HttpActionContext actionContext)
{
    // the only change I made is use the custom context builder from step 3:
    OAuthContext context = 
        new WebApiOAuthContextBuilder().FromHttpRequest(actionContext.Request);

    try
    {
        provider.AccessProtectedResourceRequest(context);

        // do nothing here
    }
    catch (OAuthException authEx)
    {
        // the OAuthException's Report property is of the type "OAuthProblemReport", it's ToString()
        // implementation is overloaded to return a problem report string as per
        // the error reporting OAuth extension: http://wiki.oauth.net/ProblemReporting
        actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
            {
               RequestMessage = request, ReasonPhrase = authEx.Report.ToString()
            };
    }
}

I have implemented my own provider so I haven’t tested the above code (except of course the WebApiOAuthContextBuilder which I’m using in my provider) but it should work fine.

Answered by Maksymilian Majer

Solution #4

To provide security, the Web API introduces the Attribute [Authorize]. This can be done on a worldwide scale (global.asx)

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

Or per controller:

[Authorize]
public class ValuesController : ApiController{
...

Naturally, your sort of authentication may differ, and you may wish to execute your own authentication; in this case, inheriting from Authorizate Attribute and extending it to match your needs may be useful:

public class DemoAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        if (Authorize(actionContext))
        {
            return;
        }
        HandleUnauthorizedRequest(actionContext);
    }

    protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
        challengeMessage.Headers.Add("WWW-Authenticate", "Basic");
        throw new HttpResponseException(challengeMessage);
    }

    private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        try
        {
            var someCode = (from h in actionContext.Request.Headers where h.Key == "demo" select h.Value.First()).FirstOrDefault();
            return someCode == "myCode";
        }
        catch (Exception)
        {
            return false;
        }
    }
}

And in your controller:

[DemoAuthorize]
public class ValuesController : ApiController{

Here’s a link to several more custom WebApi Authorizations implementations:

http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/

Answered by Dalorzo

Solution #5

If you wish to safeguard your API from a server-to-server perspective (no redirection to website for 2 legged authentication). The OAuth2 Client Credentials Grant protocol can be examined.

https://dev.twitter.com/docs/auth/application-only-auth

I’ve created a package that makes it simple to add this type of support to your WebAPI. It’s available as a NuGet package:

https://nuget.org/packages/OAuth2ClientCredentialsGrant/1.0.0.0

The library is designed to interact with the.NET Framework 4.5.

The package will create a readme file in the root of your project after you add it to it. You can find instructions on how to configure and use this package in the readme file.

Cheers!

Answered by Varun Chatterji

Post is based on https://stackoverflow.com/questions/11775594/how-to-secure-an-asp-net-web-api