Set cookie from Web Api 2 IAuthenticationFilter AuthenticateAsync method

孤者浪人 提交于 2019-12-07 13:49:35

问题


Using Web Api 2.2, I have a custom IAuthenticationFilter that I use for authenticating client requests with a custom scheme.

Basically, when a client is not authenticated and wants to access a protected resource, he sends an Authorization header: Authorization: MyCustomScheme XXXXXXX alongside the request. The filter then validates the credentials, authenticates the user and generates a stateless authentication token for further access (similar to a JWT).

I would like to store the resulting authentication token in a cookie. When present in incoming requests, the cookie is locally validated in a separate filter (which is not presented here).

My problem is that if I try to set the cookie like this:

Task IAuthenticationFilter.AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
    if (context.Request.Headers.Authorization != null &&
        string.Equals(context.Request.Headers.Authorization.Scheme, "MyCustomScheme", StringComparison.OrdinalIgnoreCase))
    {
        // This works
        CustomPrincipal principal = this.ValidateCredentials(context.Request.Headers.Authorization.Parameter);
        context.Principal = principal;

        // This doesn't work: context.ActionContext.Response is null
        var cookie = new CookieHeaderValue("MySessionCookie", principal.AuthenticationToken) { Path = "/", HttpOnly = true };
        context.ActionContext.Response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
    }
    return Task.FromResult(0);
}

Then it fails because context.ActionContext.Response is null. How do I add a cookie to the response from within AuthenticateAsync?

See related: Setting Cookie values in HttpAuthenticationContext for IAuthenticationFilter (you can see in the comments that people experience the same issue).


回答1:


My requirements were to add a header but it should be easy to adapt to add a cookie.

I took a different approach on this. I put the header I wanted to add into context.Request.Properties. Then in ChallengeAsync (it gets called for every request regardless) via a IHttpActionResult I check for the presence of the property and if it exists add it to the headers. Something like this:

protected class AddRenewOnAauthorizedResult : IHttpActionResult {

    public const string RenewalPropertyKey = "ETicket.RenewalKey";

    public AddRenewOnAauthorizedResult(HttpRequestMessage request, IHttpActionResult innerResult) {
        this.Request = request;
        this.InnerResult = innerResult;
    }

    public HttpRequestMessage Request { get; set; }
    public IHttpActionResult InnerResult { get; set; }

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken) {

        HttpResponseMessage response = await this.InnerResult.ExecuteAsync(cancellationToken);

        if (Request.Properties.ContainsKey(RenewalPropertyKey)) Request.response.Headers.Add("X-ETicket-Renew", Request.Properties(RenewalPropertyKey));

        Return response;

}

}

Then in ChallengeAsync:

public Threading.Tasks.Task ChallengeAsync(HttpAuthenticationChallengeContext context, Threading.CancellationToken cancellationToken)
{

    context.Result = new AddRenewOnAauthorizedResult(context.Request, context.Result);
    return Task.FromResult(0);

}



回答2:


I got the filter to work by implementing IActionFilter in addition to IAuthenticationFilter. This method is valid because you get access to the request, the response and the user identity in the same place. This is my implementation:

async Task<HttpResponseMessage> IActionFilter.ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
    // Process the request pipeline and get the response (this causes the action to be executed)
    HttpResponseMessage response = await continuation();

    // If the user is authenticated and the token is not present in the request cookies, then it needs to be set
    CustomPrincipal principal = actionContext.ControllerContext.RequestContext.Principal as CustomPrincipal;
    if (principal != null && !actionContext.Request.Headers.GetCookies("MySessionCookie").Any())
    {
        // Set the cookie in the response
        var cookie = new CookieHeaderValue("MySessionCookie", principal.AuthenticationToken) { Path = "/", HttpOnly = true };
        response.Headers.AddCookies(new CookieHeaderValue[] { cookie });
    }

    return response;
}

I find this method very unpractical (mixing interfaces), you should definitely have access to the response in IAuthenticationFilter.AuthenticateAsync (via an async continuation callback for exemple, or by being able to access the action result (IHttpActionResult) in the context, like in the ChallengeAsync method of the same interface).




回答3:


You may need to implement IRequiresSessionState to have cookies persist?

See: http://www.strathweb.com/2012/11/adding-session-support-to-asp-net-web-api/



来源:https://stackoverflow.com/questions/31873720/set-cookie-from-web-api-2-iauthenticationfilter-authenticateasync-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!