Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
],
"require": {
"php": ">=7.3.0",
"ext-curl": "*"
"ext-curl": "*",
"paragonie/halite": "^4.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.15|^3.6",
Expand Down
146 changes: 146 additions & 0 deletions lib/CookieSession.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

namespace WorkOS;

use WorkOS\Resource\SessionAuthenticationSuccessResponse;
use WorkOS\Resource\SessionAuthenticationFailureResponse;

/**
* Class CookieSession
*
* Handles encrypted session cookies for user authentication and session management.
*/
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 optionally rotates the cookie password.
*
* @param array $options Options for session refresh
* - 'organizationId' (string|null): Organization to scope the session to
* - 'cookiePassword' (string|null): New password for cookie rotation
*
* @return array{SessionAuthenticationSuccessResponse|SessionAuthenticationFailureResponse, string|null}
* Returns [response, newSealedSession]
* @throws Exception\WorkOSException
*/
public function refresh(array $options = [])
{
$organizationId = $options['organizationId'] ?? null;
$newCookiePassword = $options['cookiePassword'] ?? $this->cookiePassword;

// First authenticate to get the current session data
$authResult = $this->authenticate();

if (!$authResult->authenticated) {
return [$authResult, null];
}

// Use the refresh token to get new authentication tokens
try {
$refreshedAuth = $this->userManagement->authenticateWithRefreshToken(
WorkOS::getClientId(),
$authResult->refreshToken,
null,
null,
$organizationId
);

// Create new sealed session with refreshed data
$newSealedSession = $this->userManagement->sealSession(
[
'access_token' => $refreshedAuth->accessToken,
'refresh_token' => $refreshedAuth->refreshToken,
'session_id' => $authResult->sessionId
],
$newCookiePassword
);

// Build success response from refreshed auth
$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 [$successResponse, $newSealedSession];
} catch (\Exception $e) {
$failureResponse = new SessionAuthenticationFailureResponse(
SessionAuthenticationFailureResponse::REASON_INVALID_JWT
);
return [$failureResponse, null];
}
}

/**
* 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);
}
}
54 changes: 54 additions & 0 deletions lib/Resource/Session.php
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"
];
}
42 changes: 42 additions & 0 deletions lib/Resource/SessionAuthenticationFailureResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?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_INVALID_JWT = "INVALID_JWT";

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 = [];
}
}
102 changes: 102 additions & 0 deletions lib/Resource/SessionAuthenticationSuccessResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

namespace WorkOS\Resource;

/**
* Class SessionAuthenticationSuccessResponse.
*
* Represents a successful session authentication.
*
* @property bool $authenticated
* @property string $accessToken
* @property string $refreshToken
* @property string $sessionId
* @property User $user
* @property string|null $organizationId
* @property RoleResponse|null $role
* @property array|null $roles
* @property array|null $permissions
* @property array|null $entitlements
* @property array|null $featureFlags
* @property Impersonator|null $impersonator
* @property string $authenticationMethod
*/
class SessionAuthenticationSuccessResponse extends BaseWorkOSResource
{
public const RESOURCE_ATTRIBUTES = [
"authenticated",
"accessToken",
"refreshToken",
"sessionId",
"user",
"organizationId",
"role",
"roles",
"permissions",
"entitlements",
"featureFlags",
"impersonator",
"authenticationMethod"
];

public const RESPONSE_TO_RESOURCE_KEY = [
"authenticated" => "authenticated",
"access_token" => "accessToken",
"refresh_token" => "refreshToken",
"session_id" => "sessionId",
"user" => "user",
"organization_id" => "organizationId",
"role" => "role",
"roles" => "roles",
"permissions" => "permissions",
"entitlements" => "entitlements",
"feature_flags" => "featureFlags",
"impersonator" => "impersonator",
"authentication_method" => "authenticationMethod"
];

public static function constructFromResponse($response)
{
$instance = parent::constructFromResponse($response);

// Always set authenticated to true for success responses
$instance->values["authenticated"] = true;

// Construct User resource from user data
if (isset($response["user"])) {
$instance->values["user"] = User::constructFromResponse($response["user"]);
}

// Construct Role if present
if (isset($response["role"])) {
$instance->values["role"] = new RoleResponse($response["role"]["slug"]);
}

// Construct Roles array if present
if (isset($response["roles"])) {
$roles = [];
foreach ($response["roles"] as $role) {
$roles[] = new RoleResponse($role["slug"]);
}
$instance->values["roles"] = $roles;
}

// Construct FeatureFlags array if present
if (isset($response["feature_flags"])) {
$featureFlags = [];
foreach ($response["feature_flags"] as $flag) {
$featureFlags[] = FeatureFlag::constructFromResponse($flag);
}
$instance->values["featureFlags"] = $featureFlags;
}

// Construct Impersonator if present
if (isset($response["impersonator"])) {
$instance->values["impersonator"] = Impersonator::constructFromResponse(
$response["impersonator"]
);
}

return $instance;
}
}
Loading