Convert a page behind a login to PDF in C#

The converter loads a URL with its own requests, so a page behind a login sees an anonymous visitor and returns the sign-in page. Give it what a signed-in browser would send: an Authorization header, the session cookie, HTTP credentials, or the login form to fill in.

Compiled and run: Linux ARM64 · Linux x64 · macOS ARM64 · Windows x64

Video walkthrough · 3:21 · Watch on YouTube · Short · 1:01
§STEP BY STEP

Build it step by step.

  1. 1Install the NuGet packages

    SelectPdf.Universal is the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.

    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.win-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-x64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.linux-arm64
    terminal
    dotnet add package SelectPdf.Universal
    dotnet add package SelectPdf.Universal.Native.osx-arm64
  2. 2Create a web app

    Any .NET 8 or .NET 10 project works. To follow along, create an empty ASP.NET Core app, run the install commands above inside its folder, then replace Program.cs with the code below.

    terminal
    dotnet new web -n PdfSample
    cd PdfSample
  3. 3Import the namespace

    Program.cs · lines 1–1
    using SelectPdf.Universal;

    The project is created with dotnet new web, which brings in ASP.NET Core for the stand-in site; the converter is in SelectPdf.Universal.

  4. 4A protected page to convert

    Program.cs · lines 3–12
    // 1. A stand-in for your site: a report page that needs a bearer token.
    // It runs on localhost so the sample works anywhere.
    WebApplication app = WebApplication.Create();
    app.Urls.Add("http://127.0.0.1:0"); // any free port
    app.MapGet("/report", (HttpRequest request) =>
        request.Headers.Authorization == "Bearer s3cret-token"
            ? Results.Content("<h1>Sales report</h1><p>Signed in: the converter sent the token.</p>", "text/html")
            : Results.Content("<h1>Please sign in</h1>", "text/html", statusCode: 401));
    await app.StartAsync();
    string site = app.Urls.First();

    A small ASP.NET Core app on localhost plays the part of your site: /report answers with the report only when the request carries Authorization: Bearer s3cret-token, and with "Please sign in" otherwise. Port 0 picks any free port; app.Urls gives the address it got. In your application, convert your real URL and skip this step.

  5. 5Send the Authorization header

    Program.cs · lines 14–16
    // 2. Send the Authorization header with every request the converter makes.
    HtmlToPdf converter = new HtmlToPdf();
    converter.Options.HttpHeaders.Add("Authorization", "Bearer s3cret-token");

    Options.HttpHeaders adds headers to the requests the converter makes. With the bearer token in Authorization, the page sees the same request your API client or signed-in browser would send.

  6. 6Convert the protected page

    Program.cs · lines 18–21
    // 3. Convert the protected page and save it.
    PdfDocument doc = converter.ConvertUrl($"{site}/report");
    doc.Save("report.pdf");
    doc.Close();

    ConvertUrl loads the page with the header and renders it, so report.pdf holds the sales report, not the sign-in page.

  7. 7Stop the stand-in site

    Program.cs · lines 23–23
    await app.StopAsync();

    StopAsync shuts the local site down when the conversion is done.

  8. 8Run it

    terminal
    dotnet run

    The request returns report.pdf. Without a license key, every page carries a trial watermark; set GlobalProperties.LicenseKey before the first conversion to remove it.

§COMPLETE PROGRAM

The complete Program.cs.

This is the exact file our samples pipeline compiled and ran on Linux ARM64 and Linux x64 and macOS ARM64 and Windows x64 to produce the output on this page.

Program.cs
using SelectPdf.Universal;

// 1. A stand-in for your site: a report page that needs a bearer token.
// It runs on localhost so the sample works anywhere.
WebApplication app = WebApplication.Create();
app.Urls.Add("http://127.0.0.1:0"); // any free port
app.MapGet("/report", (HttpRequest request) =>
    request.Headers.Authorization == "Bearer s3cret-token"
        ? Results.Content("<h1>Sales report</h1><p>Signed in: the converter sent the token.</p>", "text/html")
        : Results.Content("<h1>Please sign in</h1>", "text/html", statusCode: 401));
await app.StartAsync();
string site = app.Urls.First();

// 2. Send the Authorization header with every request the converter makes.
HtmlToPdf converter = new HtmlToPdf();
converter.Options.HttpHeaders.Add("Authorization", "Bearer s3cret-token");

// 3. Convert the protected page and save it.
PdfDocument doc = converter.ConvertUrl($"{site}/report");
doc.Save("report.pdf");
doc.Close();

await app.StopAsync();

Open the project on GitHub → this program and the variations below, ready to dotnet run

§OUTPUT

The PDF it produces.

First page of report.pdf, the PDF this sample produces
report.pdf, page 1

Download report.pdf ↓ the file this sample produced, unedited

§VARIATIONS

Variations you will need next.

Each variation is a separate file in the same project, compiled and run with the program above.

Send the session cookie of a signed-in user

Sites with cookie sign-in, such as ASP.NET Core cookie authentication, recognize the user by a cookie. Options.HttpCookies.Add(name, value) sends it with the conversion. In a web app, read the cookie from the current request, for example Request.Cookies[".AspNetCore.Cookies"], so the PDF shows what that user sees.

CookieAuth.cs
using SelectPdf.Universal;

static class CookieAuth
{
    public static async Task Run()
    {
        // a page that needs the session cookie of a signed-in user
        WebApplication app = WebApplication.Create();
        app.Urls.Add("http://127.0.0.1:0");
        app.MapGet("/orders", (HttpRequest request) =>
            request.Cookies["session"] == "user-42-session"
                ? Results.Content("<h1>My orders</h1><p>Signed in with the session cookie.</p>", "text/html")
                : Results.Redirect("/login"));
        await app.StartAsync();

        // in a web app, read the cookie from the current request:
        // Request.Cookies[".AspNetCore.Cookies"]
        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.HttpCookies.Add("session", "user-42-session");
        PdfDocument doc = converter.ConvertUrl($"{app.Urls.First()}/orders");
        doc.Save("orders.pdf");
        doc.Close();

        await app.StopAsync();
    }
}

HTTP Basic authentication

When the server asks for HTTP credentials (the browser's login dialog), Options.Authentication.Username and Password answer the challenge. The same options supply credentials for Integrated Windows Authentication when the current user's are not enough.

BasicAuth.cs
using System.Text;
using SelectPdf.Universal;

static class BasicAuth
{
    public static async Task Run()
    {
        // a page behind HTTP Basic authentication
        string expected = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("reports:pa55word"));
        WebApplication app = WebApplication.Create();
        app.Urls.Add("http://127.0.0.1:0");
        app.MapGet("/status", (HttpRequest request, HttpResponse response) =>
        {
            if (request.Headers.Authorization == expected)
                return Results.Content("<h1>System status</h1><p>Signed in with HTTP authentication.</p>", "text/html");
            response.Headers.WWWAuthenticate = "Basic realm=\"reports\"";
            return Results.Unauthorized();
        });
        await app.StartAsync();

        // the converter answers the login challenge with these credentials
        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.Authentication.Username = "reports";
        converter.Options.Authentication.Password = "pa55word";
        PdfDocument doc = converter.ConvertUrl($"{app.Urls.First()}/status");
        doc.Save("status.pdf");
        doc.Close();

        await app.StopAsync();
    }
}

Sign in through the site's login form

Options.LoginOptions opens LoginPage, types the user name and password into the fields matched by the CSS selectors, clicks the submit button and then converts the URL, with the cookie the login set. DelayBefore and DelayAfter (milliseconds) give slow login pages time to load and to redirect.

LoginForm.cs
using SelectPdf.Universal;

static class LoginForm
{
    public static async Task Run()
    {
        // a site with its own login form; the dashboard needs the cookie it sets
        WebApplication app = WebApplication.Create();
        app.Urls.Add("http://127.0.0.1:0");
        app.MapGet("/login", () => Results.Content("""
            <form method="post" action="/login">
              <input id="user" name="user"> <input id="pass" name="pass" type="password">
              <button id="sign-in" type="submit">Sign in</button>
            </form>
            """, "text/html"));
        app.MapPost("/login", (HttpContext context) =>
        {
            IFormCollection form = context.Request.Form;
            if (form["user"] != "anna" || form["pass"] != "pa55word") return Results.Redirect("/login");
            context.Response.Cookies.Append("session", "anna-session");
            return Results.Redirect("/dashboard");
        });
        app.MapGet("/dashboard", (HttpRequest request) =>
            request.Cookies["session"] == "anna-session"
                ? Results.Content("<h1>Dashboard</h1><p>Signed in through the login form.</p>", "text/html")
                : Results.Redirect("/login"));
        await app.StartAsync();
        string site = app.Urls.First();

        // the converter opens the login page, fills in the form and submits it first
        HtmlToPdf converter = new HtmlToPdf();
        converter.Options.LoginOptions.LoginPage = $"{site}/login";
        converter.Options.LoginOptions.UsernameFieldSelector = "#user";
        converter.Options.LoginOptions.PasswordFieldSelector = "#pass";
        converter.Options.LoginOptions.SubmitButtonSelector = "#sign-in";
        converter.Options.LoginOptions.Username = "anna";
        converter.Options.LoginOptions.Password = "pa55word";
        PdfDocument doc = converter.ConvertUrl($"{site}/dashboard");
        doc.Save("dashboard.pdf");
        doc.Close();

        await app.StopAsync();
    }
}
§TROUBLESHOOTING

Common problems and fixes.

The PDF shows the login page

The converter's request was anonymous. Check that the header or cookie name and value are exactly what the site expects (cookie names are case-sensitive) and that the token or session has not expired.

Images or styles are missing behind the login

Stylesheets, scripts and images the page loads from another site that needs its own sign-in may not load. Serve them from the converted site or from a public location.

Converting pages for different users

The converter keeps its HTTP cache between conversions by default, and the cache is keyed by address, not by user. Set converter.Options.HttpCache = HttpCacheMode.Clear so one user's response is never reused for another.

The login form needs JavaScript or extra steps

Raise LoginOptions.DelayBefore and DelayAfter. If the login has steps a form fill cannot do, such as two-factor authentication, send a session cookie or token instead, or have the application render the page to HTML and convert that with ConvertHtmlString.

§GOOD TO KNOW

Good to know.

  • HtmlToPdfOptions.HttpHeaders sends additional HTTP headers to the page that is converted. Source
  • HtmlToPdfOptions.HttpCookies sends cookies; the converter issues its own request, so it is anonymous unless the authentication cookie is passed to it. Source
  • For HTTP authentication, set Options.Authentication.Username and Password; the converter uses them to log in and access the page. Source
  • LoginOptions gets past a login page given its URL, the CSS selectors of the user name and password fields and of the submit button, and the credentials. Source
  • The converter keeps its HTTP cache between conversions by default, keyed by address; for pages converted on behalf of different users, set HttpCache to HttpCacheMode.Clear. Source
?FAQ

Questions, answered.

Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.

Add it to converter.Options.HttpHeaders before ConvertUrl, for example converter.Options.HttpHeaders.Add("Authorization", "Bearer <token>"). The converter sends it with its requests to the page.
Read the authentication cookie from the current request (.AspNetCore.Cookies by default) and add it with converter.Options.HttpCookies.Add(name, value) before converting the URL.
Yes. Set converter.Options.Authentication.Username and Password. For Integrated Windows Authentication the current user's credentials are used automatically, and these options supply others when needed.
Yes. LoginOptions takes the login page URL, CSS selectors for the user name field, the password field and the submit button, and the credentials; the converter signs in and then converts the page.