ssmolen
ssmolen

Reputation: 81

static api key for Web API

I've built a RESTful API (using ASP.NET Web API 2) which is only meant to be consumed from a single end-point. This end-point is a basic front-end site containing only HTML/CSS/JS. Due to various reasons, the front-end site and the API are completely external from one-another, with the front-end site being whitelisted in the API's CORS configuration.

I'm now trying to lock-down the API so that it's only accessible from this particular end-point, without introducing a new login system, because the context of where this page lives ensures that anyone accessing it is already a trusted user (it's technically behind a login system, but the page consuming the API has almost no knowledge of this context).

At a high level, I'd like to introduce a statically defined API Key of some sort, that would be hardcoded into both the API and the JavaScript of the consuming page, to help ensure that it's the only end-point accessing the API. We can assume that all communications between the front-end page and the API will be over a secure SSL/TLS connection.

My question: for such a case where I want to authenticate API requests from a particular page with a statically-defined API Key, what would be my best option from an ease-of-implementation standpoint? Most of the articles that I've found on Web API Authorization pivot around a user login system and seem grossly over-engineered for my particular use-case. I'd consider myself a novice when it comes to the subject and so I'm really just hoping for someone to point me in the right direction.

Thanks!

Upvotes: 4

Views: 2612

Answers (1)

Ebbelink
Ebbelink

Reputation: 764

It seems like you are looking for a global filter in this specific case.

An authentication filter is a component that authenticates an HTTP request

You would basically send the shared / static api key with every request in the Authorization header and the custom filter would process this and decide whether the request is valid or not.

A basic implementation of the filter:

public class ApiKeyAuthenticationAttribute : IAuthenticationFilter
{
    public bool AllowMultiple { get; set; }

    public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        HttpRequestMessage request = context.Request;

        // Get Auth header
        AuthenticationHeaderValue authorization = request.Headers.Authorization;

        // Validate the static token
        if (authorization?.Parameter == "123")
        {
            IPrincipal principal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new Claim("CLAIMTYPE", "CLAIMVALUE") }));

            context.Principal = principal;
        }
        else
        {
            context.ErrorResult = new AuthenticationFailureResult(request);
        }
    }

    public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
        var challenge = new AuthenticationHeaderValue("Basic");
        context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);

        return Task.FromResult(0);
    }
}

And to enable it for all calls to your api add it to your WebApiConfig:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Some more config here
        config.Filters.Add(new IdentityBasicAuthenticationAttribute());
    }
}

The AuthenticationFailureResult and AddChallengeOnUnauthorizedResult are implementations of IHttpActionResult. For comprehensiveness I will add them here.

AuthenticationFailureResult

class AuthenticationFailureResult : IHttpActionResult
{
    private HttpRequestMessage _request;

    public AuthenticationFailureResult(HttpRequestMessage request)
    {
        _request = request;
    }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        response.RequestMessage = _request;
        response.Content = new StringContent("ACCESS DENIED MESSAGE");

        return Task.FromResult(response);
    }
}

AddChallengeOnUnauthorizedResult

class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
    public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
    {
        Challenge = challenge;
        InnerResult = innerResult;
    }

    public AuthenticationHeaderValue Challenge { get; private set; }

    public IHttpActionResult InnerResult { get; private set; }

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);

        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            // Only add one challenge per authentication scheme.
            if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
            {
                response.Headers.WwwAuthenticate.Add(Challenge);
            }
        }

        return response;
    }
}

This code is from or a derivative of this article Authentication Filters in ASP.NET Web API 2 and this article Authentication Filters in ASP.NET Web API 2

Upvotes: 4

Related Questions