Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: enforce IPSIE session_expiry ceiling in credentials managers#983

Open
utkrishtsahu wants to merge 8 commits into
main from
feat/ipsie-session-expiry
Open

feat: enforce IPSIE session_expiry ceiling in credentials managers #983
utkrishtsahu wants to merge 8 commits into
main from
feat/ipsie-session-expiry

Conversation

@utkrishtsahu

@utkrishtsahu utkrishtsahu commented Jun 18, 2026
edited by coderabbitai Bot
Loading

Copy link
Copy Markdown
Contributor

Changes

This change adds support for the IPSIE SL1 session_expiry claim in the credentials managers. When an upstream identity provider (e.g. an enterprise/OIDC connection) asserts a session_expiry claim on the ID token, the SDK now treats it as a hard session ceiling: an absolute lifetime, expressed in Unix seconds, beyond which the local session is no longer valid and cannot be extended by a refresh-token renewal. This is important for downstream/federated scenarios where the upstream IdP dictates a maximum session lifetime that Auth0-issued tokens must respect, independently of the access-token expiry.

Behavior

  • The claim is read at login and persisted to storage (key com.auth0.session_expiry) so that a subsequent refresh whose ID token omits the claim does not silently drop the ceiling.
  • On every getCredentials / hasValidCredentials call, if the ceiling has been reached the stored credentials are cleared and the call fails with CredentialsManagerException.SESSION_EXPIRED. The refresh token is never used to renew past the ceiling.
  • A 30-second negative clock-skew leeway is applied — the session is treated as expired slightly before the wall-clock ceiling, never after.
  • A missing claim means "no ceiling" → fully backward compatible.
  • clearCredentials() removes the persisted ceiling so it does not leak past logout.

Public API added

  • Credentials.sessionExpiresAt: Long? — nullable Unix-seconds ceiling for a credential set; null when the connection does not emit the claim.
  • CredentialsManagerException.SESSION_EXPIRED (new Code.SESSION_EXPIRED + public exception constant) — surfaced when the ceiling is reached.

Internal/affected classes (no breaking signature changes)

  • Jwt — added internal sessionExpiry: Long? parsing of the session_expiry payload claim.
  • BaseCredentialsManager — added isSessionExpired(...), persistSessionExpiry(...), validateSessionExpiryAtCreation(...), the KEY_SESSION_EXPIRY storage key, and the 30s leeway constant.
  • CredentialsManager and SecureCredentialsManager — persist the ceiling on save and enforce/clear on retrieval; remove the key on clearCredentials().

Behavioral / upgrade note: For a user whose connection asserts session_expiry, a getCredentials call that previously succeeded can now fail with SESSION_EXPIRED once the ceiling is reached. Callers should treat SESSION_EXPIRED as a prompt to re-authenticate. Documented in EXAMPLES.md (new "Upstream session expiry" section).

References

SDK-9590

Testing

Reviewers can run the existing unit suite — ./gradlew :auth0:testDebugUnitTest — which now includes coverage for the new behavior:

  • JwtTest — parsing session_expiry as Long seconds, and absence of the claim.
  • CredentialsTest — sessionExpiresAt returns the ceiling / null when absent.
  • CredentialsManagerTest and SecureCredentialsManagerTest — persisting the ceiling at save, valid-before-ceiling retrieval, SESSION_EXPIRED (with credentials cleared) once the ceiling passes, refresh not extending past the ceiling, and clearCredentials() removing the persisted key.

End-to-end, the feature was manually verified on a real device (Pixel 7a, Android 16) against a live tenant. Because the platform does not yet emit session_expiry natively, the claim was injected via a Post-Login Action (api.idToken.setCustomClaim('session_expiry', Math.floor(Date.now()/1000) + N)); all behaviors above were confirmed on both CredentialsManager and SecureCredentialsManager.

  • This change adds unit test coverage

  • This change adds integration test coverage

  • This change has been tested on the latest version of the platform/language or why not

Checklist

Summary by CodeRabbit

  • New Features
    • Added upstream session_expiry ceiling enforcement across credential, SSO, and API-credential flows, including persisted/pinned ceiling and clock-skew leeway.
    • Exposed Credentials.sessionExpiresAt and added CredentialsManagerException.SESSION_EXPIRED.
  • Bug Fixes
    • Cached/renewed credentials are no longer served once the ceiling is reached; stored credentials and the persisted ceiling are cleared.
    • hasValidCredentials now returns false after the ceiling is breached.
  • Documentation
    • Updated error-handling examples and added guidance on session_expiry ceiling behavior and leeway.
  • Tests
    • Added/expanded coverage for ceiling parsing, pinning behavior, and failure scenarios.

@utkrishtsahu utkrishtsahu requested a review from a team as a code owner June 18, 2026 01:34

coderabbitai Bot commented Jun 18, 2026
edited
Loading

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds IPSIE session_expiry ceiling enforcement across credential parsing, storage, retrieval, and validation. Jwt parses the claim, Credentials exposes sessionExpiresAt, CredentialsManagerException adds SESSION_EXPIRED, both managers enforce the ceiling, and tests plus docs cover the new behavior.

Changes

IPSIE session_expiry ceiling enforcement

Layer / File(s) Summary
JWT claim parsing, Credentials getter, and SESSION_EXPIRED exception
auth0/src/main/java/com/auth0/android/request/internal/Jwt.kt, auth0/src/main/java/com/auth0/android/result/Credentials.kt, auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt
Jwt exposes sessionExpiry: Long? from the session_expiry claim. Credentials adds sessionExpiresAt: Long? with pinned-value precedence and JWT fallback. CredentialsManagerException adds SESSION_EXPIRED and its message mapping.
BaseCredentialsManager session-expiry helpers
auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt
Adds the session-expiry storage key, leeway constant, and helpers to read, compare, stamp, persist, and validate the upstream ceiling.
CredentialsManager enforcement
auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt
saveCredentials, getCredentials, getSsoCredentials, and getApiCredentials enforce the ceiling; hasValidCredentials and clearCredentials incorporate the stored ceiling.
SecureCredentialsManager enforcement
auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt
saveCredentials, getCredentials, continueGetCredentials, getSsoCredentials, getApiCredentials, and continueGetApiCredentials enforce the ceiling across biometric and refresh flows; hasValidCredentials and clearCredentials include the ceiling state.
Tests and documentation
auth0/src/test/java/.../JwtTest.kt, auth0/src/test/java/.../CredentialsTest.kt, auth0/src/test/java/.../CredentialsManagerTest.kt, auth0/src/test/java/.../SecureCredentialsManagerTest.kt, EXAMPLES.md
Adds coverage for claim parsing, sessionExpiresAt, ceiling enforcement, leeway, persistence, clearing, and pinned precedence, plus documentation for SESSION_EXPIRED and upstream session expiry behavior.

Sequence Diagram(s)

sequenceDiagram
 participant App
 participant CredentialsManager
 participant BaseCredentialsManager
 participant Storage
 App->>CredentialsManager: saveCredentials(credentials)
 CredentialsManager->>BaseCredentialsManager: validateSessionExpiryAtCreation(idToken)
 alt session_expiry already breached
 BaseCredentialsManager-->>App: throw SESSION_EXPIRED
 else valid
 CredentialsManager->>Storage: persist credentials
 CredentialsManager->>BaseCredentialsManager: persistSessionExpiry(idToken)
 BaseCredentialsManager->>Storage: store KEY_SESSION_EXPIRY
 end
 App->>CredentialsManager: getCredentials(...)
 CredentialsManager->>BaseCredentialsManager: isSessionExpired(idToken)
 BaseCredentialsManager->>Storage: read KEY_SESSION_EXPIRY
 alt ceiling breached
 CredentialsManager->>Storage: clearCredentials + remove KEY_SESSION_EXPIRY
 CredentialsManager-->>App: SESSION_EXPIRED
 else ceiling not reached
 CredentialsManager->>BaseCredentialsManager: stampPinnedSessionExpiry(credentials)
 CredentialsManager-->>App: Credentials with sessionExpiresAt
 end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 A ceiling gleams in the token’s bright light,
session_expiry keeps the timing right.
I hop through caches, refresh, and seal,
pinning the limit so the rules stay real.
When time runs out, I thump: "re-auth, please!"
Then off I go, with whiskers at ease.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enforcing IPSIE session_expiry in credentials managers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ipsie-session-expiry

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt (1)

277-305: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Close the secure SSO/API refresh bypasses.

The secure getCredentials() path now enforces session_expiry, but SSO and API credential flows can still serve cached API credentials or use the refresh token after the upstream ceiling. Also re-check after request.execute() before saving renewed credentials so a renewal that crosses the ceiling in-flight cannot succeed.

Example guard placement for renewed credentials
 val fresh = request.execute()
+ if (isSessionExpired(fresh.idToken)) {
+ clearCredentials()
+ callback.onFailure(CredentialsManagerException.SESSION_EXPIRED)
+ return@execute
+ }
 val expiresAt = fresh.expiresAt.time

For API credentials, add a stored-ceiling guard before prompting/serving cached API credentials, and a decrypted/live-token guard before any refresh-token exchange.

Also applies to: 897-954, 984-1019, 1043-1079

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt`
around lines 277 - 305, After the request.execute() call in the SSO exchange
flow within the serialExecutor block, add a session_expiry validation check
before the saveSsoCredentials(sessionCredentials) call to ensure the renewed
credentials respect the upstream ceiling limit, similar to the guard checks
performed in the secure getCredentials() path. Apply this same validation
pattern to the other credential renewal flows mentioned in related methods
around lines 897-954, 984-1019, and 1043-1079 to ensure all SSO and API refresh
token exchanges enforce the session_expiry ceiling both before serving cached
credentials and after obtaining renewed credentials.
auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt (1)

135-155: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Apply the session ceiling to every refresh-token path.

getSsoCredentials() and getApiCredentials() still use the refresh token without checking session_expiry, and getCredentials() does not re-check after request.execute(). This allows SSO/API exchanges—or a slow credential renewal that crosses the ceiling after the pre-check—to return tokens past the hard session ceiling. Add isSessionExpired(...) guards before SSO/API cached returns and refreshes, and again immediately after each refresh response before saving or invoking success.

Example guard placement for the credential refresh path
 val fresh = request.execute()
+ if (isSessionExpired(fresh.idToken)) {
+ clearCredentials()
+ callback.onFailure(CredentialsManagerException.SESSION_EXPIRED)
+ return@execute
+ }
 val expiresAt = fresh.expiresAt.time

Apply the same pattern before ssoExchange(...), before returning cached APICredentials, and before saving renewed API credentials.

Also applies to: 511-541, 596-662

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt`
around lines 135 - 155, Add session expiry ceiling checks in all refresh-token
paths within CredentialsManager to prevent returning tokens past the hard
session ceiling. Specifically, add isSessionExpired(...) guard checks in the
getSsoCredentials() and getApiCredentials() methods before returning cached
credentials, before invoking ssoExchange() and similar refresh requests, and
immediately after request.execute() completes but before invoking
saveSsoCredentials() or the success callback. This ensures the session_expiry
validation is consistently applied across all SSO, API, and standard credential
renewal flows rather than only at entry points.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt`:
- Around line 338-360: The persistSessionExpiry() method needs to distinguish
between initial credential saves and refresh token saves to maintain proper
session ceiling semantics. Add a parameter to the persistSessionExpiry() method
(such as preserveExistingWhenMissing with a default value) that controls whether
to preserve the old ceiling when the ID token omits the session_expiry claim.
Update the method implementation so it only preserves the existing value when
the parameter is true and the claim is missing, otherwise it replaces the value.
Then locate all callers of persistSessionExpiry() and determine if they
represent fresh login saves (pass false to clear stale ceilings) or refresh
saves (pass true to preserve the earliest ceiling).
In
`@auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt`:
- Line 3582: The verifyNoMoreInteractions(client) assertions are currently
positioned before the method call under test (manager.getCredentials(callback)),
which prevents them from validating the client usage triggered by that call.
Move the verifyNoMoreInteractions(client) assertions to execute after the
manager.getCredentials(callback) call in each affected test method so that the
mock verification can properly capture any unexpected client interactions. This
ordering issue exists in multiple test methods as indicated by the line
references, so apply the same reordering change to all affected test cases to
ensure consistent test validation.
---
Outside diff comments:
In
`@auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt`:
- Around line 135-155: Add session expiry ceiling checks in all refresh-token
paths within CredentialsManager to prevent returning tokens past the hard
session ceiling. Specifically, add isSessionExpired(...) guard checks in the
getSsoCredentials() and getApiCredentials() methods before returning cached
credentials, before invoking ssoExchange() and similar refresh requests, and
immediately after request.execute() completes but before invoking
saveSsoCredentials() or the success callback. This ensures the session_expiry
validation is consistently applied across all SSO, API, and standard credential
renewal flows rather than only at entry points.
In
`@auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt`:
- Around line 277-305: After the request.execute() call in the SSO exchange flow
within the serialExecutor block, add a session_expiry validation check before
the saveSsoCredentials(sessionCredentials) call to ensure the renewed
credentials respect the upstream ceiling limit, similar to the guard checks
performed in the secure getCredentials() path. Apply this same validation
pattern to the other credential renewal flows mentioned in related methods
around lines 897-954, 984-1019, and 1043-1079 to ensure all SSO and API refresh
token exchanges enforce the session_expiry ceiling both before serving cached
credentials and after obtaining renewed credentials.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

i️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ba3f07e7-b7b9-4e4d-b060-ca7213b5671f

📥 Commits

Reviewing files that changed from the base of the PR and between 06ec2a6 and 0616347.

📒 Files selected for processing (11)
  • EXAMPLES.md
  • auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/request/internal/Jwt.kt
  • auth0/src/main/java/com/auth0/android/result/Credentials.kt
  • auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt
  • auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt
  • auth0/src/test/java/com/auth0/android/request/internal/JwtTest.kt
  • auth0/src/test/java/com/auth0/android/result/CredentialsTest.kt

* Negative clock-skew leeway (seconds) applied when checking the `session_expiry` ceiling, so
* the session is treated as expired slightly *before* the wall-clock ceiling, never after.
*/
private const val SESSION_EXPIRY_LEEWAY_SECONDS = 30L

@pmathew92 pmathew92 Jun 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 30s is the agreed leeway across ?

@utkrishtsahu utkrishtsahu Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — 30s aligns implementation ,the ~30s negative leeway. The leeway is intentionally negative so we treat the session as expired slightly before the wall-clock ceiling, never after.

?: return false
// A non-positive ceiling is not a valid Unix timestamp; treat it as "no ceiling" rather than
// already-expired (mirrors the guard in [willExpire] for unset/migration values).
if (sessionExpiry <= 0) {

@pmathew92 pmathew92 Jun 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says non-positive but the check is against 0 also. Shouldn't this be sessionExpiry < 0

@utkrishtsahu utkrishtsahu Jun 19, 2026
edited
Loading

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — "non-positive" means <= 0. We exclude 0 (epoch 1970) too, because it's not a valid live ceiling and storage.retrieveLong(KEY_SESSION_EXPIRY) returns 0 for the unset/migration case. Treating <= 0 as "no ceiling" mirrors the existing guard in willExpire, so we don't log out users who never had a ceiling persisted.

throw CredentialsManagerException.INVALID_CREDENTIALS
}
// IPSIE session_expiry: reject a session already past its ceiling at creation time.
validateSessionExpiryAtCreation(credentials.idToken)

@pmathew92 pmathew92 Jun 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateSessionExpiryAtCreation now throws an exception . Update the comment and signature of saveCredentials for the same

@utkrishtsahu utkrishtsahu Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Added @throws(CredentialsManagerException::class) and documented that saveCredentials can now throw SESSION_EXPIRED (when the ID token is already past its session_expiry ceiling at creation) and INVALID_CREDENTIALS.

throw CredentialsManagerException.INVALID_CREDENTIALS
}
// IPSIE session_expiry: reject a session already past its ceiling at creation time.
validateSessionExpiryAtCreation(credentials.idToken)

@pmathew92 pmathew92 Jun 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@utkrishtsahu utkrishtsahu Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. saveCredentials here already had @throws; updated the KDoc to document the SESSION_EXPIRED case as well.

// IPSIE session_expiry: once the upstream-IdP ceiling passes, no valid credentials remain.
// The credentials blob is encrypted and cannot be decoded here, so the ceiling is read from
// the value persisted at login (KEY_SESSION_EXPIRY) via isSessionExpired(null).
if (isSessionExpired(null)) {

@pmathew92 pmathew92 Jun 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the intent here ? isSessionExpired(null) will always return false . So this check is completely redundant

@utkrishtsahu utkrishtsahu Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't always false. With a null idToken, isSessionExpired falls through to storage.retrieveLong(KEY_SESSION_EXPIRY) — the ceiling persisted at login. We pass null deliberately because the credentials blob is encrypted and can't be decoded in hasValidCredentials, so the persisted value is the only ceiling available here. Covered by the tests shouldFailWithSessionExpiredWithoutBiometricsOrRenewWhenStoredCeilingReached and ...WhenStoredCeilingFallsWithinLeeway, which assert this returns SESSION_EXPIRED from a stored ceiling.

* before the wall-clock ceiling, never after.
*/
protected fun isSessionExpired(idToken: String?): Boolean {
val sessionExpiry = sessionExpiryFromIdToken(idToken)

@pmathew92 pmathew92 Jun 22, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per the spec, the expiry time we get during login is the final value and subsequent token refreshes shouldn't overwrite this initial value. This check , currently considers the id token which might contain a new value than the initially stored one thus giving a higher ceiling. So shouldn't the check be something like

val sessionExpiry = storage.retrieveLong(KEY_SESSION_EXPIRY)
 ?: sessionExpiryFromIdToken(idToken)
 ?: return false

@kishore7snehil could you confirm this behaviour / condition ?

@utkrishtsahu utkrishtsahu Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, went with your suggestion. We now read the pinned value first and only fall back to the ID token claim, so a refresh can't bump the ceiling up.

val jwt = runCatching { jwtDecoder.decode(idToken) }.getOrNull() ?: return
val sessionExpiry = jwt.sessionExpiry ?: return
val issuedAtSeconds = jwt.issuedAt?.time?.div(1000) ?: return
if (sessionExpiry <= issuedAtSeconds) {

@kishore7snehil kishore7snehil Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creation-time check uses a bare sessionExpiry <= issuedAtSeconds, but the read-time check at line 348 applies the 30s leeway. So a ceiling 1-30s past iat saves here, then gets rejected on the next read. Should we apply the same leeway in both places so they agree?

@utkrishtsahu utkrishtsahu Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed. Both checks now use the same 30s leeway so they agree.

kishore7snehil reacted with thumbs up emoji
Comment thread auth0/src/main/java/com/auth0/android/request/internal/Jwt.kt Outdated
val jwt = Jwt(jwtWithPayload("""{"session_expiry":1700000000.75}"""))
assertThat(jwt, `is`(notNullValue()))

assertThat(jwt.sessionExpiry, `is`(1700000000L))

@kishore7snehil kishore7snehil Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The one gap is an implausibly large (milliseconds) value - worth a test pinning that it's treated as no ceiling, since that's the silent fail-open mode for this control.

@utkrishtsahu utkrishtsahu Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test for this — pins that a milliseconds value parses as null.

kishore7snehil reacted with thumbs up emoji
Comment thread auth0/src/main/java/com/auth0/android/result/Credentials.kt

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt`:
- Around line 340-343: In the isSessionExpired function, the issue is that
non-positive values retrieved from storage (such as 0 or negative sentinel
values) are treated as valid session expiry times instead of being treated as
"not set" and falling back to sessionExpiryFromIdToken(idToken). Modify the
storage retrieval logic to check if the retrieved sessionExpiry value is
non-positive (less than or equal to 0), and if so, treat it as null so the elvis
operator will proceed to evaluate sessionExpiryFromIdToken(idToken) as the
fallback. This same pattern should be applied to all other occurrences of this
storage retrieval pattern in the file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

i️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b0324fe-2a9c-4804-94d4-3c41674004f6

📥 Commits

Reviewing files that changed from the base of the PR and between bb44ffc and d0785cc.

📒 Files selected for processing (9)
  • auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/request/internal/Jwt.kt
  • auth0/src/main/java/com/auth0/android/result/Credentials.kt
  • auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt
  • auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt
  • auth0/src/test/java/com/auth0/android/request/internal/JwtTest.kt
  • auth0/src/test/java/com/auth0/android/result/CredentialsTest.kt
🚧 Files skipped from review as they are similar to previous changes (5)
  • auth0/src/test/java/com/auth0/android/request/internal/JwtTest.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt
  • auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt
  • auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt
  • auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt

Comment thread EXAMPLES.md
Comment thread EXAMPLES.md
Comment thread EXAMPLES.md

@kishore7snehil kishore7snehil left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

utkrishtsahu added a commit to auth0/Auth0.swift that referenced this pull request Jun 26, 2026
Closes the gap between the Swift session_expiry implementation and the
Android reference (auth0/Auth0.Android#983):
- Persist the session_expiry ceiling to the Keychain at store() time,
 pinned to the initial-login value and never raised by a refresh that
 re-emits a later claim.
- Resolve the ceiling pinned-value-first with the live ID token as a
 fallback, so a refreshed token that omits the claim still enforces it.
- Enforce on credentials(), ssoCredentials(), apiCredentials(), and
 hasValid(); clear the stored credentials when the ceiling is reached.
- clear() removes the persisted ceiling key.
- Parse the claim via NSNumber so a fractional value is truncated rather
 than dropped; reject implausible (>= 1e10) millisecond values.
- Add public Credentials.sessionExpiresAt (Unix seconds).
- Extend tests and docs (EXAMPLES.md, README.md, CHANGELOG.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@pmathew92 pmathew92 pmathew92 left review comments
@coderabbitai coderabbitai[bot] coderabbitai[bot] left review comments
@kishore7snehil kishore7snehil kishore7snehil approved these changes

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /