Hacker DirectoryHacker Directory

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.

CORE · RED · BLUE · ARTIFACT · PRACTICE

1. Core · Mechanism

Read every scenario without motion

Login rotation

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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?

  1. No; idle expiry occurs and lastSeen remains 70.
  2. 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
  1. Yes; the server session remains active until the jar is cleared.
  2. 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