WEB / WEB-13.03
Session lifecycle, login and logout
Follow the Browser cookie jar and server session state in one story to locate login rotation, expiry and logout decisions.
1. Core · Mechanism
Read every scenario without motion
Login rotation
- Anonymous login request is ready
The Browser carries anonymous handle alias sid-a and prepares a login request. No new identity or generation exists yet; no raw credential is shown.
- Browser · cookie jar
- Cookie: sid-a alias
- Application · acceptance
- Login request not received
- Same process · session store
- ledger-a anonymous
- Login request reached the Application
The login request reached the Application. A successful password/MFA outcome is the supplied precondition for the next local transition; no real verifier runs here.
- Browser · cookie jar
- Awaiting response
- Application · acceptance
- Login request received; check pending
- Same process · session store
- ledger-a anonymous
- The server created a new generation
At t=10, the supplied Duru/Atlas identity result makes ledger-b active and invalidates ledger-a. Application and store are logical components in one process; the Browser still holds sid-a.
- Browser · cookie jar
- Cookie still sid-a
- Application · acceptance
- Duru/Atlas; new Set-Cookie can be prepared
- Same process · session store
- ledger-a invalidated; ledger-b active
- The new Cookie response is pending
At response_pending / not_arrived, the server knows the new generation. The prepared __Host-hd_session response has not reached the Browser, so its jar is unchanged.
- Browser · cookie jar
- Cookie still sid-a
- Application · acceptance
- Set-Cookie prepared; no receipt
- Same process · session store
- ledger-a invalidated; ledger-b active
- Cookie arrival completed
Under this selected permitting Browser policy, cookie_arrived / stored completed. The Browser stores alias sid-b; server rotation completed earlier.
- Browser · cookie jar
- Cookie: sid-b alias
- Application · acceptance
- Login response sent
- Same process · session store
- ledger-b active; createdAt_s=10
- Transport and acceptance are separate
sid-b exists in the jar, but a later protected request still needs generation, idle/absolute lifetime and separate resource-policy evaluation. Cookie flags do not automatically pass these decisions.
- Browser · cookie jar
- sid-b present; later request decision separate
- Application · acceptance
- Resource policy separately required
- Same process · session store
- ledger-b active; absoluteDeadline_s=1810
Idle boundary
- Last accepted activity was t=70
The scene starts after ledger seq=5: session_check accepted and lastSeen_s=70 are supplied. The Browser holds sid-b; the absolute deadline is 1810 and idle timeout is 300.
- Browser · cookie jar
- Cookie: sid-b alias
- Application · acceptance
- t=70 accepted check supplied
- Same process · session store
- ledger-b active; lastSeen_s=70; absolute=1810
- The next request is ready at t=370
The teaching clock selects now=370 and the Browser prepares a protected check. Clock selection is local; preparing a request does not refresh lastSeen.
- Browser · cookie jar
- sid-b present; now=370
- Application · acceptance
- New check not yet received
- Same process · session store
- ledger-b active; lastSeen_s=70; absolute=1810
- The protected check reached the Application
The sid-b check reached the Application; the Browser awaits the outcome. A handle present in the jar does not establish that server lifetime policy passes.
- Browser · cookie jar
- Awaiting response
- Application · acceptance
- now=370; lifetime check pending
- Same process · session store
- ledger-b active; lastSeen_s=70; absolute=1810
- Idle equality causes rejection
370−70=300 exactly equals the idle timeout; now >= deadline yields idle_expired. Since 370 < 1810, the reason is not absolute expiry. lastSeen_s remains 70.
- Browser · cookie jar
- Awaiting response
- Application · acceptance
- REJECT: idle_expired; response can be prepared
- Same process · session store
- ledger-b expired; lastSeen_s=70
- The rejection response is prepared
The Application prepares the rejected session_check outcome. This check is not accepted activity and does not advance the deadline; authenticated processing and protected body are withheld.
- Browser · cookie jar
- Awaiting response
- Application · acceptance
- Authn-required response prepared
- Same process · session store
- ledger-b expired; lastSeen_s=70
- The Browser learned the rejection
The rejection reached the Browser. Cookie retention may not have ended; server idle expiry does not magically clear the jar. A new authenticated session requires independent valid authentication.
- Browser · cookie jar
- sid-b present; session rejected
- Application · acceptance
- Authn-required response prepared
- Same process · session store
- ledger-b expired; lastSeen_s=70
Current logout
- Active starting state for logout
This alternative branch starts from the accepted seq=5 state at t=70, not after expiry. The Browser holds sid-b and the server generation is active.
- Browser · cookie jar
- Cookie: sid-b alias
- Application · acceptance
- t=70 accepted starting state
- Same process · session store
- ledger-b active; lastSeen_s=70; absolute=1810
- Logout intent is ready at t=90
Duru selects logout and the Browser prepares the t=90 request. The intent has not reached the server; the store remains active and the cookie jar is unchanged.
- Browser · cookie jar
- Cookie: sid-b alias
- Application · acceptance
- Logout not yet received
- Same process · session store
- ledger-b active; lastSeen_s=70; absolute=1810
- The logout request reached the Application
The logout request reached the Application. The Browser waits for the clearing response; store revocation is the next local transition, and the cookie has not been cleared.
- Browser · cookie jar
- Cookie: sid-b alias
- Application · acceptance
- Logout received; revocation pending
- Same process · session store
- ledger-b active; lastSeen_s=70; absolute=1810
- The current generation is revoked
Seq=6 session_revoked / user_logout ends ledger-b. sid-b can remain in the Browser; server revocation does not wait for cookie cleanup. This is a local same-process store transition.
- Browser · cookie jar
- Cookie: sid-b alias
- Application · acceptance
- Max-Age=0 can be prepared
- Same process · session store
- ledger-b revoked
- The cookie-clearing response is pending
Seq=7 response_pending / not_arrived prepares Max-Age=0 for the matching name and Path=/. Even if this response is lost, the revoked generation is not accepted on a later protected check.
- Browser · cookie jar
- Cookie: sid-b alias
- Application · acceptance
- Clearing response ready; no receipt
- Same process · session store
- ledger-b revoked; acceptance closed
- Cookie cleanup completed in the Browser
Seq=8 cookie_cleared / removed clears the matching cookie in the permitting Browser. This signs out the current generation; it adds no outcome for all devices, access tokens or an IdP session.
- Browser · cookie jar
- Cookie cleared
- Application · acceptance
- Clearing response sent
- Same process · session store
- ledger-b revoked; acceptance closed
ACTORS · STATE · DATA FLOW
The cookie carries a handle; the server owns the session decision
After signing in to the Atlas library, Duru opens another page. The new HTTP request does not remember the earlier login. The Browser includes an eligible cookie, and the Application checks its identifier against the Session store. Our example uses an opaque handle: the server record supplies its identity, authentication state and time limits. Possessing a cookie is not proof of an accepted session.
Track three separate areas. The Browser cookie jar holds the identifier. The Application decides whether to accept each protected request. The Session store holds the record and its lifecycle state. A store can be memory inside the same process or shared persistence; drawing it separately does not imply a separate network service.
Prerequisites: WEB-12.01/12.02 explain cookie anatomy; WEB-13.01/13.02 explain identifiers and stores. The short primer is that Set-Cookie is a response header and Cookie carries name/value pairs on a later request. Cookie attributes are not repeated in that request, so the Application cannot read security guarantees from the Cookie header.
Login does not reach both sides at once
Initially, the Browser holds an anonymous session shown as sid-a. At t=10, when Duru has been authenticated, the Application creates a new authenticated generation shown as sid-b. It does not promote the anonymous identifier into the authenticated identity. sid-a and sid-b are instructional aliases, not usable credentials or a production identifier format.
In the model, the server updates its record before the Set-Cookie response travels back. Before arrival, the store knows the new generation while the Browser still holds the old alias. The Browser accepts the cookie at the arrival step. This intermediate state matters: a connection failure can leave the server transition complete without delivering the replacement identifier to the client.
The example invalidates the previous generation immediately. Concurrent requests require an explicitly designed transition; inspect the framework’s migration and invalidation contract. Acceptance of a pre-authentication identifier and acceptance after a privilege change are separate questions.
A protected request: lookup, time checks, then resource policy
At t=70, Duru opens a book record. Browser cookie-inclusion rules carry the eligible identifier to the Application. The Application looks up the store record and checks that its generation is active and its deadlines have not passed. A successful check sets lastSeen=70 in this example. Which requests count as activity is application policy; background polling need not count as human activity.
An accepted session supplies an authenticated subject. Resource authorization remains a separate decision. Resolving Duru’s identity does not grant access to another tenant’s book. WEB-14.05 explains subject/action/object/context evaluation. This model stops protected processing after a rejected session and retains resource policy after acceptance.
A missing record, revoked generation or passed deadline cannot be overridden by a green “logged in” UI. The example asks the user to sign in again. HTTP status and redirect behavior depend on the endpoint contract; expiry does not have one universal response code.
Three clocks describe different things
Our policy uses a 300-second idle timeout and a 1800-second absolute timeout. These are teaching values, not recommended settings for every product. The new session begins at t=10, giving an absolute deadline of 1810. After the accepted action at t=70, its idle deadline is 370. At t=370, now−lastSeen >= 300 causes rejection; this request cannot rescue itself by advancing lastSeen.
Idle time follows the most recent accepted activity. Absolute time follows the session’s start and does not move with activity. Identifier rotation is not automatically reauthentication or an unlimited lifetime: restarting deadlines is a separate policy decision.
Cookie Max-Age=1800 describes how long the Browser retains the handle. The server may reject it for idle expiry while the cookie remains in the jar. Early cookie removal does not delete the server record. Max-Age takes precedence over Expires when both exist; Browser session restore also matters for a cookie without either lifetime attribute.
Logout combines server invalidation and cookie cleanup
The logout branch starts from the accepted session at t=70. Duru selects logout at t=90. The Application marks the current generation revoked, then sends Max-Age=0 for the same cookie name, Path and Domain scope, if one exists. Server invalidation, response receipt and cookie-jar cleanup are separate transitions.
If the response is lost, the Browser may retain the old cookie, but the server rejects its revoked record. Deleting only the Browser cookie creates the opposite situation: the client has dropped its handle without proving invalidation of server state. A logout banner or a closed tab also does not prove server invalidation.
This branch closes only the current session generation. Signing out all devices, changing a password or signing out from a federated identity provider can require a wider policy and propagation. With multiple nodes, when invalidation reaches each store or cache read is part of the logout guarantee.
Cookie flags protect different boundaries
The synthetic HTTPS example uses __Host-hd_session; Path=/; Secure; HttpOnly; SameSite=Lax without Domain. A Browser supporting the __Host- rule requires Secure, host-only scope and Path=/. The prefix is not a compatibility guarantee for every Browser version.
Secure affects transport, HttpOnly affects script access to the cookie, and SameSite affects cross-site inclusion. None makes a revoked store record active or grants resource permission. Path filters transmission; it is not dependable security isolation between applications on the same host.
An HttpOnly value is unavailable through document.cookie, but eligible JavaScript-initiated requests can still include the cookie. Reading Set-Cookie from a frontend fetch response is also not the expected observation method. DevTools cookie/network inspection and application logging expose different evidence.
Self-contained tokens change the logout question
With an opaque handle, the Application ties each acceptance decision to server state. If a self-contained-token verifier independently checks a signature and expected issuer/audience/time conditions, deleting the token in the Browser does not send that verifier new revocation information. Cryptographic validity and current permission to use a token are different questions.
When a JWT has exp, it cannot be accepted at or after that time; clock-skew tolerance and required claims depend on its profile. “JWT is stateless, so logout is impossible” is also too broad. Revocation state, a session version, introspection or a short access-token lifetime can form part of a design, with state, communication and delay costs.
Revoking a refresh token can prevent new access tokens from being issued; invalidation of existing access tokens across resource servers needs its own evidence. Refresh-token rotation is distinct from the Browser session-identifier rotation taught here. WEB-14.03/14.S03 and WEB-15 identity lessons develop token validation and delegation.
2. Red · Trust & Impact
Read every scenario without motion
Idle boundary
- A cookie is not an acceptance decision
The browser still holds teaching alias sid-b. The server generation is already at its termination boundary.
- Browser cookie jar
- sid-b present
- Acceptance gate
- No check
- Session store
- ledger-b: expired
- Protected resource
- Protected data withheld
- The check reached the application
An ordinary protected check with alias sid-b reached the application. Cookie flags or browser retention do not remove server validity checks.
- Browser cookie jar
- Waiting for response
- Acceptance gate
- sid-b check received
- Session store
- ledger-b: expired
- Protected resource
- Protected data withheld
- Generation state was read
The session store is the application’s logical state owner here, not a mandatory network hop. Last accepted activity is at t=70. With a 300-second idle timeout, the acceptance deadline is reached at t=370.
- Browser cookie jar
- Waiting for response
- Acceptance gate
- Waiting for validity result
- Session store
- ledger-b: expired
- Protected resource
- Protected data withheld
- The acceptance gate closed
The now >= deadline rule rejects the t=370 check with idle_expired. A rejected check does not refresh lastSeen_s=70.
- Browser cookie jar
- Waiting for response
- Acceptance gate
- REJECT: idle_expired
- Session store
- ledger-b: expired
- Protected resource
- Protected data withheld
- The resource boundary is preserved
Failed session acceptance prevents an authenticated context reaching resource processing. This gate does not replace resource authorization; it is an earlier required condition.
- Browser cookie jar
- Waiting for response
- Acceptance gate
- REJECT: idle_expired
- Session store
- ledger-b: expired
- Protected resource
- Body withheld
- The outcome returned to the browser
An authn-required teaching outcome reached the browser. Cookie presence and server session acceptance remain on separate cards; no new numeric HTTP status is invented.
- Browser cookie jar
- sid-b present; not accepted
- Acceptance gate
- REJECT: idle_expired
- Session store
- ledger-b: expired
- Protected resource
- Body withheld
After logout
- A cookie is not an acceptance decision
The browser still holds teaching alias sid-b. The server generation is already at its termination boundary.
- Browser cookie jar
- sid-b present
- Acceptance gate
- No check
- Session store
- ledger-b: revoked
- Protected resource
- Protected data withheld
- The check reached the application
An ordinary protected check with alias sid-b reached the application. Cookie flags or browser retention do not remove server validity checks.
- Browser cookie jar
- Waiting for response
- Acceptance gate
- sid-b check received
- Session store
- ledger-b: revoked
- Protected resource
- Protected data withheld
- Generation state was read
The store is a logical data owner in this example; no mandatory network hop is drawn. At t=90, session_revoked ends the generation; the cookie-clearing response may not have arrived.
- Browser cookie jar
- Waiting for response
- Acceptance gate
- Waiting for validity result
- Session store
- ledger-b: revoked
- Protected resource
- Protected data withheld
- The acceptance gate closed
A revoked generation is not accepted. Even a lost cookie-clearing response does not delegate the server decision to browser state.
- Browser cookie jar
- Waiting for response
- Acceptance gate
- REJECT: user_logout
- Session store
- ledger-b: revoked
- Protected resource
- Protected data withheld
- The resource boundary is preserved
Failed session acceptance prevents an authenticated context reaching resource processing. This gate does not replace resource authorization; it is an earlier required condition.
- Browser cookie jar
- Waiting for response
- Acceptance gate
- REJECT: user_logout
- Session store
- ledger-b: revoked
- Protected resource
- Body withheld
- The outcome returned to the browser
An authn-required teaching outcome reached the browser. Cookie presence and server session acceptance remain on separate cards; no new numeric HTTP status is invented.
- Browser cookie jar
- sid-b present; not accepted
- Acceptance gate
- REJECT: user_logout
- Session store
- ledger-b: revoked
- Protected resource
- Body withheld
ASSUMPTION · BOUNDARY · CONTROL
Why keep pre-authentication state separate?
An anonymous session can hold preferences or temporary UI state. An authenticated session binds Duru’s identity to protected operations. The authority of the latter must not be established by trusting who selected the earlier identifier or where it was previously used.
The broken assumption is: “A successful login makes the existing identifier trustworthy.” Authentication checks credentials; it does not automatically change the old handle’s history. A new generation after login or a significant privilege change, with acceptance of the old generation closed, keeps those trust levels separate. Impact remains bounded by the identity and permissions the session supplies; a session defect does not inherently compromise the whole system.
Protecting a cookie and invalidating a session are separate controls
When an identifier is a bearer handle sufficient to reach authenticated state, its confidentiality matters. Appropriate HTTPS transport and narrow cookie scope reduce exposure. HttpOnly limits script access to the value; it does not independently prevent the Browser from carrying the identity on eligible script-initiated actions.
Therefore, HttpOnly does not establish action safety, CSRF protection or resource authorization. Place the control at its actual boundary: the channel for transport, a cookie attribute for script access, request validation for state-changing actions, and server policy for object access. The exercise distinguishes control responsibilities rather than constructing payloads.
Why must an expired or revoked handle be rejected?
At t=370, the cookie may still be visible while the session no longer meets the idle condition. Relying on a UI timer makes the server decision depend on client presentation. Similarly, logout that only cleans the jar can leave server authority intact. Durable enforcement evaluates the current generation and server deadlines where protected processing is accepted.
In a self-contained design, the answer depends on revocation propagation. A node checking only signature and exp may receive no information that immediately changes its decision after another node processes logout. State the permitted delay window and the observable control that verifies the design.
3. Blue · Secure development & defense
Read every scenario without motion
Ledger consistent
- The ledger carries no secret
The supplied generationRef ledger-b is an audit correlation label, not a cookie value or working credential; this scene does not execute KQL.
- Lifecycle ledger
- ledger-b / seq 5…7
- Correlation review
- Scope not selected
- Integrity conclusion
- No conclusion
- Generation scope was selected
seq orders events only within idleBranch. logoutBranch is an alternative timeline; the branches are not merged into a global distributed order.
- Lifecycle ledger
- idleBranch
- Correlation review
- ledger-b / one branch
- Integrity conclusion
- No conclusion
- Terminal evidence was inspected
seq=6 session_expired / expired records the t=370 boundary. A session_check result remains a separate decision in the same field family.
- Lifecycle ledger
- seq 6: session_expired
- Correlation review
- ledger-b / one branch
- Integrity conclusion
- No conclusion
- The following check was read separately
The supplied seq=7 record is session_check / rejected / idle_expired. lastSeen_s=70 remains unchanged; rejection is not counted as renewed activity.
- Lifecycle ledger
- seq 7: rejected
- Correlation review
- lastSeen_s=70
- Integrity conclusion
- No conclusion
- All matching records were examined
All checks with the matching generationRef are reviewed together, not merely the first row. Duplicates or uncertain ordering stop a definite causal claim.
- Lifecycle ledger
- Supplied checks selected
- Correlation review
- Accepted-after-terminal absent
- Integrity conclusion
- No conclusion
- The integrity conclusion was bounded
This bounded ledger contains no accepted check after termination. This is fixture consistency, not a distributed revocation test or proof of complete log coverage.
- Lifecycle ledger
- Evidence copy
- Correlation review
- Order consistent
- Integrity conclusion
- PASS: ledger integrity
Terminal absent
- The ledger carries no secret
The supplied generationRef ledger-b is an audit correlation label, not a cookie value or working credential; this scene does not execute KQL.
- Lifecycle ledger
- ledger-b / seq 5…7
- Correlation review
- Scope not selected
- Integrity conclusion
- No conclusion
- Generation scope was selected
seq orders events only within idleBranch. logoutBranch is an alternative timeline; the branches are not merged into a global distributed order.
- Lifecycle ledger
- idleBranch
- Correlation review
- ledger-b / one branch
- Integrity conclusion
- No conclusion
- Terminal evidence was inspected
This branch omits seq=6 session_expired from the review copy. Collection loss and no event are not the same; store state cannot be concluded from this absence.
- Lifecycle ledger
- Terminal record missing
- Correlation review
- ledger-b / one branch
- Integrity conclusion
- No conclusion
- The following check was read separately
The supplied seq=7 record is session_check / rejected / idle_expired. lastSeen_s=70 remains unchanged; rejection is not counted as renewed activity.
- Lifecycle ledger
- seq 7: rejected
- Correlation review
- lastSeen_s=70
- Integrity conclusion
- No conclusion
- All matching records were examined
All checks with the matching generationRef are reviewed together, not merely the first row. Duplicates or uncertain ordering stop a definite causal claim.
- Lifecycle ledger
- Supplied checks selected
- Correlation review
- Accepted-after-terminal unknown
- Integrity conclusion
- No conclusion
- The integrity conclusion was bounded
The rejected check is known, but evidence of the terminal state transition is missing. Review preserves UNKNOWN and requests the lifecycle record.
- Lifecycle ledger
- Evidence copy
- Correlation review
- Incomplete coverage
- Integrity conclusion
- UNKNOWN: termination
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Which observation proves which state?
Seeing Set-Cookie in DevTools Network shows that the Application sent a header. Assess Browser acceptance separately through cookie storage and relevant request inclusion; policy, scope or Browser settings can affect it. A visible cookie does not establish an active server record, and a UI identity label does not prove the latest protected decision succeeded.
The ledger is application-owned synthetic JSON. It is not a built-in web-server event, an existing SIEM table or a packet capture. event, seq, clock_s, generationRef, requestId, result and reason belong to our fixture schema. A real application needs documentation of the emitting code path and log-delivery behavior.
Lifecycle correlation without credentials
Use an audit correlation reference to connect rotation, expiry and logout; do not log raw session values, Set-Cookie values or credentials. generationRef=ledger-b is an audit reference that cannot be converted into the Browser handle. requestId=demo-session-3 connects only the application check and its decision record.
Ask a precise correlation question: is there an accepted session_check for the same generationRef after its terminal transition? The artifact’s KQL uses seq in an embedded datatable. seq orders one model scenario; it does not create a global ordering of wall-clock timestamps across hosts. Real event ordering and node/store consistency require separate investigation.
The idle branch normally has an accepted check at t=70 and expiry/rejection at t=370, producing no query row. A positive fixture adding acceptance after a terminal transition can expose a missing control. A missing terminal event means missing evidence, not proof of acceptance. Delivery gaps, duplicate events and incorrectly matched generation references must be evaluated before labelling a real row an attack.
Validation and durable controls
In the manual lab, observe the jar before and after login, the sanitized store view, expiry using the explicit demo clock, and the protected decision after logout. Predict each result first, then record its observation layer. Explain the intermediate view if store invalidation and response arrival are separate steps.
For production hardening, assess the framework’s session lifecycle, HTTPS/proxy configuration, cookie scope and an application-appropriate idle/absolute policy together. Validate changes with a controlled user group and measure effects on long forms, background activity and multiple tabs. Decide separately how changes to roles or account state affect existing sessions.
Success means explaining the independent evidence for a clean Browser jar, a revoked store record and a rejected protected check. The KQL is annotated review logic; the presence of query text does not mean a real ingest pipeline or detection backend has been configured.
Renew the server-side session identifier at login or privilege change; stop accepting the old generation for protected operations. Enforce idle and absolute deadlines against trusted server state on each protected request, independently of Browser cookie expiry. On logout, invalidate the relevant server record before sending cookie-clearing instructions. Distinguish this-session and all-device scope. Verify denial for the old generation after rotation, deadline equality and the old handle after logout; missing terminal evidence does not establish global revocation.
NOW APPLY IT TO THE CASE
How do cookie presence and server acceptance differ?
Duru’s browser may retain cookie label sid-b after generation ledger-b ends through logout or idle expiry. Cookie retention, server acceptance and lifecycle evidence have separate owners.
Find separate observations for the case handle, server binding and session lifetime. Does a retained browser value establish current server admission by itself?
Open the related case file ↗READ THE EXAMPLE TOGETHER
Reading a synthetic cookie beside its session ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"origin": "https://library.outreason.example",
"classification": "inert educational fixture; no working credential",
"setCookieIllustration": "__Host-hd_session=SYNTHETIC_AUTH_B; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1800",
"logoutSetCookieIllustration": "__Host-hd_session=; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=0",
"modelAliases": {
"sid-a": "anonymous browser handle label",
"sid-b": "authenticated browser handle label",
"ledger-a": "audit ref for previous generation",
"ledger-b": "audit ref for current generation"
},
"policy": {
"clockUnit": "seconds since scenario start",
"idleTimeout_s": 300,
"absoluteTimeout_s": 1800,
"loginCreatedAt_s": 10,
"absoluteDeadline_s": 1810,
"activity": "accepted protected check",
"boundary": "reject at now >= deadline"
},
"idleBranch": {
"ledger": [
{
"seq": 1,
"clock_s": 0,
"event": "anonymous_created",
"generationRef": "ledger-a",
"subject": null,
"result": "created",
"reason": "anonymous_state",
"requestId": "demo-session-1"
},
{
"seq": 2,
"clock_s": 10,
"event": "session_rotated",
"generationRef": "ledger-b",
"previousGenerationRef": "ledger-a",
"subject": "Duru",
"tenant": "Atlas",
"result": "active",
"reason": "authenticated_transition",
"requestId": "demo-session-2",
"previousGenerationState": "invalidated"
},
{
"seq": 3,
"clock_s": 10,
"event": "response_pending",
"generationRef": "ledger-b",
"result": "not_arrived",
"reason": "cookie_jar_still_sid_a",
"requestId": "demo-session-2"
},
{
"seq": 4,
"clock_s": 10,
"event": "cookie_arrived",
"generationRef": "ledger-b",
"result": "stored",
"reason": "jar_updated_to_sid_b",
"requestId": "demo-session-2"
},
{
"seq": 5,
"clock_s": 70,
"event": "session_check",
"generationRef": "ledger-b",
"result": "accepted",
"reason": "active_within_deadlines",
"requestId": "demo-session-3",
"lastSeen_s": 70
},
{
"seq": 6,
"clock_s": 370,
"event": "session_expired",
"generationRef": "ledger-b",
"result": "expired",
"reason": "idle_deadline_reached",
"requestId": "demo-session-4"
},
{
"seq": 7,
"clock_s": 370,
"event": "session_check",
"generationRef": "ledger-b",
"result": "rejected",
"reason": "idle_expired",
"requestId": "demo-session-4",
"lastSeen_s": 70
}
],
"expectedAt370": {
"cookieJar": "sid-b may remain until browser lifetime ends",
"serverGeneration": "expired",
"sessionDecision": "rejected",
"lastSeen_s": 70
}
},
"logoutBranch": {
"startsAfterSeq": 5,
"ledger": [
{
"seq": 6,
"clock_s": 90,
"event": "session_revoked",
"generationRef": "ledger-b",
"result": "revoked",
"reason": "user_logout",
"requestId": "demo-session-5"
},
{
"seq": 7,
"clock_s": 90,
"event": "response_pending",
"generationRef": "ledger-b",
"result": "not_arrived",
"reason": "cookie_clear_not_yet_received",
"requestId": "demo-session-5"
},
{
"seq": 8,
"clock_s": 90,
"event": "cookie_cleared",
"generationRef": "ledger-b",
"result": "removed",
"reason": "matching_scope_max_age_zero",
"requestId": "demo-session-5"
}
],
"expectedProtectedDecision": "rejected even if clearing response is lost"
},
"correlationQuery": {
"language": "KQL",
"backend": "Azure Data Explorer or compatible Kusto engine; embedded datatable requires no ingest mapping",
"schema": "seq:int, event:string, generationRef:string, result:string",
"query": "// Synthetic fixture only. seq is local scenario order, not a distributed timestamp.\nlet Events = datatable(seq:int, event:string, generationRef:string, result:string)\n[\n 5, 'session_check', 'ledger-b', 'accepted',\n 6, 'session_expired', 'ledger-b', 'expired',\n 7, 'session_check', 'ledger-b', 'rejected'\n];\nlet Ended = Events\n| where event in ('session_expired', 'session_revoked')\n| summarize endSeq=min(seq) by generationRef;\nEvents\n| where event == 'session_check' and result == 'accepted'\n| join kind=inner Ended on generationRef\n| where seq > endSeq\n| project generationRef, seq, endSeq, result",
"expectedIdleBranchRows": 0,
"positiveFixtureChange": "Add seq=8, event=session_check, generationRef=ledger-b, result=accepted to the embedded datatable; expected one row. This is an integrity fixture, not an attack step.",
"executed": false
}
}- SYNTHETIC_AUTH_B / sid-b
- A fixed instructional label for following the authenticated handle, not a working secret. It does not demonstrate production identifier generation.
- __Host-hd_session, Path=/, no Domain
- Host-only and root-path prefix requirements in supporting Browsers, not permission to access a resource.
- Secure / HttpOnly / SameSite=Lax
- Transport, script access and site-context inclusion boundaries, independent of server validity.
- Max-Age=1800 / Max-Age=0
- Browser retention/deletion instructions, not a store TTL or server logout result.
- generationRef / previousGenerationRef
- Our schema’s audit correlation references, distinct for each generation and never accepted as cookie values.
- clock_s / seq
- An explicit model clock and event order within one branch, not real time, Windows Event IDs or distributed global order.
- lastSeen_s / absoluteDeadline_s
- The latest accepted activity for idle calculation and the independent absolute deadline. The rejected t=370 request does not update lastSeen.
- event / result / reason
- Synthetic application-owned lifecycle events and decision explanations; not an automatic mapping to gateway access logs.
- requestId
- Correlates one check/response operation; it is not an authenticated identity or session secret.
- idleBranch / logoutBranch
- Alternative scenarios branching from the same accepted state; do not merge them into logout occurring after expiry.
The HTTPS header is inert text. The ledger is neither a captured session nor framework output. KQL is unexecuted review logic; the fixture does not implement identity verification, cryptographic token validation or distributed cache behavior. A loopback HTTP lab may explain a different cookie name/flag profile; it is not assumed to execute this HTTPS __Host- illustration unchanged.
Two reality checks
An HttpOnly cookie prevents every security problem involving JavaScript.
HttpOnly restricts script access to the cookie value; eligible script-initiated requests can still include it. Action safety and resource policy require separate controls.
Deleting the Browser cookie automatically invalidates the server session.
The cookie jar and session store are separate state. Server invalidation needs its own transition; jar cleanup alone does not prove revocation.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
The last accepted check was 70; idle timeout is 300 and now is 370. Does a rejected check refresh lastSeen?
- No; idle expiry occurs and lastSeen remains 70.
- Yes; every incoming request indefinitely extends expiry.
Read the explained answer
No; idle expiry occurs and lastSeen remains 70. — 70+300=370 reaches the deadline at equality; a rejected check is not accepted activity.
If the logout clearing response is lost, is the revoked session accepted?
Selected lesson artifact fields; teaching example
- logoutBranch.ledger[0]
- {"seq":6,"clock_s":90,"event":"session_revoked","generationRef":"ledger-b","result":"revoked","reason":"user_logout","requestId":"demo-session-5"}
- logoutBranch.ledger[1]
- {"seq":7,"clock_s":90,"event":"response_pending","generationRef":"ledger-b","result":"not_arrived","reason":"cookie_clear_not_yet_received","requestId":"demo-session-5"}
- logoutBranch.expectedProtectedDecision
- rejected even if clearing response is lost
- Yes; the server session remains active until the jar is cleared.
- No; server revocation closes the protected decision.
Read the explained answer
No; server revocation closes the protected decision. — Even if the jar retains the old handle, the trusted generation is revoked; clearing delivery does not reopen authentication eligibility.
At login rotation ledger-a is invalidated and ledger-b active. Why can the browser sid-a state not change before Cookie arrival?
Read the explained answer
Server-generation rotation changes local trusted state; a new Cookie travels in the response. While the response is pending, the jar stays sid-a; cookie_arrived establishes sid-b. Audit generationRef is correlation, not a working credential. KQL expected rows inspect local synthetic order and establish neither distributed runtime behavior nor executed detection.
□ I separated store rotation from jar arrival.
□ I did not treat audit aliases/detection expectations as runtime credentials/proof.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
MANUAL LAB
Request, session, and resource policy
Two workbooks follow actual loopback HTTP requests, cookie/store boundaries, and seeded permission decisions.
Open the prediction → observation → explanation workbook ↗Source on GitHub ↗Sources and scope
- MDN — Using HTTP cookiesCreating, removing, and updating cookies · Block access to your cookies · Define where cookies are sent
- MDN — Set-Cookie headerIntroduction: forbidden response header name · Attributes: HttpOnly, Max-Age, SameSite, Secure · Cookie prefixes: __Host-
- OWASP — Session Management Cheat SheetSession ID Content (or Value) · Renew the Session ID After Any Privilege Level Change · Session Expiration / Idle Timeout / Absolute Timeout · Logout Button · Logging Sessions Life Cycle: Monitoring Creation, Usage, and Destruction of Session IDs
- RFC 6265 — HTTP State Management Mechanism4.1.2.2 The Max-Age Attribute · 4.1.2.3 The Domain Attribute · 4.1.2.4 The Path Attribute · 4.1.2.6 The HttpOnly Attribute · 4.2.2 Semantics
- RFC 7519 — JSON Web Token (JWT)4.1.3 aud · 4.1.4 exp · 4.1.5 nbf · 11.1 Trust Decisions
- RFC 7009 — OAuth 2.0 Token Revocation2.1 Revocation Request: invalidation and propagation · 3 Implementation Note: self-contained versus handles
- RFC 9700 — Best Current Practice for OAuth 2.0 Security4.14.2 Recommendations: refresh-token rotation, revocation and expiry