Reputation: 3881
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).
Upvotes: 3
Views: 3011
Reputation: 2946
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);
}
Upvotes: 2
Reputation: 3881
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).
Upvotes: 1
Reputation: 24515
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/
Upvotes: 0