1

In one of apis in my controller I need to reference HttpContext.Current. Everything worked fine until I implemented a custom authentication filter. The filter uses async methods with await. However, with the filter in place, the HttpContext.Current is null when it reaches my controller.

I'm assuming that this is because my controller executes on a different thread that it used to due to async/await in the filter.
If this is the case, how should I access the 'original' context in my controller?
This is a bit disappointing because filters seem like a good pluggable idea but unfortunately they come with some [serious] disadvantages.
Sample pseudo code:

public class TestAuthFilter : Attribute, IAuthenticationFilter
{
    private static string validationClaim = "testClaim";
    private tokenValidationUri;

    public bool AllowMultiple { get { return false; } }

    public TestAuthFilter(string tokenValidationUri)
    {
        this.tokenValidationUri = tokenValidationUri;
    }

    public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
        // We are not adding any authentication challenges to the response
        return Task.FromResult(0);
    }

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

        if (authorization == null)
        {
            return;
        }

        if (authorization.Scheme != "Bearer")
        {
            return;
        }

        if (String.IsNullOrEmpty(authorization.Parameter))
        {
            context.ErrorResult = new AuthenticationFailureResult("Missing access token", request);
            return;
        }

        string token = authorization.Parameter;
        IPrincipal principal = await AuthenticateAsync(token, cancellationToken);
        if (principal == null)
        {
            context.ErrorResult = new AuthenticationFailureResult("Invalid access token", request);
        }
        else
        {
            context.Principal = principal;
        }
    }

    public async Task<IPrincipal> AuthenticateAsync(string token, CancellationToken cancellationToken)
    {
        using (HttpClient client = new HttpClient())
        {
            try
            {                    
                HttpResponseMessage response = await client.GetAsync(tokenValidationUri + token, cancellationToken);

                using (HttpContent content = response.Content)
                {
                    string result = await content.ReadAsStringAsync();
                    if (result != null)
                    {
                        if (response.IsSuccessStatusCode)
                        {
                            var success = await response.Content.ReadAsAsync<TokenValidationSuccess>();
                            if (success.audience.Equals(validationAudience, StringComparison.Ordinal))
                            {
                                TestPrincipal principal = new TestPrincipal([...]);
                                return principal;
                            }
                        }
                        else
                        {
                            var error = await response.Content.ReadAsAsync<TokenValidationError>();
                        }
                    }
                }
            }
            catch (HttpRequestException e)
            {
                Debug.WriteLine("Exception: {0}", e.Message);
            }
        }
        return null;
    }
}



I'd appreciate any suggestions.
Thanks.

bdristan
  • 1,048
  • 1
  • 12
  • 36
  • Pass the context as a parameter. – Lex Li Aug 31 '15 at 15:18
  • How can I pass the context as a parameter to my controller? This implies that I should be able to grab the 'original' context at some point and then pass it to the controller. However, the methods of my controller are invoked by the framework. Could you please elaborate? – bdristan Aug 31 '15 at 16:01
  • 1
    Could you submit your custom filter? – Sam FarajpourGhamari Aug 31 '15 at 16:42
  • I just posted sample code. – bdristan Aug 31 '15 at 20:12
  • 1
    Your `AuthenticationFilter` must have `OnAuthentication()` method. Could you submit it too. And also where are you calling `HttpContext.Current` I could not find it in your code. – Sam FarajpourGhamari Aug 31 '15 at 20:28
  • IAuthenticationFilter has only 2 methods as per docs: https://msdn.microsoft.com/en-us/library/system.web.http.filters.iauthenticationfilter(v=vs.118).aspx. OnAuthentication() is not one of them. I call HttpContext.Current in methods of my controller (not included). As I mentioned before, if I remove the filter then the context in the controller is not null. – bdristan Aug 31 '15 at 20:43
  • 2
    @bdristan: Are you targeting .NET 4.5, ***and*** have you set `httpRuntime@targetFramework` to `4.5` in your `web.config`? – Stephen Cleary Aug 31 '15 at 20:59
  • OMG! That's it. I now recall reading about targetFramework=4.5 somewhere but ignored it thinking that setting target framework to 4.5 in project settings was equivalent to the former. I would flag your comment as an answer but I can't. Thanks. – bdristan Sep 01 '15 at 13:57

2 Answers2

1

The solution was to include "targetFramework=4.5" in in web.config. Credits go to Stephen Cleary - see comments above.

bdristan
  • 1,048
  • 1
  • 12
  • 36
0

Here's a good link on AuthenticateAsync in WebAPI: http://www.asp.net/web-api/overview/security/authentication-filters

Should take as parameter HttpAuthenticationContext, which contains thread safe versions of ActionContext and HttpRequestMessage Request.

Here's the inheritance chain to get there:

(HttpRequestMessage)System.Web.Http.Filters.HttpAuthenticationContext.ActionContext.ControllerContext.Request
TotPeRo
  • 6,561
  • 4
  • 47
  • 60
codeMonkey
  • 4,134
  • 2
  • 31
  • 50