Skip to content

Identity: user-facing active sessions page #178

Description

@antosubash

Summary

Add a self-service "Active sessions" page under Manage/ so users can see every device / browser currently signed in to their account and revoke any of them — including a one-click "Sign out everywhere else".

Why we need this

The plumbing already exists: OpenIddictSessionService tracks active sessions and the Admin module's UserSessionsTab already renders / revokes them on behalf of an admin. But end users themselves cannot see or revoke their own sessions. This is one of the most-requested account-security features by end users and is table-stakes for any product handling sensitive data.

Real scenarios this addresses:

  • "I logged in on a hotel computer last week and forgot to sign out." Today the user has to email support, who then has to use the admin tool. Tomorrow it's two clicks.
  • "I think someone got into my account." User changes password, but unless we revoke other sessions the attacker keeps their cookie. (Note: this overlaps with the UpdateSecurityStampAsync issue — that one is the nuclear option, this one is selective.)
  • "I lost my phone." User signs in from desktop, revokes the phone session, and the lost device is locked out next time it tries.
  • Compliance: GDPR / SOC 2 audits expect users to have visibility into their own sessions.

How the user will use it

New page at Manage/ActiveSessions:

  1. Page lists every active session: device / browser (parsed from User-Agent), IP address (or coarse geo if we want to avoid leaking exact IPs), created-at timestamp, last-activity timestamp, and a "(this device)" tag on the current session.
  2. Each non-current session has a "Revoke" button → calls OpenIddictSessionService.RevokeSessionAsync(sessionId).
  3. Top of page has a "Sign out of all other devices" button → revokes everything except the current session.
  4. Current session is never revocable from this page — for that, users use Logout.
  5. Sessions older than 30 days display with a "Stale" indicator to help users notice forgotten devices.

Add a menu link under the existing Account Settings group.

Implementation notes

  • New view endpoint ActiveSessionsEndpoint in modules/Users/src/SimpleModule.Users/Pages/Account/Manage/ that returns the current user's sessions via the existing IOpenIddictSessionService (or whatever the contract surface is — re-use, do not duplicate).
  • New API endpoints RevokeSessionEndpoint (POST /account/manage/sessions/{id}/revoke) and RevokeOtherSessionsEndpoint (POST /account/manage/sessions/revoke-others).
  • Authorization: every endpoint must check the session belongs to User.GetUserId() — never trust a path parameter alone. Add an integration test for the cross-user attempt that asserts a 404 (not 403, to avoid leaking session-id existence).
  • Identify "current session" by matching the request's session id / token claim to the session record.
  • Front-end: new Manage/ActiveSessions.tsx page using existing UI primitives from @simplemodule/ui. Format User-Agent strings via ua-parser-js (already in the dep graph?) or a small helper.
  • Register the new view endpoint in modules/Users/src/SimpleModule.Users/Pages/index.ts. npm run validate-pages must pass.
  • Tests: list sessions for the current user only, revoke another session, revoke-others leaves current session alive, cross-user revocation returns 404.

Benefits

  • Direct user-empowering security feature — reduces support load and incident response time.
  • Reuses existing OpenIddict session machinery — code change is mostly UI + thin endpoint layer.
  • Materially improves the security posture of any deployment without needing infra changes.
  • Necessary precondition for "force re-auth on suspicious activity" features down the road.

Acceptance criteria

  • Manage/ActiveSessions page lists the current user's sessions with device, IP, and timestamps.
  • Current session is identified and not revocable via this page.
  • User can revoke a single session.
  • User can "revoke all others" in one click.
  • Cross-user revocation attempts return 404 (covered by test).
  • Page registered in Pages/index.ts; npm run validate-pages passes.
  • Integration tests for list / revoke single / revoke others / cross-user guard.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions