Sessions & Security
Document sessions and access control
A Doconut token is powerful: whoever presents it could request every page of the document if it were not bound to the opening session. This page explains what a session holds, how long it lives, and the checks UseDoconut() enables by default.
What a document session holds
Each successful OpenDocumentAsync creates one session in IMemoryCache:
- the loaded format viewer (the document engine instance holding the parsed document),
- per-page state — rotation, flips, and annotation data the user applies in the widget,
- the optional search index, built lazily on the first search (or loaded from a pre-built
.srhfile when pages are served from shared storage), - 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 is evicted — by expiration or by CloseDocument(token) — its eviction callback disposes the document engine and frees the associated memory immediately.
// 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 client must re-open to obtain a fresh token.
Built-in token binding
With UnsafeMode = false (the default), OpenDocumentAsync binds the new token to the ASP.NET session of the HTTP request that opened it, by writing a secure-{token} marker into that session. The Doconut middleware then refuses to serve pages to any other browser session:
- Different browser/session presenting a stolen token → error image
You Are Not Authorized To View This Page. - Session middleware not registered → HTTP 500
Session middleware not configured. Call UseSession() before UseDoconut().
This is why the Quick Start insists on AddSession() + app.UseSession() before the Doconut branch. Two practical consequences:
- The client must send the ASP.NET session cookie with page requests. Cross-origin setups that strip cookies (or an API client with no cookie jar) will fail the check — that is the feature working, not a bug.
options.UnsafeMode = truedisables the binding entirely. It exists for controlled scenarios; leave itfalsein production on a single node.
Note the spelling. The previous .NET Standard library called this property UnSafeMode
and it was passed to the middleware; here it is UnsafeMode and it is set once inside
AddDoconut().
Token binding is controlled solely by this global UnsafeMode switch — it is on by default (UnsafeMode = false) and applies to every session. There is no per-document opt-out; setting UnsafeMode = true disables the binding globally.
Access grants and authenticated users
When UnsafeMode is false, UseDoconut() inserts the document-access middleware automatically before the page middleware. Do not register it a second time. When a request carries a token, it looks up the access grant recorded when the document was opened and authorizes only if all of these hold:
- a grant exists for the token,
- it has not expired (grant lifetime = the document's
TimeOut), - the requesting ASP.NET session ID matches the one that opened the document,
- if the opener was authenticated, the requesting user's
NameIdentifierclaim matches too.
Failures return 403 — as a PNG error image for page/thumbnail requests, as plain text otherwise. The message and token query key come from DocumentSecurityOptions (TokenQueryKey, default "token"; UnauthorizedMessage, default "You Are Not Authorized To View This Page."). Configure those options through ASP.NET Core DI before building the app. If session state is unavailable the middleware fails closed with HTTP 500: ASP.NET Session is required for Doconut document security.
builder.Services.Configure<Doconut.Security.DocumentSecurityOptions>(options =>
{
options.TokenQueryKey = "token";
options.UnauthorizedMessage = "You Are Not Authorized To View This Page.";
});The core page middleware then verifies the secure-{token} session marker before it serves the document. With UnsafeMode = true, UseDoconut() skips the access middleware and the core marker check is also disabled.
Security across several nodes
Session-bound tokens assume that the node serving a page also saw the request that opened the document. That assumption does not hold when several nodes serve the same documents from shared storage: a page request can land on a node that never saw the open, and the per-session check rejects it.
The answer is not to leave documents unprotected. Use signed access tickets through
AddDoconutDistributedDocumentSecurity(...), which replaces the session check with a
credential every node can verify independently. The full arrangement is on
Distributed Deployments.
Revocation
CloseDocument(token) doesn't just free memory — it also removes the secure-{token} marker and revokes the access grant, so a closed token is dead on both security layers immediately.
Checklist for production
- Keep
UnsafeMode = false(the default) on a single node — this global switch is what binds tokens to sessions. - Register
AddSession()and callapp.UseSession()before the Doconut middleware branch. - Make sure your session cookie policy lets the widget's requests carry the cookie (
SameSite, HTTPS). - Use
CloseDocumentwhen the user leaves the document — memory and security both benefit. - Never log or share tokens; treat them as short-lived credentials.
- Serving from shared storage? Move to signed access tickets rather than accepting unprotected documents.
หน้านี้เป็นประโยชน์หรือไม่?