10

I maintain an ASP.NET MVC website that uses

FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);

to sign users in (they end up with a cookie named .ASPXAUTH).

The client wants me to add an HTML to PDF feature, so I'm wrapping the wkhtmltopdf library and calling that. This ends up being a command that looks like this:

wkhtmltopdf http://example.com/Foo/Edit/42 Foo.pdf

However, that results in making a PDF of the login screen, as the wkhtmltopdf user agent is redirected since it doesn't have the correct cookie.

That's fine since, according to the wkhtmltopdf documentation, there's an argument like this:

--cookie <name> <value>         Set an additional cookie (repeatable)

So I modify the command to be:

wkhtmltopdf --cookie .ASPXAUTH 91C0DE4C...  http://example.com/Foo/Edit/42 Foo.pdf

Where the cookie value is retrieved using Request.Cookie[".ASPXAUTH"].Value.

Unfortunately, this doesn't seem to work and I have no idea why. I know that ASP.NET is receiving the cookie because when I breakpoint the login page after the redirect, I can see that it's been set. So why does ASP.NET not accept my copied cookie?

Here's the contents of a request that ASP.NET allows (from Chrome):

GET http://localhost:50189/ReportingMonth/Edit/1193391 HTTP/1.1
Host: localhost:50189
Connection: keep-alive
Cache-Control: max-age=0
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-CA,en;q=0.8,en-US;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: .ASPXAUTH=C8189041BF69FEF89A834B6F5035B786EC40145FFFBA3DBB6A04973BC58021C73D8D374E3577AA44BC26A784BC8A0C24831CF49FBD596BFFBA42C613E3C2C0C893D1587B7743D051643088BB8BAB667C047E0D1B84D7B76C4AADA7C62AB460D87C954BF9118BF5945E7D325D455CFD13A34C3DD5E597AFDF75D3C8EE76D8488B08ABBF6AE065B4C57CE47CB65AB17D65; language=en; ui-tabs-[object Object]=0

And here's one that it redirects to the login (from wkhtmltopdf):

GET http://localhost:50189/ReportingMonth/Edit/1193391 HTTP/1.1
Cookie: .ASPXAUTH=C8189041BF69FEF89A834B6F5035B786EC40145FFFBA3DBB6A04973BC58021C73D8D374E3577AA44BC26A784BC8A0C24831CF49FBD596BFFBA42C613E3C2C0C893D1587B7743D051643088BB8BAB667C047E0D1B84D7B76C4AADA7C62AB460D87C954BF9118BF5945E7D325D455CFD13A34C3DD5E597AFDF75D3C8EE76D8488B08ABBF6AE065B4C57CE47CB65AB17D65
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.3 (KHTML, like Gecko) Qt/4.7.1 Safari/533.3
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Connection: Keep-Alive
Accept-Encoding: gzip
Accept-Language: en-US,*
Host: localhost:50189
jdphenix
  • 15,022
  • 3
  • 41
  • 74
cdmckay
  • 31,832
  • 25
  • 83
  • 114

2 Answers2

16

I found the problem. I noticed that once I changed the User-Agent field (in Fiddler) to be the same as Chrome, it worked fine. So I did a little web sleuthing and discovered this bug on the wkhtmltopdf project page.

From the bug:

This is an issue under ASP .NET 4.0 as it seems that .NET interprets the User-Agent string "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-AU) AppleWebKit/532.4 (KHTML, like Gecko) Qt/4.6.1 Safari/532.4" as not supporting cookies which I think is preventing the --cookie option from working under ASP.

So it looks like the solution is either figuring out a way to make wkhtmltopdf change it's User-Agent header (not looking promising) or figure out a way to tell ASP.NET that that user agent does support cookies.

Thanks for you help Darin Dimitrov.

Update

Ok, I figured out how to tell ASP.NET that the Qt web browser used by wkhtmltopdf supports cookies. You need to create a file called qt.browser and save it in a directory callde App_Browsers in the root of your ASP.NET project. Here's what you put in the qt.browser file:

<browsers>
    <!-- Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/530.1 (KHTML, like Gecko) Qt/4.7.1 Safari/530.1 -->
    <browser id="Qt" parentID="Safari">
        <identification>
            <userAgent match="Qt/(?'version'(?'major'\d+)(\.(?'minor'\d+)?)\w*)" />
        </identification>       

        <capabilities>
            <capability name="browser"                         value="Qt" />
            <capability name="version"                         value="${version}" />
            <capability name="majorversion"                    value="${major}" />
            <capability name="minorversion"                    value="${minor}" />
            <capability name="type"                            value="Qt${major}" />
            <capability name="ecmascriptversion"               value="3.0" />
            <capability name="javascript"                      value="true" />
            <capability name="javascriptversion"               value="1.7" />
            <capability name="w3cdomversion"                   value="1.0" />
            <capability name="tagwriter"                       value="System.Web.UI.HtmlTextWriter" />
            <capability name="cookies"                         value="true" />
            <capability name="frames"                          value="true" />
            <capability name="javaapplets"                     value="true" />
            <capability name="supportsAccesskeyAttribute"      value="true" />
            <capability name="supportsCallback"                value="true" />
            <capability name="supportsDivNoWrap"               value="false" />
            <capability name="supportsFileUpload"              value="true" />
            <capability name="supportsMaintainScrollPositionOnPostback" value="true" />
            <capability name="supportsMultilineTextBoxDisplay" value="true" />
            <capability name="supportsXmlHttp"                 value="true" />
            <capability name="tables"                          value="true" />
        </capabilities>
    </browser>
</browsers>

Then re-compile your project (and maybe restart your server if you can) and then presto, you can emulate the ASP.NET authentication cookie!

cdmckay
  • 31,832
  • 25
  • 83
  • 114
  • 4
    The browser crap in ASP.NET sickens me, don't decide stuff for a browser. It should serve everything exactly as coded to the browser and let the browser choke on it if that browser is too stupid, and I haven't spent the effort to fix the problems in the browser client side. – Chris Marisic Sep 21 '11 at 19:17
2

Looks like a bug and there seems to be a fix in the trunk.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • I saw that bug report but that's from Jan 2010 and for version 0.9.0. There have been several releases since then that have contained the fix, including 0.10.0, the current version. Furthermore, I mentioned that I can clearly see the cookie being set, it's just not being accepted by ASP.NET for whatever reason. – cdmckay Sep 21 '11 at 06:14
  • @cdmckay, did you verify that the trunk was patched? The status says *Fixed* but looking at the problem you are describing here it looks strangely close to this bug report. – Darin Dimitrov Sep 21 '11 at 06:14
  • Yes. I also tried the `--custom-header Cookie .ASPX=91C0DE4C...` workaround just in case. Same problem. – cdmckay Sep 21 '11 at 06:16
  • I think there is something more that ASP.NET checks but I'm not sure what it is. – cdmckay Sep 21 '11 at 06:16
  • @cdmckay, no there isn't. The cookie is the only thing that the Forms Authentication module of ASP.NET checks by default. Of course if you have added additional checks on the top of it that's another thing. Try using Fiddler to look at what's sent on the wire and see if the cookie is being sent. Another possibility is to temporarily disable authentication for this action and put a breakpoint inside and inspect the Request.Cookies to see if the Auth cookie is present. If it isn't then there's a problem. – Darin Dimitrov Sep 21 '11 at 06:17
  • I disabled the authentication and then breakpointed the method. The cookie is definitely being set. – cdmckay Sep 21 '11 at 06:32
  • @cdmckay, hmm, things are getting weirder. Fiddler? Ah by the way, any chance you might be using anti forgery tokens on this action? – Darin Dimitrov Sep 21 '11 at 06:36
  • I don't think so, unless they're on by default. I've watched the value of the .ASPXAUTH cookie between requests and it doesn't seem to change. – cdmckay Sep 21 '11 at 06:39
  • @cdmckay, try capturing the HTTP request sent from `wkhtmltopdf` and compare it to a normal request to see what might be different that makes it to fail. I have no more clues. It looks like ASP.NET is unable to parse the authentication cookie that is sent (wrong name, value, expired?). – Darin Dimitrov Sep 21 '11 at 06:40
  • Ok, I'll use Fiddler and compare the requests to see if I'm missing anything else. It's late here, so I'll have to try tomorrow. Thanks for you help. – cdmckay Sep 21 '11 at 06:43
  • I've added an example of each request using Fiddler. – cdmckay Sep 21 '11 at 15:15