fix(session): require valid JWT expiration - #442
Open
gjtorikian wants to merge 3 commits into
Open
Conversation
PKCEHelper's four flows (AuthKit/SSO authorization URL and code exchange) required callers to re-pass a clientId the WorkOS client already carries (constructor arg / WORKOS_CLIENT_ID). Make the parameter optional with a fallback to requireClientId(), aligning PHP with the other backend SDKs' override-with-fallback pattern. Explicit arguments still win; an unconfigured client now throws ConfigurationException instead of a TypeError. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed session tokens without a numeric expiration could bypass expiry validation indefinitely. Fail closed while preserving numeric-string compatibility, and reject tokens at the expiration boundary. Addresses VULN-1270.
Contributor
|
Comment on lines
+445
to
447
| if ((int) $decoded['exp'] <= time()) { | ||
| throw new \InvalidArgumentException('JWT has expired'); | ||
| } |
Contributor
There was a problem hiding this comment.
Fractional expirations end early
Casting exp to an integer before comparing it rejects valid fractional expirations during their final second. For example, when time() is 1700000000, an exp of 1700000000.5 is still in the future but is truncated to 1700000000 and returned as invalid_jwt. This matters because the test matrix explicitly treats floating-point expiration claims as supported.
Suggested change
| if ((int) $decoded['exp'] <= time()) { | |
| throw new \InvalidArgumentException('JWT has expired'); | |
| } | |
| if ((float) $decoded['exp'] <= time()) { | |
| throw new \InvalidArgumentException('JWT has expired'); | |
| } |
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: lib/SessionManager.php
Line: 445-447
Comment:
**Fractional expirations end early**
Casting `exp` to an integer before comparing it rejects valid fractional expirations during their final second. For example, when `time()` is `1700000000`, an `exp` of `1700000000.5` is still in the future but is truncated to `1700000000` and returned as `invalid_jwt`. This matters because the test matrix explicitly treats floating-point expiration claims as supported.
```suggestion
if ((float) $decoded['exp'] <= time()) {
throw new \InvalidArgumentException('JWT has expired');
}
```
**Knowledge Base Used:**
- [SSO and session management](https://app.greptile.com/workos/-/custom-context/knowledge-base/workos/workos-php/-/docs/sso-and-session-management.md)
- [Authentication and sessions](https://app.greptile.com/workos/-/custom-context/knowledge-base/workos/workos-php/-/docs/authentication.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
expis missing, null, or non-numeric, preventing signed session tokens from bypassing expiration checks.exp == time()boundary, while preserving numeric-string compatibility.invalid_jwtresponse.iss/aud/nbfvalidation is intentionally deferred. Issuer validation is tracked separately in feat: Add optional issuer check to SessionManager authenticate #440, and the existing issuer/audience TODO remains unchanged.Validation
composer cipasses: PHP-CS-Fixer reports 0 fixable files, PHPStan reports 0 errors, and PHPUnit reports 366 tests and 1,708 assertions. The two original missing-fixture skips are pre-existing and unrelated; currentmainadds a third (OrganizationsTest::testListItContacts). All 15 expiration cases pass.Current
mainwas merged without conflicts to exclude an inherited, already-merged PKCE change from the PR diff. Onlylib/SessionManager.php,tests/SessionManagerTest.php, andtests/Fixtures/session_expiration_clock.phpdiffer frommain.