jsonhttpinternet-explorer-11http-status-code-403websharper

IE11 does not set x-cfrtoken HTTP header, unless with an InPrivate window, this causes server to respond with HTTP 403 Access Denied


I've been baffled by this for two days now. The situation:

It appears that in InPrivate mode, the x-csfrtoken is set in the request header, outside InPrivate mode this header is not set. The server then returns an HTTP 403 error, which seems to be at the root of the problem.

I don't know how to instruct the server (IIS) to ignore this token.

To see this behavior in action, got to that site and type in anything, then click "Inloggen". You should see a login-error (in Dutch), but in IE11 on Windows 7, this error does not appear.

I tried this solution by Microsoft, on improper rights on LocalLow, but it did not resolve the issue and seems otherwise unrelated.


Solution

  • Apparently this is a bug in IE11 on Windows 7 and Windows 8 / 8.1. I found out that the browser does send the csrftoken cookie, but forgets the required x-csrftoken HTTP Header parameter, which all other browsers, including older and newer versions of IE and IE11 on Windows 10 properly send.

    If your toolchain protects itself by validating the x-csrftoken (which is recommended by any framework), then this fails with IE11. It was discussed here for WebSharper, but without a complete solution yet.

    The workaround that I found that worked properly is the following. It's hacky, it alters the HTTP headers upon arrival, but other tools do that too (think proxy servers for one). Here's code to place in the global.asax.fs in F# if you are using WebSharper (a bit messy, but I'll leave cleaning up as an exercise for the reader ;)).

    member __.Application_BeginRequest(sender: obj, args: System.EventArgs) =
        HttpContext.Current
        |> function
        | null -> ()
        | ctx ->
            match ctx.Request with
            | null -> ()
            | req ->
                match req.Cookies.Item "csrftoken", req.Headers.Item "x-csrftoken" with
                | null, null -> ()
                | cookie, null ->
                    // fix for IE11, which does not always set the HTTP Header "x-csrftoken"
                    try req.Headers.Item "x-csrftoken" <- cookie.Value
                    with _ -> ()       // ignore possible errors
                | null, _ ->
                    // if header is set but cookie is not, there's nothing we can do (cookie collection is read-only)
                    ()
                | cookie, csrfHeader when cookie.Value <> csrfHeader ->
                    try req.Headers.Item "x-csrftoken" <- cookie.Value
                    with _ -> ()       // ignore possible errors
                | _ ->
                    ()      // all is fine, the default: cookie "csfrtoken" and header "x-csfrtoken" are equal