-
Notifications
You must be signed in to change notification settings - Fork 18
Implement Authkit Sessions #315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
78aafc3
chore: add Halite encryption dependency
birdcar 133b8ef
feat: add Session resource model
birdcar ed7f007
feat: add session authentication response resources
birdcar 5638685
feat: add session encryption interface and Halite implementation
birdcar ffb5318
feat: add signing-only session handler
birdcar 05ac08c
feat: add session management to UserManagement
birdcar 36e0279
feat: add CookieSession class
birdcar 0666e78
fix: tighten exception handling to catch only HTTP errors
birdcar 1bbb38e
Add workflows for automatically releasing PHP
gjtorikian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| name: Release | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: | ||
| - closed | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| release: | ||
| if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'version-bump') | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Generate GitHub App token | ||
| id: app-token | ||
| uses: actions/create-github-app-token@v1 | ||
| with: | ||
| app-id: ${{ vars.SDK_BOT_APP_ID }} | ||
| private-key: ${{ secrets.SDK_BOT_PRIVATE_KEY }} | ||
|
|
||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Get version from Version.php | ||
| id: version | ||
| run: | | ||
| VERSION=$(grep -oP "SDK_VERSION = '\K[0-9]+\.[0-9]+\.[0-9]+" lib/Version.php) | ||
| echo "version=$VERSION" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Create Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| tag_name: ${{ steps.version.outputs.version }} | ||
| name: ${{ steps.version.outputs.version }} | ||
| generate_release_notes: true | ||
| make_latest: true |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| name: Version Bump | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| bump_type: | ||
| description: 'Version bump type' | ||
| required: true | ||
| type: choice | ||
| options: | ||
| - patch | ||
| - minor | ||
| - major | ||
|
|
||
| jobs: | ||
| bump-version: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Generate GitHub App token | ||
| id: app-token | ||
| uses: actions/create-github-app-token@v1 | ||
| with: | ||
| app-id: ${{ vars.SDK_BOT_APP_ID }} | ||
| private-key: ${{ secrets.SDK_BOT_PRIVATE_KEY }} | ||
|
|
||
| - name: Checkout repository | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
|
|
||
| - name: Configure Git | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
|
|
||
| - name: Bump version | ||
| id: bump | ||
| run: | | ||
| VERSION_FILE="lib/Version.php" | ||
| OLD_VERSION=$(grep -oP "SDK_VERSION = '\K[0-9]+\.[0-9]+\.[0-9]+" "$VERSION_FILE") | ||
|
|
||
| IFS='.' read -r MAJOR MINOR PATCH <<< "$OLD_VERSION" | ||
|
|
||
| case "${{ inputs.bump_type }}" in | ||
| major) | ||
| MAJOR=$((MAJOR + 1)) | ||
| MINOR=0 | ||
| PATCH=0 | ||
| ;; | ||
| minor) | ||
| MINOR=$((MINOR + 1)) | ||
| PATCH=0 | ||
| ;; | ||
| patch) | ||
| PATCH=$((PATCH + 1)) | ||
| ;; | ||
| esac | ||
|
|
||
| NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" | ||
|
|
||
| sed -i "s/SDK_VERSION = '$OLD_VERSION'/SDK_VERSION = '$NEW_VERSION'/" "$VERSION_FILE" | ||
|
|
||
| echo "old_version=$OLD_VERSION" >> $GITHUB_OUTPUT | ||
| echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT | ||
|
|
||
| - name: Create Pull Request | ||
| uses: peter-evans/create-pull-request@v7 | ||
| with: | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| branch: version-bump-${{ steps.bump.outputs.new_version }} | ||
| commit-message: "Bump version from ${{ steps.bump.outputs.old_version }} to ${{ steps.bump.outputs.new_version }}" | ||
| title: "Bump version to ${{ steps.bump.outputs.new_version }}" | ||
| body: | | ||
| This PR bumps the version from `${{ steps.bump.outputs.old_version }}` to `${{ steps.bump.outputs.new_version }}`. | ||
|
|
||
| **Bump type:** ${{ inputs.bump_type }} | ||
|
|
||
| --- | ||
| *This PR was automatically created by the version bump workflow.* | ||
| labels: version-bump |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| <?php | ||
|
|
||
| namespace WorkOS; | ||
|
|
||
| use WorkOS\Resource\SessionAuthenticationSuccessResponse; | ||
| use WorkOS\Resource\SessionAuthenticationFailureResponse; | ||
|
|
||
| /** | ||
| * Class CookieSession | ||
| * | ||
| * Handles encrypted session cookies for user authentication and session management. | ||
| * Matches workos-node CookieSession behavior - unsealing and validating sessions. | ||
| */ | ||
| class CookieSession | ||
| { | ||
| /** | ||
| * @var UserManagement | ||
| */ | ||
| private $userManagement; | ||
|
|
||
| /** | ||
| * @var string Encrypted session data | ||
| */ | ||
| private $sealedSession; | ||
|
|
||
| /** | ||
| * @var string Cookie encryption password | ||
| */ | ||
| private $cookiePassword; | ||
|
|
||
| /** | ||
| * Constructor. | ||
| * | ||
| * @param UserManagement $userManagement UserManagement instance | ||
| * @param string $sealedSession Encrypted session cookie data | ||
| * @param string $cookiePassword Password used to decrypt the session | ||
| */ | ||
| public function __construct( | ||
| UserManagement $userManagement, | ||
| string $sealedSession, | ||
| string $cookiePassword | ||
| ) { | ||
| $this->userManagement = $userManagement; | ||
| $this->sealedSession = $sealedSession; | ||
| $this->cookiePassword = $cookiePassword; | ||
| } | ||
|
|
||
| /** | ||
| * Authenticates the sealed session and returns user information. | ||
| * | ||
| * @return SessionAuthenticationSuccessResponse|SessionAuthenticationFailureResponse | ||
| * @throws Exception\WorkOSException | ||
| */ | ||
| public function authenticate() | ||
| { | ||
| return $this->userManagement->authenticateWithSessionCookie( | ||
| $this->sealedSession, | ||
| $this->cookiePassword | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Refreshes an expired session and returns new tokens. | ||
| * | ||
| * Note: This method returns raw tokens. The calling code (e.g., authkit-php) | ||
| * is responsible for sealing the tokens into a new session cookie. | ||
| * | ||
| * @param array $options Options for session refresh | ||
| * - 'organizationId' (string|null): Organization to scope the session to | ||
| * | ||
| * @return array{SessionAuthenticationSuccessResponse|SessionAuthenticationFailureResponse, array|null} | ||
| * Returns [response, newTokens] where newTokens contains: | ||
| * - 'access_token': The new access token | ||
| * - 'refresh_token': The new refresh token | ||
| * - 'session_id': The session ID | ||
| * Returns [failureResponse, null] on error. | ||
| * @throws Exception\WorkOSException | ||
| */ | ||
| public function refresh(array $options = []) | ||
| { | ||
| $organizationId = $options['organizationId'] ?? null; | ||
|
|
||
| // First authenticate to get the current session data | ||
| $authResult = $this->authenticate(); | ||
|
|
||
| if (!$authResult->authenticated) { | ||
| return [$authResult, null]; | ||
| } | ||
|
|
||
| // Tight try/catch for refresh token API call | ||
| try { | ||
| $refreshedAuth = $this->userManagement->authenticateWithRefreshToken( | ||
| WorkOS::getClientId(), | ||
| $authResult->refreshToken, | ||
| null, | ||
| null, | ||
| $organizationId | ||
| ); | ||
| } catch (Exception\BaseRequestException $e) { | ||
| $failureResponse = new SessionAuthenticationFailureResponse( | ||
| SessionAuthenticationFailureResponse::REASON_HTTP_ERROR | ||
| ); | ||
| return [$failureResponse, null]; | ||
| } | ||
|
|
||
| // Build success response | ||
| $successResponse = SessionAuthenticationSuccessResponse::constructFromResponse([ | ||
| 'authenticated' => true, | ||
| 'access_token' => $refreshedAuth->accessToken, | ||
| 'refresh_token' => $refreshedAuth->refreshToken, | ||
| 'session_id' => $authResult->sessionId, | ||
| 'user' => $refreshedAuth->user->raw, | ||
| 'organization_id' => $refreshedAuth->organizationId ?? $organizationId, | ||
| 'authentication_method' => $authResult->authenticationMethod | ||
| ]); | ||
|
|
||
| // Return raw tokens for the caller to seal | ||
| $newTokens = [ | ||
| 'access_token' => $refreshedAuth->accessToken, | ||
| 'refresh_token' => $refreshedAuth->refreshToken, | ||
| 'session_id' => $authResult->sessionId | ||
| ]; | ||
|
|
||
| return [$successResponse, $newTokens]; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the logout URL for the current session. | ||
| * | ||
| * @param array $options | ||
| * - 'returnTo' (string|null): URL to redirect to after logout | ||
| * | ||
| * @return string Logout URL | ||
| * @throws Exception\UnexpectedValueException | ||
| */ | ||
| public function getLogoutUrl(array $options = []) | ||
| { | ||
| $authResult = $this->authenticate(); | ||
|
|
||
| if (!$authResult->authenticated) { | ||
| throw new Exception\UnexpectedValueException( | ||
| "Cannot get logout URL for unauthenticated session" | ||
| ); | ||
| } | ||
|
|
||
| $returnTo = $options['returnTo'] ?? null; | ||
| return $this->userManagement->getLogoutUrl($authResult->sessionId, $returnTo); | ||
| } | ||
| } | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| <?php | ||
|
|
||
| namespace WorkOS\Resource; | ||
|
|
||
| /** | ||
| * Class Session. | ||
| * | ||
| * @property string $id | ||
| * @property string $userId | ||
| * @property string|null $ipAddress | ||
| * @property string|null $userAgent | ||
| * @property string|null $organizationId | ||
| * @property string $authenticationMethod | ||
| * @property string $status | ||
| * @property string $expiresAt | ||
| * @property string|null $endedAt | ||
| * @property string $createdAt | ||
| * @property string $updatedAt | ||
| * @property string $object | ||
| */ | ||
| class Session extends BaseWorkOSResource | ||
| { | ||
| public const RESOURCE_TYPE = "session"; | ||
|
|
||
| public const RESOURCE_ATTRIBUTES = [ | ||
| "id", | ||
| "userId", | ||
| "ipAddress", | ||
| "userAgent", | ||
| "organizationId", | ||
| "authenticationMethod", | ||
| "status", | ||
| "expiresAt", | ||
| "endedAt", | ||
| "createdAt", | ||
| "updatedAt", | ||
| "object" | ||
| ]; | ||
|
|
||
| public const RESPONSE_TO_RESOURCE_KEY = [ | ||
| "id" => "id", | ||
| "user_id" => "userId", | ||
| "ip_address" => "ipAddress", | ||
| "user_agent" => "userAgent", | ||
| "organization_id" => "organizationId", | ||
| "authentication_method" => "authenticationMethod", | ||
| "status" => "status", | ||
| "expires_at" => "expiresAt", | ||
| "ended_at" => "endedAt", | ||
| "created_at" => "createdAt", | ||
| "updated_at" => "updatedAt", | ||
| "object" => "object" | ||
| ]; | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| <?php | ||
|
|
||
| namespace WorkOS\Resource; | ||
|
|
||
| /** | ||
| * Class SessionAuthenticationFailureResponse. | ||
| * | ||
| * Represents a failed session authentication. | ||
| * | ||
| * @property bool $authenticated | ||
| * @property string $reason | ||
| */ | ||
| class SessionAuthenticationFailureResponse extends BaseWorkOSResource | ||
| { | ||
| public const REASON_NO_SESSION_COOKIE_PROVIDED = "NO_SESSION_COOKIE_PROVIDED"; | ||
| public const REASON_INVALID_SESSION_COOKIE = "INVALID_SESSION_COOKIE"; | ||
| public const REASON_ENCRYPTION_ERROR = "ENCRYPTION_ERROR"; | ||
| public const REASON_HTTP_ERROR = "HTTP_ERROR"; | ||
|
|
||
| public const RESOURCE_ATTRIBUTES = [ | ||
| "authenticated", | ||
| "reason" | ||
| ]; | ||
|
|
||
| public const RESPONSE_TO_RESOURCE_KEY = [ | ||
| "authenticated" => "authenticated", | ||
| "reason" => "reason" | ||
| ]; | ||
|
|
||
| /** | ||
| * Construct a failure response with a specific reason. | ||
| * | ||
| * @param string $reason Reason for authentication failure | ||
| */ | ||
| public function __construct(string $reason) | ||
| { | ||
| $this->values = [ | ||
| "authenticated" => false, | ||
| "reason" => $reason | ||
| ]; | ||
| $this->raw = []; | ||
| } | ||
| } |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.