Skip to content

feat: enforce upstream IdP session_expiry ceiling (IPSIE SL1)#120

Open
kishore7snehil wants to merge 4 commits into
mainfrom
feat/ipsie-session-expiry
Open

feat: enforce upstream IdP session_expiry ceiling (IPSIE SL1)#120
kishore7snehil wants to merge 4 commits into
mainfrom
feat/ipsie-session-expiry

Conversation

@kishore7snehil

Copy link
Copy Markdown
Contributor

📋 Changes

This PR adds enforcement of the IPSIE SL1 session_expiry claim to auth0-server-python — an absolute Unix-epoch ceiling an upstream IdP can assert on how long an Auth0 app session may live. The SDK reads the claim at login, stamps it on internal.session_expires_at, and enforces it lazily on every read.

✨ Features

  • Login-time enforcement: complete_interactive_login reads session_expiry/iat from the verified ID token claims and stamps internal.session_expires_at on the persisted session. A login whose ceiling is already in the past is rejected with SessionExpiredError rather than persisted; the spent transaction is cleaned up before raising since its authorization code cannot be reused.
  • Read-time enforcement: get_session() / get_user() return None (and delete the session) once the ceiling is reached; get_access_token() raises AccessTokenError(SESSION_EXPIRED). A 30s negative clock-skew leeway is applied so the SDK never serves a session the platform has already revoked.
  • Refresh preservation: The ceiling stamped at login is preserved across refresh-token grants — the platform does not re-emit session_expiry on a refresh, so the original ceiling is never overwritten or erased.

🔧 API Changes

  • New SessionExpiredError — flow-agnostic, carries code == "session_expired"
  • UserClaims.session_expiry: Optional[int] and InternalStateData.session_expires_at: Optional[int]

📖 Documentation

  • Updated README.md and examples/RetrievingData.md with usage, the upgrade null-check note, leeway behavior, and refresh preservation

🧪 Testing

  • This change adds test coverage
  • This change has been tested on the latest version of the platform/language

Contributor Checklist

…laim extraction

Add a login-time lockout guard to complete_interactive_login: when the upstream
IdP asserts a session_expiry ceiling already in the past at login (compared
against the ID token iat with the same 30s leeway as read-time enforcement),
raise the new flow-agnostic SessionExpiredError instead of persisting an
already-expired session. A missing claim stays a no-op, preserving existing
behavior.

Generalize extract_session_expiry into extract_epoch_claim(claims, name),
reused for both session_expiry and iat, and rename the ceiling predicates to
is_session_ceiling_reached (read-time) and is_session_ceiling_in_past (login).

Document the login rejection in the README, RetrievingData guide, and the
ipsie-webapp example.
…m boundary

The signature-verified, Auth0-issued ID token has already been validated
upstream (the platform refuses to emit a malformed session_expiry), so the
SDK reads it like every other operational claim instead of running a bespoke
validator. Removes State.extract_epoch_claim and reads session_expiry/iat
with a plain .get() at both extraction sites; the None guards in the ceiling
comparison functions still deliver the absent/null "no ceiling" safe default.

Production-reachable inputs (absent/null, clean integer) are unchanged; only
unreachable malformed values change behavior, now failing closed rather than
being silently accepted.
Delete the transaction before raising SessionExpiredError at the login
guard — the authorization code was already exchanged, so the transaction
is spent and cannot be reused. Also treat iat=0 as a valid issued-at
reference (use `is not None` rather than truthiness) in the ceiling
lapsed check.
@kishore7snehil kishore7snehil requested a review from a team as a code owner June 11, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant