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
Build it step by step.
-
1Install the NuGet packages
SelectPdf.Universalis the library. The native package carries the Chromium engine for one platform: add the one for the platform you run on.dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.win-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-x64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.linux-arm64
dotnet add package SelectPdf.Universal dotnet add package SelectPdf.Universal.Native.osx-arm64
-
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.cswith the code below.dotnet new web -n PdfSample cd PdfSample
-
3Import the namespace
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 inSelectPdf.Universal. -
4A protected page to convert
// 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:
/reportanswers with the report only when the request carriesAuthorization: Bearer s3cret-token, and with "Please sign in" otherwise. Port 0 picks any free port;app.Urlsgives the address it got. In your application, convert your real URL and skip this step. -
5Send the Authorization header
// 2. Send the Authorization header with every request the converter makes. HtmlToPdf converter = new HtmlToPdf(); converter.Options.HttpHeaders.Add("Authorization", "Bearer s3cret-token");Options.HttpHeadersadds headers to the requests the converter makes. With the bearer token inAuthorization, the page sees the same request your API client or signed-in browser would send. -
6Convert the protected page
// 3. Convert the protected page and save it. PdfDocument doc = converter.ConvertUrl($"{site}/report"); doc.Save("report.pdf"); doc.Close();ConvertUrlloads the page with the header and renders it, soreport.pdfholds the sales report, not the sign-in page. -
7Stop the stand-in site
await app.StopAsync();
StopAsyncshuts the local site down when the conversion is done. -
8Run it
dotnet run
The request returns
report.pdf. Without a license key, every page carries a trial watermark; setGlobalProperties.LicenseKeybefore the first conversion to remove it.
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.
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
The PDF it produces.
Download report.pdf ↓ the file this sample produced, unedited
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.
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.
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.
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();
}
}
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.
- 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
Questions, answered.
Short answers about this guide. The SelectPdf.Universal documentation covers every class and option.