Welcome Guest Search | Active Topics | Sign In | Register

HtmlToPdf.ConvertUrl using Windows Authentication - 401 error Options
Zach Essary
Posted: Thursday, May 16, 2019 1:34:52 PM
Rank: Newbie
Groups: Member

Joined: 1/3/2017
Posts: 2
I've seen many posts about this issue while researching but never saw a great solution. So, I'm posting mine here in hopes that it helps.

In short, I have an application making a request to another application to generate a PDF from a URL. Once I upgraded to a more recent version of EO.Pdf, this process resulted in a PDF of a 401.2 error. That’s because we use Windows authentication on this site. EO.Pdf’s underlying webrequest no longer has “usedefaultcredentials” set to true. In fact, it does not appear to actually set credentials at all.

The standard transaction for this type of authentication is: request from a client, 401.2 response from server with headers identifying acceptable authentication options, a request from client with a proper authentication header. By not setting credentials, the underlying webrequest simply stops at the 401.2. So EO.Pdf can either render that response as PDF (oddly the default) or throw an error because it did not get a 2xx response code (I think that’s their logic, but I wouldn’t know exactly how they have that set up). What’s even more frustrating, the exception thrown by EO.Pdf doesn't include a clean way to get the actual non-2xx response code for handling.

I used a few tools on my web server to watch the requests/responses and have verified that EO.Pdf never follows up to the 401.2 response. It never attempts to send an authentication header at all. It’s unfortunate, because the webRequest object in .NET is designed to abstract all of that lower level authentication headache from the developer. Oh well, I decided to jump in and make my own authentication header.

The potential flaw with this, which I believe to be inherent in the way EO.Pdf has abstracted the underlying request and response, is that you can’t reliably/cleanly detect a 401.2 error (step 1 response in a normal interaction). That means you may need to create two methods. One that makes your request assuming Windows authentication and another that assumes no authentication.

Code: C#
private static string GetNegotiateAuthHeader(string targetUrl, int tokenTimeoutMinutes = 1)
{
    // get uri object for targetUrl to resolve both host and absoluteUri
    var requestUri = new Uri(targetUrl);
    // this is the standard location to store SPN. If this one is not in there, set it.
    // If you instantiate a webrequest and properly set the credentials (usedefaultcredential = true, for example), then perform the request,
    //  the webrequest object will automatically retry after the intial 401.2 response and set this same information for use in subsequent requests.
    // We are just following that model since we cannot control the behavior of EO.Pdf's underlying webrequest objects.
    if (!AuthenticationManager.CustomTargetNameDictionary.ContainsKey(requestUri.AbsoluteUri))
    {
        // dns lookup to get the actual hostName from the url.
        var hostentry = Dns.GetHostEntry(requestUri.Host);
        // add it here. MSDN says the key should be the absoluteUri, so we're sticking to their recommendation.
        AuthenticationManager.CustomTargetNameDictionary.Add(requestUri.AbsoluteUri, $"HTTP/{hostentry.HostName}");
    }

    // instantiate tokenProvider using SPN
    var provider = new KerberosSecurityTokenProvider(AuthenticationManager.CustomTargetNameDictionary[requestUri.AbsoluteUri]);
    // the actual token to be used
    KerberosRequestorSecurityToken token = (KerberosRequestorSecurityToken)provider.GetToken(new TimeSpan(0, tokenTimeoutMinutes, 0));
    // base64 encoding of the value that can be used in the header of the http request
    string initialContextToken = Convert.ToBase64String(token.GetRequest());

    // properly formatted http Authorization header value for kerberose authentication.
    return $"Authorization: Negotiate {initialContextToken}";
}


That method could be used with EO.Pdf as follows:

Code: C#
public static void EoTesting(string url, string pdfPath)
{
    var options = new HtmlToPdfOptions() { AdditionalHeaders = new string[] { GetNegotiateAuthHeader(url) } };
    HtmlToPdf.ConvertUrl(url, pdfPath, options);
}
eo_support
Posted: Friday, May 17, 2019 10:41:34 AM
Rank: Administration
Groups: Administration

Joined: 5/27/2007
Posts: 24,067
Thank you very much for sharing your insight and solution, your contribution is much appreciated. While EO.Pdf runs a browser engine inside, we rely on the browser engine for all network related communications and our main focus has been on the PDF layer. Your analysis provided a much in depth view on this particular issue and is very helpful both for other customers and for us in the future if we choose to address this issue on our side.


You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.