3

I have a need to read/write cookies during the authentication step of a WebAPI pipeline. I have created a custom filter for this.

In an attempt to comply with self-hosting concepts, what would be a safe way to access and write cookies out to the client? Rick Strahl commented that if we use HttpContext.Current.Response.Cookies.Add(), and my application is self-hosted, the context may/will not exist.

So how would I write a cookie out to the client using HttpAuthenticationContext and still be self-host safe?

Maxime Rossini
  • 3,612
  • 4
  • 31
  • 47
FrankO
  • 2,522
  • 6
  • 24
  • 34

2 Answers2

2

You can't access authContext.ActionContext.Response from within IAuthenticationFilter.AuthenticateAsync(). Well, actually you can, but only to set a new response and shortcut the rest of the pipeline.

I had the same problem (needed to set a cookie after a successful authentication) and solved it by implementing IActionFilter in addition to IAuthenticationFilter:

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();

    // Here you get access to:
    // - The request (actionContext.Request)
    // - The response (response) and its cookies (response.Headers.AddCookies())
    // - The principal (actionContext.ControllerContext.RequestContext.Principal)

    return response;
}

See: Set cookie from Web Api 2 IAuthenticationFilter AuthenticateAsync method

Community
  • 1
  • 1
Maxime Rossini
  • 3,612
  • 4
  • 31
  • 47
1
HttpAuthenticationContext authContext;
authContext.ActionContext.Response.Headers.AddCookies(/*cookies */);

edit2

HttpAuthenticationContext authContext;
var myCookie = new CookieHeaderValue("key", "value")
authContext.ActionContext.Response.Headers.Add("Set-Cookie", myCookie.ToString());

edit

AddCookie is an extension method located in System.Net.Http.Formatting.dll (as of version v5.2.2.0), and the extension method is declared by static class HttpResponseHeadersExtensions, located in namespace System.Net.Http.

  • If you cannot find the extension method, try locate HttpResponseHeadersExtensions class.

  • If you cannot find HttpResponseHeadersExtensions class, try upgrade Web Api 2 libraries. The most efficient way to upgrade all nuget packages of WebApi2 of every projects (for those who hate upgrading nuget packages like me), is to do a global search/replace on .config files of term 'version="x.x.x" targetFramework="net45"' (where x.x.x is an older version replaced by 'version="5.2.2" targetFramework="net45"'

  • In worst case scenario if your boss or your mom won't let you upgrade the nuget packages, you can always adopt a rebel attitude and decompile the code containing AddCookie, it appear to look like this:

        using System;
        using System.Collections.Generic;
        using System.ComponentModel;
        using System.Net.Http.Headers;
        using System.Net.Http.Properties;
        using System.Web.Http;
        namespace System.Net.Http
        {
            /// <summary> Provides extension methods for the <see cref="T:System.Net.Http.Headers.HttpResponseHeaders" /> class. </summary>
            [EditorBrowsable(EditorBrowsableState.Never)]
            public static class HttpResponseHeadersExtensions
            {
                private const string SetCookie = "Set-Cookie";
                /// <summary> Adds cookies to a response. Each Set-Cookie header is  represented as one <see cref="T:System.Net.Http.Headers.CookieHeaderValue" /> instance. A <see cref="T:System.Net.Http.Headers.CookieHeaderValue" /> contains information about the domain, path, and other cookie information as well as one or more <see cref="T:System.Net.Http.Headers.CookieState" /> instances. Each <see cref="T:System.Net.Http.Headers.CookieState" /> instance contains a cookie name and whatever cookie state is associate with that name. The state is in the form of a  <see cref="T:System.Collections.Specialized.NameValueCollection" /> which on the wire is encoded as HTML Form URL-encoded data.  This representation allows for multiple related "cookies" to be carried within the same Cookie header while still providing separation between each cookie state. A sample Cookie header is shown below. In this example, there are two <see cref="T:System.Net.Http.Headers.CookieState" /> with names state1 and state2 respectively. Further, each cookie state contains two name/value pairs (name1/value1 and name2/value2) and (name3/value3 and name4/value4). &lt;code&gt; Set-Cookie: state1:name1=value1&amp;amp;name2=value2; state2:name3=value3&amp;amp;name4=value4; domain=domain1; path=path1; &lt;/code&gt;</summary>
                /// <param name="headers">The response headers</param>
                /// <param name="cookies">The cookie values to add to the response.</param>
                public static void AddCookies(this HttpResponseHeaders headers, IEnumerable<CookieHeaderValue> cookies)
                {
                    if (headers == null)
                    {
                        throw Error.ArgumentNull("headers");
                    }
                    if (cookies == null)
                    {
                        throw Error.ArgumentNull("cookies");
                    }
                    foreach (CookieHeaderValue current in cookies)
                    {
                        if (current == null)
                        {
                            throw Error.Argument("cookies", Resources.CookieNull, new object[0]);
                        }
                        headers.TryAddWithoutValidation("Set-Cookie", current.ToString());
                    }
                }
            }
        }
    
    • In the end you feel a little stupid spending so much time looking for an extension method, when you realize that adding a cookie in webapi2 is simply done in a line of code:

headers.TryAddWithoutValidation("Set-Cookie", new CookieHeaderValue("key", "value")); //where headers is a HttpResponseHeaders

uzul
  • 1,096
  • 9
  • 23
  • Hi @uzul, I do not have `AddCookies` on my `Headers` object. Is there a different namespace? My `HttpAuthenticationContext` is part of the `System.Web.Http.Filters` namespace. – FrankO Apr 07 '15 at 14:48
  • Hi @FrankO, indeed, AddCookies is an extension method :) you simply need to add assembly System.Net.Http.Formatting.dll – uzul Apr 08 '15 at 02:34
  • Hi @uzul, I could not add it since the assembly is already part of the project. :) I tried adding the namespace but am still not able to get it to work. I am using Web API2 and .Net 4.5. Any other thoughts? – FrankO Apr 08 '15 at 20:32
  • Hi @FrankO, this is interesting. I'm using .NET4.5/web api 2 as well. I'm using assembly System.Net.Http.Formatting.dll, v5.2.2.0, and the extension method is located in namespace System.Net.Http, declared by static class HttpResponseHeadersExtensions – uzul Apr 08 '15 at 23:21
  • No go. That method does not allow me to add an HttpCookie which includes expiration date, httponly, secure and other properties. I don't get how MS overlooked this capability and has not clearly documented the 'new' way to get it done at this point in the process. Maybe they do not expect you to do anything in this step. – FrankO Apr 09 '15 at 19:29
  • 3
    Ok. I have what is needed but the problem with your solution is that `authContext.ActionContext.Response` is null and if I try using `authContext.ActionContext.Response = new HttpResponseMessage()` in the IAuthenticationFilter, the controller Action overwrites it when I use `return Request.CreateReponse(myObj);` – FrankO Apr 17 '15 at 17:21
  • I need to return this cookie during the authenticationfilter step unless there is another point in the pipeline I can manipulate the credentials and cookies before the Action is run. – FrankO Apr 17 '15 at 17:23
  • can't you just upgrade to latest web api? The other point in the pipeline I can think of is implementing a MessageHandler – uzul Apr 17 '15 at 20:59
  • 1
    I thought I was on the latest Web API? Version 2.2? I think I will have to use HttpContext.Current for now and change to token based methods. Thanks for your help @uzul. – FrankO Apr 17 '15 at 21:04
  • 1
    I'm having the same problem as FrankO. Response is null in AuthenticationFilter's AuthenticateAsync method. I'm on WebApi 2.2. How is one supposed to set cookies without resorting to HttpContext? – crush Jun 20 '15 at 01:50