4

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!

1
  • I'm having a similar case, I have an API over SSL and I want to know if adding a header or param with a key will make it more secure or is a bad practice, what do you did? Commented Sep 27, 2016 at 12:37

1 Answer 1

4

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

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.