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:
- 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.
- Each non-current session has a "Revoke" button → calls
OpenIddictSessionService.RevokeSessionAsync(sessionId). - Top of page has a "Sign out of all other devices" button → revokes everything except the current session.
- Current session is never revocable from this page — for that, users use Logout.
- 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
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:
OpenIddictSessionServicetracks active sessions and the Admin module'sUserSessionsTabalready 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:
UpdateSecurityStampAsyncissue — that one is the nuclear option, this one is selective.)How the user will use it
New page at
Manage/ActiveSessions:OpenIddictSessionService.RevokeSessionAsync(sessionId).Add a menu link under the existing Account Settings group.
Implementation notes
ActiveSessionsEndpointinmodules/Users/src/SimpleModule.Users/Pages/Account/Manage/that returns the current user's sessions via the existingIOpenIddictSessionService(or whatever the contract surface is — re-use, do not duplicate).RevokeSessionEndpoint(POST/account/manage/sessions/{id}/revoke) andRevokeOtherSessionsEndpoint(POST/account/manage/sessions/revoke-others).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).Manage/ActiveSessions.tsxpage using existing UI primitives from@simplemodule/ui. Format User-Agent strings viaua-parser-js(already in the dep graph?) or a small helper.modules/Users/src/SimpleModule.Users/Pages/index.ts.npm run validate-pagesmust pass.Benefits
Acceptance criteria
Manage/ActiveSessionspage lists the current user's sessions with device, IP, and timestamps.Pages/index.ts;npm run validate-pagespasses.