Reference

Session guard

A valid cookie does not prove the owner is still using it.

Bind after login

Call from your backend after successful authentication. Use an opaque session identifier instead of the cookie itself. Keep your secret server-side. Only bind after authentication: binding again resets the trusted device and the binding time.

POST https://nolgard.com/v1/session
{"secret":"sk_…","event":"bind","sessionId":"sess_42","accountId":"user_42","token":"BROWSER_TOKEN","ip":"198.51.100.8","userAgent":"Mozilla/5.0 … Chrome/130.0.0.0 Safari/537.36","country":"US"}

{"decision":"allow","reasons":[],"boundAt":1792800000000}

Get the optional browser token with await Nolgard.execute({action:'login'}). Tokens must belong to the site and be unexpired. Session and account ids are nonempty strings up to 256 characters. Optional fields: token (32768 characters), ip (128), userAgent (2048), country (two-letter ISO code).

Check before sensitive actions

Check before billing changes, API-key creation, email or password changes, and exports. Read the account and session identifiers from your authenticated server session instead of untrusted form fields. Send a fresh browser token and the end user's User-Agent; your server's own HTTP-client User-Agent is the wrong value here.

POST /v1/session
{"secret":"sk_…","event":"check","sessionId":"sess_42","accountId":"user_42","token":"FRESH_BROWSER_TOKEN","ip":"203.0.113.4","userAgent":"curl/8.0","country":"US"}

{"decision":"revoke","reasons":["session_tool_ua","session_ua_change"],"boundAt":1792800000000}

Your backend must enforce the result: allow the action, require independent step-up for challenge, or invalidate the session for revoke. Nolgard cannot delete your application's cookie.

Reasons

ReasonConditionDecision
session_device_changeBoth known visitor and fingerprint keys differrevoke
session_tool_uaBrowser binding is now an HTTP library or headless toolrevoke
session_ua_changeBrowser family or OS differschallenge
session_country_jumpCountry differs from binding, previous check (or bind) less than two hours agochallenge
session_network_changeBoth IP prefix and known ASN differallow alone
session_unknownNo current binding for session and accountallow

The strongest decision wins. Unknown sessions return boundAt:null. Version upgrades in the same browser family and OS do not trigger a UA change. Missing observations do not count as changed observations.

Node SDK

const nf = createNolgard({ secret: process.env.NOLGARD_SECRET });
await nf.session.bind({ sessionId, accountId, token, ip, userAgent, country });
const result = await nf.session.check({ sessionId, accountId, token, ip, userAgent, country });
if (result.decision === 'revoke') return invalidateSession();
if (result.decision === 'challenge') return requireIndependentStepUp();

Both methods fail open on timeout, network failure, 429 or 5xx: {decision:'allow',reasons:[],boundAt:null,failOpen:true}. Invalid secrets and other 4xx errors throw.

Webhooks and retention

Challenge and revoke checks queue session.suspicious with {accountId,sessionPrefix,reasons,decision}. Only the first 12 characters of the session HMAC are included, never the raw session id. The site dashboard shows the latest ten suspicious checks with account hash, reasons, decision and time. Session bindings expire 30 days after last_seen; suspicious checks are retained 30 days. Scheduled pruning removes expired rows.

Invalid input or token returns 400; invalid secret 401; demo writes 403; more than 120 requests per minute per site 429. All four public test secrets return write-free allow results; check returns session_unknown, bind returns no reasons and null boundAt.

Limits

Rules and the two-hour window are logical defaults rather than measured thresholds. VPN toggles, corporate proxies and mobile roaming can change location or network. Network changes alone never challenge. Browser updates can change fingerprints; browser version alone is not a UA change. Device-key comparison needs browser tokens on both bind and check. ASN comes from authenticated browser-token metadata, never your backend's connection. Without a token, ASN is unknown. A missing token cannot prove device continuity. Attackers who steal browser identifiers too may evade this check. Session and account ids and IP prefixes are hashed at rest; raw IPs and User-Agent strings are not stored.