Sessions & Security

Document sessions and access control

A Doconut token identifies an open document. If any request carrying it were served, whoever obtained it could read every page. This page explains what a document session holds, how long it lives, and how DocImage.axd decides who may see its pages — on one server and in a web farm.

What a document session holds

Each successful OpenDocumentAsync creates one session, kept in memory:

  • the loaded format viewer — the engine instance holding the parsed document,
  • per-page state — rotation, flips, and the annotation data the user applies in the widget,
  • the optional search index, built on the first search (or loaded from a pre-built .srh file),
  • the session watermark from DocOptions.Watermark.

Lifetime

Sessions expire on a sliding window: DocOptions.TimeOut minutes (default 60), reset by every request that presents the token. When a session ends — by expiration or by CloseDocument(token) — its document engine is released and its memory freed immediately.

csharp
// A short-lived session for a one-shot preview
var token = await viewer.OpenDocumentAsync(path, new DocOptions { TimeOut = 10 });

A request with an expired token gets an error image reading Document session not found. Please re-open document. The page must open the document again to get a new token. On a single server, recycling the application pool ends every session too.

Pages are served only to the session that opened them

With UnsafeMode = false — the default — opening a document grants access to the ASP.NET session of the request that opened it, by writing a secure-{token} entry into that session. DocImage.axd requires session state itself, and serves a page only when the requesting session holds that entry. Any other browser presenting the same token gets an error image reading You Are Not Authorized To View This Page.

csharp
Doconut.DoconutHost.Initialize(options =>
{
    // false (the default): DocImage.axd serves a page only to the ASP.NET session that opened
    // the document. The handler asks for session state itself; it only needs the site not to
    // turn session state off in Web.config.
    options.UnsafeMode = false;
});

Three practical consequences:

  • The open endpoint needs session state. MVC controllers and Web Forms pages have it by default. A custom IHttpHandler does not unless it implements System.Web.SessionState.IRequiresSessionState. An open that runs without a session has nothing to grant, so the viewer shows "You Are Not Authorized To View This Page." on every page.
  • Session state must stay on. <sessionState mode="Off" /> in Web.config disables both the grant and the check, and every page is refused.
  • The browser must send the session cookie. The widget's requests are same-origin, so they carry ASP.NET_SessionId automatically. A client without a cookie jar — a script, a server-to-server call, a cross-origin setup that strips cookies — is refused. That is the feature working.

UnsafeMode = true switches the check off: any request carrying the token is served. It is one global switch for every document, with no per-document override. Leave it false.

Revocation

CloseDocument(token) does more than free memory: it removes the secure-{token} entry from the current request's session and revokes the token, so a closed token is refused at once.

Web farms: signed tickets

In a farm, the next request can land on a server that never saw the document, in a different worker process, with its own ASP.NET session. The session grant cannot follow it. Distributed mode replaces it with a signed ticket bound to the browser: every node shares one store for document sessions and one signing key, and DocImage.axd accepts a request when its ticket is valid for that token, that browser, and — when the user is signed in — that user.

The setup, identical on every node:

csharp
// Global.asax.cs - Application_Start
Doconut.DoconutHost.Initialize(
    options => options.UnsafeMode = false,     // the ticket check only runs when this is false
    services => services.AddDoconutDistributed(d =>
    {
        d.Store             = CloudLocation.FileShare;
        d.FileShareRootPath = @"\\fileserver\doconut-shared";  // a folder every node can reach
        d.SigningKey        = farmKey;                          // at least 32 bytes, the SAME on every node
    }));

The open endpoint then binds the ticket to the browser before opening, and returns the ticket with the token:

  • set DocOptions.BrowserId = DoconutFarmBinding.EnsureBrowserId(System.Web.HttpContext.Current, securityOptions) — it issues the doconut-client cookie the ticket is tied to — and DocOptions.UserId = DoconutFarmBinding.ResolveUserId(System.Web.HttpContext.Current);
  • after OpenDocumentAsync, return viewer.AccessToken alongside the token;
  • in the page, pass both to the widget: objViewer.View(token, access).

Doconut.MvcSample.WebFarm in the samples package is a complete, runnable farm node, and the migration guide beside it explains each step.

Keep the signing key out of source control: it both signs the tickets and encrypts the documents in the shared store. A node with a different key rejects every ticket the others issued.

Test a farm with a real browser, or a script that keeps cookies. A client without the doconut-client cookie is not answered with 401: it receives the "not authorized" error image with status 200, about 2 KB, where a real page is tens to hundreds of kilobytes.

Production checklist

  • Keep UnsafeMode = false (the default).
  • Open documents from requests that have session state, and never turn session state off.
  • Make sure the session cookie reaches DocImage.axd (same origin, HTTPS, a SameSite policy that allows it).
  • In a farm, use distributed mode with one signing key shared by every node.
  • Call CloseDocument when the user leaves a document — memory and security both benefit.
  • Never log or share tokens; treat them as short-lived credentials.

Var den här sidan hjälpsam?