Open Closed

Bug: Idle Session Timeout is client-side-only, not server-enforced — violates OWASP/NIST session management guidance #10758


User avatar
0
FarReach created

Summary ABP Commercial's Idle Session Timeout feature (Account Pro module, @volo/abp.ng.account) is implemented entirely client-side. The configured timeout is enforced by browser JavaScript only; the server does not track last-activity, does not refuse stale access tokens, and does not revoke them on idle. Any client-side failure (browser closed without logout, JavaScript disabled, extension interference, attacker bypassing the SPA by calling the API directly with a captured token) leaves the access token usable until its absolute exp — typically hours after the documented "idle" threshold has passed.

This makes the Idle Session Timeout a UX hint, not a security control, which is not how it is documented and not how enterprise customers (banking, healthcare, regulated SaaS) interpret it. It fails to meet the OWASP, ASVS, and NIST 800-63B session-management requirements that those customers are typically held to by their own auditors.

The cross-tab fix in v9.3.x → v10.x (BroadcastChannel-based coordination) is a real and welcome improvement at the UX layer. It does not change the underlying security posture.

Environment ABP version verified: @volo/abp.ng.account@10.0.1 (compiled output inspected directly) UI: Angular (commercial Account Pro module) Auth server: OpenIddict (JWT access tokens by default) Likely affected: all versions from v9.1 (when the Idle Session Timeout feature was introduced) forward Reproduction Configure Idle Session Timeout: Setting > Account > Idle Session Timeout, enabled, set timeout to e.g. 30 minutes.

Log in to the Angular SPA. Capture the bearer access token from the browser session (any of: DevTools Application → Local Storage, network request inspection, or a debugging proxy).

Stop interacting with the SPA. Wait past the idle threshold (e.g., 35 minutes). Observe the SPA's modal fire and "log out" the user.

From a separate process (curl, Postman, or any HTTP client), issue an authenticated API request using the captured bearer token — for example:

curl -H "Authorization: Bearer <captured-token>" https://your-host/api/account/my-profile Observe: the request succeeds. The token remains valid until its OpenIddict-configured exp (default 1 hour, often configured much longer for "remember me" / refresh-token flows).

Expected behavior After the configured idle threshold has elapsed without server-observed activity, the server should refuse the access token. Acceptable implementations:

Sliding refresh with idle gate. Short-lived access tokens (minutes). The refresh endpoint refuses to re-issue if now - LastActivityTime > IdleThreshold. LastActivityTime is updated on every successful authenticated request. Reference tokens + introspection. OpenIddict supports this. Idle revocation becomes a state change on the token record. Custom OpenIddict event handler on ProcessAuthenticationContext that checks a server-side LastActivityTime per user/session. Any of these would close the gap. The current timer(idleTimeoutMinutes * SECONDS) in IdleSessionService.startIdleTimeout does not.

Evidence (@volo/abp.ng.account@10.0.1) From the compiled source at package/fesm2022/volo-abp.ng.account-admin.mjs in the published npm tarball:

Lines 343–481 define IdleSessionService. Line 409–421 (startIdleTimeout): the idle timer is a client-side RxJS timer(idleTimeoutMinutes * SECONDS). No server round-trip, no last-activity ping, no refresh-token gate. Line 399–402 (logout): on countdown completion, calls this.authService.logout().subscribe(). This triggers the standard OpenIddict logout (clears the client's tokens and cookies). It does not revoke the access token on the server — and even if it did, the revocation only fires when the client-side timer reaches zero. If the client never gets there (browser closed, JS error, captured-token attacker), the server never knows the session went idle. No LastActivityTime tracking is performed on the server. No interceptor, no filter, no refresh-endpoint check. The Account Pro server-side IdleSessionSettings config exposes the threshold but does not enforce it. For comparison, the cross-tab fix at the same lines (BroadcastChannel 'idle-session-activity', lines 437–458) is well-implemented — this report is specifically about the server-side enforcement gap, not the client behavior.

Why this matters — standards references This is not a theoretical concern. Multiple authoritative standards that enterprise/regulated ABP customers are held to explicitly require server-side enforcement:

OWASP Session Management Cheat Sheet (link):

"Session timeouts must be set on the server side and must invalidate the session ID. It is mandatory for the web application to take active actions when the session expires, or the user actively closes it. It is not enough for the web application to expect the web browser to do it."

OWASP ASVS v4.0.3 Section 3 (link):

V3.3.1 (L1+): logout and expiration must invalidate the session token server-side. V3.3.2 (L2+): re-authentication required after idle period. V3.3.4 (L2+): server-side session termination. NIST SP 800-63B §7.2 (link):

AAL2: re-authentication required after 30 minutes of inactivity. AAL3: re-authentication required after 15 minutes of inactivity. "The session shall be terminated when any of these time limits is reached." PCI DSS v4.0 Requirement 8.2.8: 15-minute server-enforced idle timeout for cardholder-data environments.

An L2+ ASVS audit, a NIST AAL2 control review, or a PCI assessment of a current ABP-based application would flag the Idle Session Timeout feature as non-compliant. Customers in regulated industries who reasonably believed the feature was a security control will discover this during their own security reviews — typically late, and typically as a surprise.

Threat model The client-side-only design protects against a single threat: a casual passerby walking up to an unattended browser. It provides no protection against:

An attacker who has captured the access token (XSS, browser memory inspection, malware on the user's machine, unsigned-out shared workstation, intercepted proxy logs) and uses it directly via HTTP client. A token leak in logs, browser history, or accidentally-shared session. Any scenario where the legitimate client's JavaScript does not fire — browser closed, computer suspended, JS error, tab crashed. A stolen device where the attacker reads the token from disk-resident storage. The "idle timeout" name implies protection against these scenarios. The implementation does not provide it.

Suggested approach (not prescriptive) Any of the following would address the gap; the choice is ABP's:

Short access tokens + sliding refresh with idle gate (recommended).

Default AccessTokenLifetime to the idle threshold (or shorter). On the refresh-token endpoint, check LastActivityTime (new field, tracked server-side, updated on every authenticated request via a MiddlewareBase or IAsyncActionFilter). Refuse re-issue if exceeded. This is the lowest-friction option for existing customers; the client-side IdleSessionService continues to work as the UX layer, and the server becomes the source of truth. OpenIddict reference tokens + introspection.

Switch the default for new Account Pro deployments to reference tokens. Idle revocation becomes a single Status field update on the OpenIddictToken aggregate. Higher friction (introspection on every request), but cleanest semantics. Per-request LastActivityTime check.

A LastActivityTime field on user or a new UserSession aggregate. Updated on every authenticated request. A custom IAuthorizationFilter or OpenIddictServerEvents.ProcessAuthenticationContext handler checks the value and short-circuits with 401 if exceeded. Simpler than option 1 for customers not running a refresh-token flow; doesn't require JWT lifetime changes. In all three options, the existing IdleSessionService should also call a server-side Logout or Revoke endpoint when its countdown fires, so the server-side state is updated on the happy-path client-side timeout too.

The IdleSessionTimeoutMinutes setting should remain authoritative — server enforcement reads the same setting value as the client.

Related Multi-tab cross-tab coordination was fixed in v9.3.x (BroadcastChannel) — confirmed at v10.0.1. That fix is good. This bug is the next layer down and is distinct. ABP support thread on the multi-tab fix for context: #10079. ABP docs for the feature: https://abp.io/docs/latest/modules/account/idle-session-timeout. The doc should be updated to clearly state the enforcement boundary (whatever it ends up being) so customers don't misinterpret the feature. Severity rationale This is not a remote-code-execution-class vulnerability; an attacker still needs to capture a valid token or be at the user's workstation. But it is a security control that does not do what its name and documentation imply it does, in a framework that is explicitly marketed to enterprise and regulated customers. We consider it a high-severity correctness issue in a security feature, not a defect in functional behavior. We are filing because we have several enterprise customers (including regulated industries) who rely on this control and need it to be defensible in their own security reviews.

Happy to provide any additional reproduction detail or discuss implementation approaches.

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

3 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • What you observed is correct for ABP Commercial Account Pro (Angular): the Idle Session Timeout feature described in the Account module docs is implemented as a client-side idle detection + client logout UX, and it does not (by itself) enforce a server-side “idle invalidation” of already-issued JWT access tokens. With self-contained JWTs, the API can’t reject a token for “idle” unless you introduce server-side state (introspection/reference tokens, a session store, per-request activity tracking + custom validation, etc.). The setting is still useful to protect against an unattended browser, but it is not sufficient as a security control against a stolen/captured bearer token.

    • If you need OWASP/NIST/PCI-style server-enforced inactivity timeout, implement one of these server-side patterns in your ABP/OpenIddict setup (ABP doesn’t currently provide this automatically as part of Idle Session Timeout):

      1. Short-lived access tokens + refresh-token “idle gate” (recommended)
        • Configure access tokens to be short-lived (e.g., 5–10 minutes).
        • Track LastActivityTime on the server (update on every successful authenticated API request via middleware/action filter).
        • On the refresh token flow, refuse to issue a new access token if now - LastActivityTime > IdleThreshold.
        • Result: after the idle window, the client cannot refresh; the old token naturally expires quickly.
      2. Reference tokens + introspection
        • Use reference tokens so the resource server calls introspection (or shares token store) and can reject tokens based on server-side session state.
        • Result: you can revoke/expire on idle immediately, but there’s added runtime cost/complexity.
      3. Server-side session record + custom validation
        • Maintain a “session” entity keyed by (user, device, client_id, session_id) and update activity per request.
        • Add a validation step in the authentication pipeline that returns 401 if the session is idle beyond the threshold.
        • With pure JWT validation, this effectively requires an extra server-side lookup on requests.
    • Also, if you keep the existing UX behavior, you should add a server-side revoke/logout endpoint and call it from the client when the idle countdown completes, so the happy path idle logout updates server state too (this still won’t help if the browser never runs the timer, but it closes the loop when it does).

    • Documentation note: the current doc page does not state “client-only” explicitly; it only describes the setting and the modal/logout behavior. There isn’t a verified official ABP source stating that the setting is server-enforced, nor one documenting a built-in server enforcement mechanism for JWT idle invalidation in Account Pro.

    Links verified:

    • https://abp.io/docs/latest/modules/account/idle-session-timeout

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    FarReach created

    We can definitely go down the route of fixing the issue on our side but we want you to be aware as this seems like a security vulnerability that needs patched. Thanks!

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    Your analysis is correct — the Idle Session Timeout is a client-side idle sign-out for an unattended browser, and that's intentional. It doesn't track last activity on the server and isn't a server-side idle token-revocation mechanism, so while the user keeps interacting the session runs on the normal token lifetime. Fair point that the docs don't spell this boundary out; we'll make it clearer.

    To bound how long an issued token stays usable, the lever is the token lifetime (access and refresh), not the idle setting. You can configure it here:

    https://abp.io/docs/latest/modules/openiddict#setting-tokens-lifetime

    Note that's an absolute lifetime, not a server-side sliding idle timeout.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 28, 2026, 11:44
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.