Skip to content

Conversation

@birdcar
Copy link

@birdcar birdcar commented Jan 5, 2026

Description

This PR implements AuthKit Sessions support for the PHP SDK, bringing it to parity with the Node, Python, and Ruby sdks.

Documentation

Does this require changes to the WorkOS Docs? E.g. the API Reference or code snippets need updates.

[x] Yes

If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required.

@birdcar birdcar self-assigned this Jan 5, 2026
@birdcar birdcar marked this pull request as ready for review January 5, 2026 21:52
@birdcar birdcar requested a review from a team as a code owner January 5, 2026 21:52
@birdcar birdcar requested a review from dandorman January 5, 2026 21:52
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 5, 2026

Greptile Summary

This PR successfully implements AuthKit Sessions support for the PHP SDK, bringing it to parity with Node, Python, and Ruby SDKs. The implementation adds secure cookie-based session management with encrypted session storage using the Paragonie Halite library (libsodium).

Key Changes:

  • Added CookieSession class for managing encrypted session cookies with authenticate, refresh, and logout methods
  • Implemented HaliteSessionEncryption using libsodium with HKDF key derivation and TTL validation
  • Added session management methods to UserManagement: listSessions, revokeSession, sealSession, authenticateWithSessionCookie, loadSealedSession, getSessionFromCookie
  • Created resource models: Session, SessionAuthenticationSuccessResponse, SessionAuthenticationFailureResponse
  • Added comprehensive test coverage including a regression test for the previously reported parameter bug

Bug Fix:

  • Fixed the issue reported in previous review where CookieSession.refresh() was passing only 2 parameters to authenticateWithRefreshToken() instead of the required 5 parameters. A regression test (testRefreshPassesCorrectParametersToAuthenticateWithRefreshToken) was added to prevent this bug from reoccurring.

Security:

  • Uses industry-standard libsodium encryption via Halite
  • Implements proper key derivation with HKDF
  • Session data includes TTL validation to prevent expired session usage
  • No sensitive data logging violations detected
  • Sealed sessions contain access_token and refresh_token which are properly encrypted

Confidence Score: 5/5

  • This PR is safe to merge with high confidence - the implementation is secure, well-tested, and the previously reported bug has been fixed with a regression test
  • Score of 5 reflects: (1) comprehensive test coverage including unit tests and regression tests, (2) secure encryption implementation using industry-standard libsodium, (3) proper key derivation with HKDF, (4) the previously reported parameter bug has been fixed and protected with a regression test, (5) no security violations found against custom rules, (6) clean implementation following existing SDK patterns, (7) proper error handling with typed failure responses
  • No files require special attention - all files show high-quality implementation with proper testing

Important Files Changed

Filename Overview
lib/Session/HaliteSessionEncryption.php Implements secure session encryption with libsodium, HKDF key derivation, and TTL validation
lib/Resource/SessionAuthenticationFailureResponse.php Failure response with typed reasons (NO_SESSION_COOKIE_PROVIDED, INVALID_SESSION_COOKIE, INVALID_JWT)
lib/Resource/SessionAuthenticationSuccessResponse.php Success response with user data, tokens, roles, permissions, and nested resource construction
lib/CookieSession.php Session management class with authenticate, refresh, and logout methods. Previous parameter bug fixed with regression test.
lib/UserManagement.php Added session methods: listSessions, revokeSession, sealSession, authenticateWithSessionCookie, loadSealedSession, getSessionFromCookie

Sequence Diagram

sequenceDiagram
    participant Client
    participant App as PHP Application
    participant CookieSession
    participant UserManagement
    participant Encryption as HaliteSessionEncryption
    participant API as WorkOS API

    Note over Client,API: Initial Authentication & Session Creation
    Client->>App: Login request
    App->>API: authenticateWithPassword/authenticateWithCode
    API-->>App: AuthenticationResponse (access_token, refresh_token)
    App->>UserManagement: sealSession(sessionData, cookiePassword)
    UserManagement->>Encryption: seal(data, password, ttl)
    Encryption->>Encryption: deriveKey(password) via HKDF
    Encryption->>Encryption: encrypt with libsodium
    Encryption-->>UserManagement: sealed session string
    UserManagement-->>App: sealed session
    App->>Client: Set encrypted cookie

    Note over Client,API: Session Authentication
    Client->>App: Request with session cookie
    App->>UserManagement: getSessionFromCookie(cookiePassword)
    UserManagement->>CookieSession: new CookieSession(userMgmt, sealedSession, password)
    App->>CookieSession: authenticate()
    CookieSession->>UserManagement: authenticateWithSessionCookie(sealedSession, password)
    UserManagement->>Encryption: unseal(sealedSession, password)
    Encryption->>Encryption: decrypt with libsodium
    Encryption->>Encryption: validate TTL
    Encryption-->>UserManagement: session data (access_token, refresh_token)
    UserManagement->>API: POST /sessions/authenticate
    API-->>UserManagement: SessionAuthenticationSuccessResponse
    UserManagement-->>CookieSession: Success/Failure response
    CookieSession-->>App: Success/Failure response
    App-->>Client: Authenticated request

    Note over Client,API: Session Refresh
    App->>CookieSession: refresh(options)
    CookieSession->>CookieSession: authenticate() to get current session
    CookieSession->>UserManagement: authenticateWithRefreshToken(clientId, refreshToken, ip, ua, orgId)
    UserManagement->>API: POST /authenticate with refresh_token
    API-->>UserManagement: New access_token & refresh_token
    UserManagement-->>CookieSession: AuthenticationResponse
    CookieSession->>UserManagement: sealSession(newData, newPassword)
    UserManagement->>Encryption: seal(newData, newPassword)
    Encryption-->>UserManagement: new sealed session
    UserManagement-->>CookieSession: new sealed session
    CookieSession-->>App: [SuccessResponse, newSealedSession]
    App->>Client: Update cookie with new sealed session

    Note over Client,API: Session Logout
    App->>CookieSession: getLogoutUrl(returnTo)
    CookieSession->>CookieSession: authenticate() to get sessionId
    CookieSession->>UserManagement: getLogoutUrl(sessionId, returnTo)
    UserManagement-->>CookieSession: logout URL
    CookieSession-->>App: logout URL
    App->>Client: Redirect to logout URL
    Client->>API: GET /sessions/logout
    API-->>Client: Redirect to returnTo URL
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

11 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@birdcar
Copy link
Author

birdcar commented Jan 6, 2026

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

11 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@birdcar birdcar added the enhancement New feature or request label Jan 6, 2026
@birdcar birdcar force-pushed the birdcar/session-implementation branch from 8ea745b to c80a0ed Compare January 6, 2026 23:57
@birdcar birdcar force-pushed the birdcar/session-implementation branch from c80a0ed to 19d649b Compare January 7, 2026 00:11
@birdcar
Copy link
Author

birdcar commented Jan 7, 2026

This doesn't have to wait, but it would likely be beneficial to wait until #316 merges so I can update the new list* methods to use it.

Copy link

@dandorman dandorman left a comment

Choose a reason for hiding this comment

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

This seems good to me, outside of a couple nits. (And i am woefully out of date on my PHP knowledge, so take anything i say with a grain of salt.)

The code looks good, Halite seems like a reasonable choice for an encryption library. I think that is my sole caveat with this PR. It does indeed bring the PHP SDK closer to how the others operate, but that includes the sealed session stuff i think we're hoping to deprecate. Most frameworks provide their own way to encrypt and/or sign cookies, there's little reason for us to add our own layer. Additionally, this creates another island of un-interoperability, where sessions sealed in one platform can't easily be unsealed in another. (cc @cmatheson , who has more thoughts/knowledge on the matter)

Would it be possible to make the sealed-session stuff optional? I don't want to overburden you, but @nicknisi has done some excellent work at paving the way to make session backing more plugin in the JS world.

But if we're not interested in pursuing any of that now, let me know. I don't want to hold this up if we'd rather get parity.

return Resource\SessionAuthenticationSuccessResponse::constructFromResponse($response);
} catch (\Exception $e) {
return new Resource\SessionAuthenticationFailureResponse(
Resource\SessionAuthenticationFailureResponse::REASON_INVALID_SESSION_COOKIE

Choose a reason for hiding this comment

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

i think that, e.g., the refresh request could fail for other reasons—at least the HTTP call. Might be worth making the try/catch block more tightly around the session unsealing?

return [$successResponse, $newSealedSession];
} catch (\Exception $e) {
$failureResponse = new SessionAuthenticationFailureResponse(
SessionAuthenticationFailureResponse::REASON_INVALID_JWT

Choose a reason for hiding this comment

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

i think this could fail for reasons besides an invalid JWT. Is it worth tightening the scope of the try/catch block here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Development

Successfully merging this pull request may close these issues.

3 participants