Session management
Skill almasumdev/awesome-mobile-backend-agent-skills/.github/skills/auth/session-management
Model multi-device sessions on the backend with sliding vs absolute expiry, device listing, and remote logout. Use when building the session model or a "Your devices" screen.From its SKILL.md
npx -y skills add almasumdev/awesome-mobile-backend-agent-skills --skill session-managementAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.0 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Session Management
Instructions
A session represents one (user, device) pair. Mobile apps run for months; sessions must be long enough to be painless but short enough to contain damage after loss or theft.
1. Session Model
-- Postgres-ish
CREATE TABLE sessions (
id TEXT PRIMARY KEY, -- ULID
user_id TEXT NOT NULL,
device_id TEXT NOT NULL,
device_label TEXT, -- "Pixel 9 Pro", "iPhone 15"
created_at TIMESTAMPTZ NOT NULL,
last_used_at TIMESTAMPTZ NOT NULL,
absolute_expiry TIMESTAMPTZ NOT NULL,
sliding_expiry TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
ip_last INET,
ua_last TEXT
);
One row per active session family (see token-strategy). Refresh tokens are rows in a child table with session_id FK.
2. Sliding vs Absolute Expiry
Use both, and revoke when either trips.
- Sliding window: extends on each successful refresh. Typical: 30 days from
last_used_at. Lets active users stay signed in forever; idle devices drop off. - Absolute window: hard ceiling from
created_at. Typical: 90–180 days. Forces periodic re-auth even on active devices.
valid_session <=> now() < MIN(sliding_expiry, absolute_expiry) AND revoked_at IS NULL
Adjust for the app's risk profile. Banking: sliding 7d / absolute 30d. Social: sliding 60d / absolute 365d.
3. Multi-Device Behavior
Every login creates a new session row -- never reuse across devices. A user has N concurrent sessions (one per device_id).
Listing devices:
GET /v1/me/sessions
{
"items": [
{ "id": "sess_01HX7...", "device_label": "iPhone 15", "last_used_at": "2026-04-10T09:12:00Z", "current": true },
{ "id": "sess_01HX6...", "device_label": "Pixel 9", "last_used_at": "2026-03-28T17:02:00Z", "current": false }
]
}
Revoke a single device:
DELETE /v1/me/sessions/{id}
Revoke all except current (security event):
POST /v1/me/sessions/revoke-others
4. Revocation Semantics
Revocation hides behind the two-layer model from token-strategy:
- Mark
revoked_at = now()on the session row. Child refresh tokens inherit via JOIN. - Publish the
session_id(a.k.a.sid) to a short-lived denylist (Redis set with TTL = access-token max lifetime, e.g., 15 min). - Resource services check
sidagainst the denylist before honoring an access token.
After the access-token TTL, the denylist entry expires; refresh attempts will fail independently because the DB row is revoked.
5. Concurrency Caps (Optional)
For high-assurance apps, cap concurrent sessions per user (e.g., 5). On login, if the count is exceeded, revoke the least-recently-used session.
// Kotlin/JVM pseudocode
fun enforceCap(userId: String, max: Int = 5) {
val active = sessions.listActive(userId).sortedBy { it.lastUsedAt }
val toRevoke = active.size - (max - 1)
if (toRevoke > 0) active.take(toRevoke).forEach { sessions.revoke(it.id, reason = "CAP") }
}
Client UX: show the evicted device in a "Signed out of X to make room" notification on next login.
6. Security-Sensitive Actions
Password change, 2FA enrollment change, email change, account deletion:
- Revoke all sessions except the one performing the action.
- Force re-auth for the current session within 30 days (reset absolute expiry).
7. Step-Up Auth
For high-risk actions (large transfer, adding a new recipient), require a fresh authentication within N minutes even on a long-lived session. Track last_mfa_at on the session row; enforce in the API layer:
# FastAPI dependency
def require_fresh_auth(max_age: timedelta = timedelta(minutes=5)):
def dep(session: Session = Depends(current_session)):
if not session.last_mfa_at or now() - session.last_mfa_at > max_age:
raise HTTPException(401, {"error": {"code": "REAUTH_REQUIRED"}})
return dep
8. Client Consumption
When the app receives REAUTH_REQUIRED or INVALID_SESSION, it navigates to the login screen and drops the refresh token. Do not attempt silent retry.
// Swift
switch error {
case .reauthRequired:
await router.present(.login(reason: .reauth))
case .invalidSession:
await tokenStore.clear()
await router.replaceRoot(.login(reason: .expired))
}
Checklist
- Session row per (user, device) with sliding + absolute expiry.
-
GET /me/sessions,DELETE /me/sessions/{id},revoke-othersendpoints implemented. - Revocation writes DB + publishes to short-lived denylist.
- Concurrent-session cap decision recorded (with or without).
- Password / 2FA / email changes revoke other sessions.
-
last_mfa_attracked; step-up enforced on high-risk endpoints. - Client treats
INVALID_SESSION/REAUTH_REQUIREDas non-retryable. - Session events (create, revoke, reuse) logged for audit.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.