├── LICENSE ├── README.md ├── composer.json └── lib ├── AuditLogs.php ├── Client.php ├── DirectorySync.php ├── Exception ├── AuthenticationException.php ├── AuthorizationException.php ├── BadRequestException.php ├── BaseRequestException.php ├── ConfigurationException.php ├── GenericException.php ├── NotFoundException.php ├── ServerException.php ├── UnexpectedValueException.php └── WorkOSException.php ├── MFA.php ├── Organizations.php ├── Passwordless.php ├── Portal.php ├── RequestClient ├── CurlRequestClient.php └── RequestClientInterface.php ├── Resource ├── AuditLogCreateEventStatus.php ├── AuditLogExport.php ├── AuthenticationChallengeSms.php ├── AuthenticationChallengeTotp.php ├── AuthenticationFactorAndChallengeTotp.php ├── AuthenticationFactorSms.php ├── AuthenticationFactorTotp.php ├── AuthenticationResponse.php ├── BaseWorkOSResource.php ├── Connection.php ├── ConnectionType.php ├── Directory.php ├── DirectoryGroup.php ├── DirectoryUser.php ├── Domain.php ├── EmailVerification.php ├── Impersonator.php ├── Invitation.php ├── MagicAuth.php ├── Order.php ├── Organization.php ├── OrganizationMembership.php ├── PasswordReset.php ├── PasswordlessSession.php ├── PortalLink.php ├── Profile.php ├── ProfileAndToken.php ├── Response.php ├── Role.php ├── RoleResponse.php ├── User.php ├── UserAndToken.php ├── UserAuthenticationFactorTotp.php ├── UserResponse.php ├── VerificationChallenge.php ├── Webhook.php ├── WebhookResponse.php ├── WidgetScope.php └── WidgetTokenResponse.php ├── SSO.php ├── UserManagement.php ├── Util └── Request.php ├── Version.php ├── Webhook.php ├── Widgets.php └── WorkOS.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WorkOS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WorkOS PHP Library 2 | 3 | ![Packagist Version](https://img.shields.io/packagist/v/workos/workos-php) 4 | [![CI](https://github.com/workos/workos-php/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/workos/workos-php/actions/workflows/ci.yml) 5 | 6 | The WorkOS library for PHP provides convenient access to the WorkOS API from applications written in PHP. 7 | 8 | ## Documentation 9 | 10 | See the [API Reference](https://workos.com/docs/reference/client-libraries) for PHP usage examples. 11 | 12 | ## Installation 13 | 14 | To install via composer, run the following: 15 | 16 | ``` 17 | composer require workos/workos-php 18 | ``` 19 | 20 | ## Configuration 21 | 22 | The package will need to be configured with your [API Key](https://dashboard.workos.com/api-keys) and [Client ID](https://dashboard.workos.com/configuration). By default, the packages looks for a `WORKOS_API_KEY` and `WORKOS_CLIENT_ID` environment variable. 23 | 24 | ## SDK Versioning 25 | 26 | For our SDKs WorkOS follows a Semantic Versioning ([SemVer](https://semver.org/)) process where all releases will have a version X.Y.Z (like 1.0.0) pattern wherein Z would be a bug fix (e.g., 1.0.1), Y would be a minor release (1.1.0) and X would be a major release (2.0.0). We permit any breaking changes to only be released in major versions and strongly recommend reading changelogs before making any major version upgrades. 27 | 28 | ## Beta Releases 29 | 30 | WorkOS has features in Beta that can be accessed via Beta releases. We would love for you to try these 31 | and share feedback with us before these features reach general availability (GA). To install a Beta version, 32 | please follow the [installation steps](#installation) above using the Beta release version. 33 | 34 | > Note: there can be breaking changes between Beta versions. Therefore, we recommend pinning the package version to a 35 | > specific version. This way you can install the same version each time without breaking changes unless you are 36 | > intentionally looking for the latest Beta version. 37 | 38 | We highly recommend keeping an eye on when the Beta feature you are interested in goes from Beta to stable so that you 39 | can move to using the stable version. 40 | 41 | ## More Information 42 | 43 | - [Single Sign-On Guide](https://workos.com/docs/sso/guide) 44 | - [Directory Sync Guide](https://workos.com/docs/directory-sync/guide) 45 | - [Admin Portal Guide](https://workos.com/docs/admin-portal/guide) 46 | - [Magic Link Guide](https://workos.com/docs/magic-link/guide) 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "workos/workos-php", 3 | "description": "WorkOS PHP Library", 4 | "homepage": "https://workos.com", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "WorkOS", 9 | "email": "eng@workos.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.3.0", 14 | "ext-curl": "*" 15 | }, 16 | "require-dev": { 17 | "friendsofphp/php-cs-fixer": "^2.15|^3.6", 18 | "phpunit/phpunit": "^9" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "WorkOS\\": "lib/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "WorkOS\\": [ 28 | "tests/", 29 | "tests/WorkOS/" 30 | ] 31 | } 32 | }, 33 | "scripts": { 34 | "clean": "rm -rf composer.lock vendor/", 35 | "format": "php vendor/bin/php-cs-fixer fix -v --using-cache=no .", 36 | "format-check": "php vendor/bin/php-cs-fixer fix -v --dry-run --using-cache=no .", 37 | "test": "php vendor/bin/phpunit tests" 38 | }, 39 | "config": { 40 | "allow-plugins": { 41 | "workos-php/*": true, 42 | "kylekatarnls/update-helper": true 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/AuditLogs.php: -------------------------------------------------------------------------------- 1 | $organizationId, 48 | "event" => $event 49 | ]; 50 | 51 | $headers = [ 52 | "idempotency_key" => $idempotencyKey 53 | ]; 54 | 55 | $response = Client::request(Client::METHOD_POST, $eventsPath, $headers, $params, true); 56 | 57 | return Resource\AuditLogCreateEventStatus::constructFromResponse($response); 58 | } 59 | 60 | /** 61 | * @param array $auditLogExportOptions Associative array containing the keys detailed below 62 | * @var null|string $organizationId Description of the record. 63 | * @var null|string $rangeStart ISO-8601 Timestamp of the start of Export's the date range. 64 | * @var null|string $rangeEnd ISO-8601 Timestamp of the end of Export's the date range. 65 | * @var null|array $actions Actions that Audit Log Events will be filtered by. 66 | * @var null|array $actors Actor names that Audit Log Events will be filtered by. @deprecated 3.3.0 Use $actorNames instead. This method will be removed in a future major version. 67 | * @var null|array $targets Target types that Audit Log Events will be filtered by. 68 | * @var null|array $actorNames Actor names that Audit Log Events will be filtered by. 69 | * @var null|array $actorIds Actor IDs that Audit Log Events will be filtered by. 70 | * 71 | * @throws Exception\WorkOSException 72 | * 73 | * @return Resource\AuditLogExport 74 | */ 75 | 76 | public function createExport($organizationId, $rangeStart, $rangeEnd, $actions = null, $actors = null, $targets = null, $actorNames = null, $actorIds = null) 77 | { 78 | $createExportPath = "audit_logs/exports"; 79 | 80 | $params = [ 81 | "organization_id" => $organizationId, 82 | "range_end" => $rangeEnd, 83 | "range_start" => $rangeStart 84 | ]; 85 | 86 | if (!is_null($actions)) { 87 | $params["actions"] = $actions; 88 | }; 89 | 90 | if (!is_null($actors)) { 91 | $msg = "'actors' is deprecated. Please use 'actorNames' instead'"; 92 | 93 | error_log($msg); 94 | 95 | $params["actors"] = $actors; 96 | }; 97 | 98 | if (!is_null($actorNames)) { 99 | $params["actor_names"] = $actorNames; 100 | }; 101 | 102 | if (!is_null($actorIds)) { 103 | $params["actor_ids"] = $actorIds; 104 | }; 105 | 106 | if (!is_null($targets)) { 107 | $params["targets"] = $targets; 108 | }; 109 | 110 | $response = Client::request(Client::METHOD_POST, $createExportPath, null, $params, true); 111 | return Resource\AuditLogExport::constructFromResponse($response); 112 | } 113 | 114 | /** 115 | * @param string $auditLogExportId Unique identifier of the Audit Log Export 116 | * 117 | * @throws Exception\WorkOSException 118 | * 119 | * @return Resource\AuditLogExport 120 | */ 121 | public function getExport($auditLogExportId) 122 | { 123 | $getExportPath = "audit_logs/exports/{$auditLogExportId}"; 124 | 125 | $response = Client::request(Client::METHOD_GET, $getExportPath, null, null, true); 126 | 127 | return Resource\AuditLogExport::constructFromResponse($response); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/Client.php: -------------------------------------------------------------------------------- 1 | 48 | */ 49 | public static function request($method, $path, $headers = null, $params = null, $withAuth = false) 50 | { 51 | $url = self::generateUrl($path); 52 | 53 | $requestHeaders = self::generateBaseHeaders($withAuth); 54 | if ($headers) { 55 | $requestHeaders = \array_merge($requestHeaders, $headers); 56 | } 57 | 58 | list($result, $responseHeaders, $responseCode) = self::requestClient()->request( 59 | $method, 60 | $url, 61 | $requestHeaders, 62 | $params 63 | ); 64 | $response = new Resource\Response($result, $responseHeaders, $responseCode); 65 | 66 | if ($responseCode >= 400) { 67 | if ($responseCode >= 500) { 68 | throw new Exception\ServerException($response); 69 | } elseif ($responseCode === 401) { 70 | throw new Exception\AuthenticationException($response); 71 | } elseif ($responseCode === 403) { 72 | throw new Exception\AuthorizationException($response); 73 | } elseif ($responseCode === 404) { 74 | throw new Exception\NotFoundException($response); 75 | } 76 | 77 | throw new Exception\BadRequestException($response); 78 | } 79 | 80 | return $response->json(); 81 | } 82 | 83 | /** 84 | * Generate base headers for request. 85 | * 86 | * @param boolean $withAuth return with authorization header if true 87 | * 88 | * @return array 89 | */ 90 | public static function generateBaseHeaders($withAuth = false) 91 | { 92 | $baseHeaders = ["User-Agent: " . WorkOS::getIdentifier() . "/" . WORKOS::getVersion()]; 93 | if ($withAuth) { 94 | \array_push($baseHeaders, "Authorization: Bearer " . WorkOS::getApikey()); 95 | } 96 | 97 | return $baseHeaders; 98 | } 99 | 100 | /** 101 | * Generates a URL to the WorkOS API. 102 | * 103 | * @param string $path Path to the WorkOS resource 104 | * @param null|array $params Associative array to be passed as query parameters 105 | * 106 | * @return string 107 | */ 108 | public static function generateUrl($path, $params = null) 109 | { 110 | $url = WorkOS::getApiBaseUrl() . $path; 111 | 112 | if (is_array($params) && !empty($params)) { 113 | $queryParams = http_build_query($params); 114 | $url .= "?" . $queryParams; 115 | } 116 | 117 | return $url; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/DirectorySync.php: -------------------------------------------------------------------------------- 1 | $limit, 41 | "before" => $before, 42 | "after" => $after, 43 | "domain" => $domain, 44 | "search" => $search, 45 | "organization_id" => $organizationId, 46 | "order" => $order 47 | ]; 48 | 49 | $response = Client::request( 50 | Client::METHOD_GET, 51 | $directoriesPath, 52 | null, 53 | $params, 54 | true 55 | ); 56 | 57 | $directories = []; 58 | list($before, $after) = Util\Request::parsePaginationArgs($response); 59 | foreach ($response["data"] as $responseData) { 60 | \array_push($directories, Resource\Directory::constructFromResponse($responseData)); 61 | } 62 | 63 | return [$before, $after, $directories]; 64 | } 65 | 66 | /** 67 | * List Directory Groups. 68 | * 69 | * @param null|string $directory Directory ID 70 | * @param null|string $user Directory User ID 71 | * @param int $limit Maximum number of records to return 72 | * @param null|string $before Directory Group ID to look before 73 | * @param null|string $after Directory Group ID to look after 74 | * @param Resource\Order $order The Order in which to paginate records 75 | * 76 | * @throws Exception\WorkOSException 77 | * 78 | * @return array{?string, ?string, Resource\DirectoryGroup[]} An array containing the Directory Group ID to use as before and after cursor, and an array of Directory Group instances 79 | */ 80 | public function listGroups( 81 | $directory = null, 82 | $user = null, 83 | $limit = self::DEFAULT_PAGE_SIZE, 84 | $before = null, 85 | $after = null, 86 | $order = null 87 | ) { 88 | $groupsPath = "directory_groups"; 89 | 90 | $params = [ 91 | "limit" => $limit, 92 | "before" => $before, 93 | "after" => $after, 94 | "order" => $order 95 | ]; 96 | if ($directory) { 97 | $params["directory"] = $directory; 98 | } 99 | if ($user) { 100 | $params["user"] = $group; 101 | } 102 | 103 | $response = Client::request( 104 | Client::METHOD_GET, 105 | $groupsPath, 106 | null, 107 | $params, 108 | true 109 | ); 110 | 111 | $groups = []; 112 | list($before, $after) = Util\Request::parsePaginationArgs($response); 113 | foreach ($response["data"] as $response) { 114 | \array_push($groups, Resource\DirectoryGroup::constructFromResponse($response)); 115 | } 116 | 117 | return [$before, $after, $groups]; 118 | } 119 | 120 | /** 121 | * Get a Directory Group. 122 | * 123 | * @param string $directoryGroup Directory Group ID 124 | * 125 | * @throws Exception\WorkOSException 126 | * 127 | * @return Resource\DirectoryGroup 128 | */ 129 | public function getGroup($directoryGroup) 130 | { 131 | $groupPath = "directory_groups/{$directoryGroup}"; 132 | 133 | $response = Client::request( 134 | Client::METHOD_GET, 135 | $groupPath, 136 | null, 137 | null, 138 | true 139 | ); 140 | 141 | return Resource\DirectoryGroup::constructFromResponse($response); 142 | } 143 | 144 | /** 145 | * List Directory Users. 146 | * 147 | * @param null|string $directory Directory ID 148 | * @param null|string $group Directory Group ID 149 | * @param int $limit Maximum number of records to return 150 | * @param null|string $before Directory User ID to look before 151 | * @param null|string $after Directory User ID to look after 152 | * @param Resource\Order $order The Order in which to paginate records 153 | * 154 | * @return array{?string, ?string, Resource\DirectoryUser[]} An array containing the Directory User ID to use as before and after cursor, and an array of Directory User instances 155 | * 156 | * @throws Exception\WorkOSException 157 | */ 158 | public function listUsers( 159 | $directory = null, 160 | $group = null, 161 | $limit = self::DEFAULT_PAGE_SIZE, 162 | $before = null, 163 | $after = null, 164 | $order = null 165 | ) { 166 | $usersPath = "directory_users"; 167 | 168 | $params = [ 169 | "limit" => $limit, 170 | "before" => $before, 171 | "after" => $after, 172 | "order" => $order 173 | ]; 174 | if ($directory) { 175 | $params["directory"] = $directory; 176 | } 177 | if ($group) { 178 | $params["group"] = $group; 179 | } 180 | 181 | $response = Client::request( 182 | Client::METHOD_GET, 183 | $usersPath, 184 | null, 185 | $params, 186 | true 187 | ); 188 | 189 | $users = []; 190 | list($before, $after) = Util\Request::parsePaginationArgs($response); 191 | foreach ($response["data"] as $response) { 192 | \array_push($users, Resource\DirectoryUser::constructFromResponse($response)); 193 | } 194 | 195 | return [$before, $after, $users]; 196 | } 197 | 198 | /** 199 | * Get a Directory User. 200 | * 201 | * @param string $directoryUser Directory User ID 202 | * 203 | * @throws Exception\WorkOSException 204 | * 205 | * @return Resource\DirectoryUser 206 | */ 207 | public function getUser($directoryUser) 208 | { 209 | $userPath = "directory_users/{$directoryUser}"; 210 | 211 | $response = Client::request( 212 | Client::METHOD_GET, 213 | $userPath, 214 | null, 215 | null, 216 | true 217 | ); 218 | 219 | return Resource\DirectoryUser::constructFromResponse($response); 220 | } 221 | 222 | /** 223 | * Delete a Directory. 224 | * 225 | * @param string $directory Directory ID 226 | * 227 | * @throws Exception\WorkOSException 228 | * 229 | * @return Resource\Response 230 | */ 231 | public function deleteDirectory($directory) 232 | { 233 | $directoryPath = "directories/{$directory}"; 234 | 235 | $response = Client::request( 236 | Client::METHOD_DELETE, 237 | $directoryPath, 238 | null, 239 | null, 240 | true 241 | ); 242 | 243 | return $response; 244 | } 245 | 246 | /** 247 | * Get a Directory. 248 | * 249 | * @param string $directory WorkOS directory ID 250 | * 251 | * @throws Exception\WorkOSException 252 | * 253 | * @return Resource\Directory 254 | */ 255 | public function getDirectory($directory) 256 | { 257 | $directoriesPath = "directories/{$directory}"; 258 | 259 | $response = Client::request(Client::METHOD_GET, $directoriesPath, null, null, true); 260 | 261 | return Resource\Directory::constructFromResponse($response); 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /lib/Exception/AuthenticationException.php: -------------------------------------------------------------------------------- 1 | response = $response; 31 | 32 | $responseJson = $response->json(); 33 | 34 | if (!empty($responseJson["error"])) { 35 | $this->responseError = $responseJson["error"]; 36 | } 37 | if (!empty($responseJson["error_description"])) { 38 | $this->responseErrorDescription = $responseJson["error_description"]; 39 | } 40 | if (!empty($responseJson["errors"])) { 41 | $this->responseErrors = $responseJson["errors"]; 42 | } 43 | if (!empty($responseJson["code"])) { 44 | $this->responseCode = $responseJson["code"]; 45 | } 46 | if (!empty($responseJson["message"])) { 47 | $this->responseMessage = $responseJson["message"]; 48 | } 49 | 50 | $this->filterResponseForException($response); 51 | 52 | if (isset($message)) { 53 | $this->message = $message; 54 | } 55 | } 56 | 57 | private function filterResponseForException($response) 58 | { 59 | try { 60 | $responseBody = $response->body; 61 | 62 | $this->message = $responseBody; 63 | } catch (\Exception $e) { 64 | $this->message = ""; 65 | } 66 | 67 | if (\array_key_exists("x-request-id", $response->headers)) { 68 | $this->requestId = $response->headers["x-request-id"]; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/Exception/ConfigurationException.php: -------------------------------------------------------------------------------- 1 | message = $message; 23 | 24 | if (!empty($data)) { 25 | $this->data = $data; 26 | } else { 27 | $this->data = array(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/Exception/NotFoundException.php: -------------------------------------------------------------------------------- 1 | $type, 52 | "totp_issuer" => $totpIssuer, 53 | "totp_user" => $totpUser, 54 | "phone_number" => $phoneNumber 55 | ]; 56 | $response = Client::request( 57 | Client::METHOD_POST, 58 | $enrollPath, 59 | null, 60 | $params, 61 | true 62 | ); 63 | 64 | if ($type == "totp") { 65 | return Resource\AuthenticationFactorTotp::constructFromResponse($response); 66 | } elseif ($type == "sms") { 67 | return Resource\AuthenticationFactorSms::constructFromResponse($response); 68 | } 69 | } 70 | 71 | 72 | /** 73 | * Initiates the authentication process (a challenge) for an authentication factor 74 | * 75 | * @param string $authenticationFactorId - ID of the authentication factor 76 | * @param string|null $smsTemplate - Optional parameter to customize the message for sms type factors. Must include "{{code}}" if used. 77 | * 78 | * @return Resource\AuthenticationChallengeTotp|Resource\AuthenticationChallengeSms 79 | */ 80 | public function challengeFactor( 81 | $authenticationFactorId, 82 | $smsTemplate = null 83 | ) { 84 | if (!isset($authenticationFactorId)) { 85 | $msg = "Incomplete arguments: 'authentication_factor_id' is a required parameter"; 86 | throw new Exception\UnexpectedValueException($msg); 87 | } 88 | 89 | $challengePath = "auth/factors/{$authenticationFactorId}/challenge"; 90 | 91 | $params = [ 92 | "sms_template" => $smsTemplate 93 | ]; 94 | 95 | $response = Client::request( 96 | Client::METHOD_POST, 97 | $challengePath, 98 | null, 99 | $params, 100 | true 101 | ); 102 | if (isset($response['expires_at'])) { 103 | return Resource\AuthenticationChallengeSms::constructFromResponse($response); 104 | } else { 105 | return Resource\AuthenticationChallengeTotp::constructFromResponse($response); 106 | } 107 | } 108 | 109 | 110 | /** 111 | * @deprecated 1.12.0 Use `verifyChallenge` instead. This method will be removed in a future major version. 112 | * Verifies the one time password provided by the end-user. 113 | * 114 | * @param string $authenticationChallengeId - The ID of the authentication challenge that provided the user the verification code. 115 | * @param string $code - The verification code sent to and provided by the end user. 116 | */ 117 | 118 | public function verifyFactor( 119 | $authenticationChallengeId, 120 | $code 121 | ) { 122 | if (!isset($authenticationChallengeId) || !isset($code)) { 123 | $msg = "Incomplete arguments: 'authenticationChallengeId' and 'code' are required parameters"; 124 | throw new Exception\UnexpectedValueException($msg); 125 | } 126 | 127 | $msg = "'verifyFactor' is deprecated. Please use 'verifyChallenge' instead"; 128 | 129 | error_log($msg); 130 | 131 | $response = (new \WorkOS\MFA()) 132 | ->verifyChallenge( 133 | $authenticationChallengeId, 134 | $code 135 | ); 136 | 137 | return $response; 138 | } 139 | 140 | 141 | /** 142 | * Verifies the one time password provided by the end-user. 143 | * 144 | * @param string $authenticationChallengeId - The ID of the authentication challenge that provided the user the verification code. 145 | * @param string $code - The verification code sent to and provided by the end user. 146 | * 147 | * @throws Exception\WorkOSException 148 | */ 149 | public function verifyChallenge( 150 | $authenticationChallengeId, 151 | $code 152 | ) { 153 | if (!isset($authenticationChallengeId) || !isset($code)) { 154 | $msg = "Incomplete arguments: 'authenticationChallengeId' and 'code' are required parameters"; 155 | throw new Exception\UnexpectedValueException($msg); 156 | } 157 | 158 | $verifyPath = "auth/challenges/{$authenticationChallengeId}/verify"; 159 | 160 | $params = [ 161 | "code" => $code 162 | ]; 163 | 164 | $response = Client::request( 165 | Client::METHOD_POST, 166 | $verifyPath, 167 | null, 168 | $params, 169 | true 170 | ); 171 | 172 | return Resource\VerificationChallenge::constructFromResponse($response); 173 | } 174 | 175 | 176 | /** 177 | * Returns a Factor. 178 | * 179 | * @param string $authenticationFactorId - WorkOS Factor ID 180 | * 181 | * @throws Exception\WorkOSException 182 | */ 183 | public function getFactor($authenticationFactorId) 184 | { 185 | $getFactorPath = "auth/factors/{$authenticationFactorId}"; 186 | 187 | $response = Client::request( 188 | Client::METHOD_GET, 189 | $getFactorPath, 190 | null, 191 | null, 192 | true 193 | ); 194 | 195 | return Resource\AuthenticationFactorTotp::constructFromResponse($response); 196 | } 197 | 198 | 199 | /** 200 | * Deletes a Factor. 201 | * 202 | * @param string $authenticationFactorId - WorkOS Factor ID 203 | * 204 | * @return Resource\Response 205 | * 206 | * @throws Exception\WorkOSException 207 | */ 208 | public function deleteFactor($authenticationFactorId) 209 | { 210 | $deleteFactorPath = "auth/factors/{$authenticationFactorId}"; 211 | 212 | $response = Client::request( 213 | Client::METHOD_DELETE, 214 | $deleteFactorPath, 215 | null, 216 | null, 217 | true 218 | ); 219 | 220 | return $response; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /lib/Organizations.php: -------------------------------------------------------------------------------- 1 | $limit, 38 | "before" => $before, 39 | "after" => $after, 40 | "domains" => $domains, 41 | "order" => $order 42 | ]; 43 | 44 | $response = Client::request( 45 | Client::METHOD_GET, 46 | $organizationsPath, 47 | null, 48 | $params, 49 | true 50 | ); 51 | 52 | $organizations = []; 53 | list($before, $after) = Util\Request::parsePaginationArgs($response); 54 | foreach ($response["data"] as $responseData) { 55 | \array_push($organizations, Resource\Organization::constructFromResponse($responseData)); 56 | } 57 | 58 | return [$before, $after, $organizations]; 59 | } 60 | 61 | /** 62 | * Create Organization. 63 | * 64 | * @param string $name The name of the Organization. 65 | * @param null|array $domains @deprecated 4.5.0 The domains of the Organization. Use domain_data instead. 66 | * @param null|array $domain_data The domains of the Organization. 67 | * @param null|boolean $allowProfilesOutsideOrganization @deprecated 4.5.0 If you need to allow sign-ins from 68 | * any email domain, contact support@workos.com. 69 | * @param null|string $idempotencyKey is a unique string that identifies a distinct organization 70 | * @param null|string $externalId The organization's external id 71 | * @param null|array $metadata The organization's metadata 72 | * 73 | * @throws Exception\WorkOSException 74 | * 75 | * @return Resource\Organization 76 | */ 77 | public function createOrganization( 78 | $name, 79 | $domains = null, 80 | $allowProfilesOutsideOrganization = null, 81 | $idempotencyKey = null, 82 | $domain_data = null, 83 | $externalId = null, 84 | $metadata = null 85 | ) { 86 | $idempotencyKey ? $headers = array("Idempotency-Key: $idempotencyKey") : $headers = null; 87 | $organizationsPath = "organizations"; 88 | 89 | $params = ["name" => $name]; 90 | 91 | if (isset($domains)) { 92 | $params["domains"] = $domains; 93 | } 94 | if (isset($domain_data)) { 95 | $params["domain_data"] = $domain_data; 96 | } 97 | if (isset($allowProfilesOutsideOrganization)) { 98 | $params["allow_profiles_outside_organization"] = $allowProfilesOutsideOrganization; 99 | } 100 | if (isset($externalId)) { 101 | $params["external_id"] = $externalId; 102 | } 103 | if (isset($metadata)) { 104 | $params["metadata"] = $metadata; 105 | } 106 | 107 | $response = Client::request(Client::METHOD_POST, $organizationsPath, $headers, $params, true); 108 | 109 | return Resource\Organization::constructFromResponse($response); 110 | } 111 | 112 | /** 113 | * Update Organization. 114 | * 115 | * @param string $organization An Organization identifier. 116 | * @param null|array $domains @deprecated 4.5.0 The domains of the Organization. Use domain_data instead. 117 | * @param null|array $domain_data The domains of the Organization. 118 | * @param null|string $name The name of the Organization. 119 | * @param null|boolean $allowProfilesOutsideOrganization @deprecated 4.5.0 If you need to allow sign-ins from 120 | * any email domain, contact support@workos.com. 121 | * @param null|string $stripeCustomerId The Stripe Customer ID of the Organization. 122 | * @param null|string $externalId The organization's external id 123 | * @param null|array $metadata The organization's metadata 124 | * 125 | * @throws Exception\WorkOSException 126 | */ 127 | public function updateOrganization( 128 | $organization, 129 | $domains = null, 130 | $name = null, 131 | $allowProfilesOutsideOrganization = null, 132 | $domain_data = null, 133 | $stripeCustomerId = null, 134 | $externalId = null, 135 | $metadata = null 136 | ) { 137 | $organizationsPath = "organizations/{$organization}"; 138 | 139 | $params = ["name" => $name]; 140 | 141 | if (isset($domains)) { 142 | $params["domains"] = $domains; 143 | } 144 | if (isset($domain_data)) { 145 | $params["domain_data"] = $domain_data; 146 | } 147 | if (isset($allowProfilesOutsideOrganization)) { 148 | $params["allow_profiles_outside_organization"] = $allowProfilesOutsideOrganization; 149 | } 150 | if (isset($stripeCustomerId)) { 151 | $params["stripe_customer_id"] = $stripeCustomerId; 152 | } 153 | if (isset($externalId)) { 154 | $params["external_id"] = $externalId; 155 | } 156 | if (isset($metadata)) { 157 | $params["metadata"] = $metadata; 158 | } 159 | 160 | $response = Client::request(Client::METHOD_PUT, $organizationsPath, null, $params, true); 161 | 162 | return Resource\Organization::constructFromResponse($response); 163 | } 164 | 165 | /** 166 | * Get an Organization 167 | * 168 | * @param string $organization WorkOS organization ID 169 | * 170 | * @throws Exception\WorkOSException 171 | * 172 | * @return Resource\Organization 173 | */ 174 | public function getOrganization($organization) 175 | { 176 | $organizationsPath = "organizations/{$organization}"; 177 | 178 | $response = Client::request(Client::METHOD_GET, $organizationsPath, null, null, true); 179 | 180 | return Resource\Organization::constructFromResponse($response); 181 | } 182 | 183 | /** 184 | * Get an Organization by its external id 185 | * 186 | * @param string $externalId external id 187 | * 188 | * @throws Exception\WorkOSException 189 | * 190 | * @return Resource\Organization 191 | */ 192 | public function getOrganizationByExternalId($externalId) 193 | { 194 | $organizationsPath = "organizations/external_id/{$externalId}"; 195 | 196 | $response = Client::request(Client::METHOD_GET, $organizationsPath, null, null, true); 197 | 198 | return Resource\Organization::constructFromResponse($response); 199 | } 200 | 201 | /** 202 | * Delete an Organization. 203 | * 204 | * @param string $organization WorkOS organization ID 205 | * 206 | * @throws Exception\WorkOSException 207 | * 208 | * @return Resource\Response 209 | */ 210 | public function deleteOrganization($organization) 211 | { 212 | $organizationsPath = "organizations/{$organization}"; 213 | 214 | $response = Client::request( 215 | Client::METHOD_DELETE, 216 | $organizationsPath, 217 | null, 218 | null, 219 | true 220 | ); 221 | 222 | return $response; 223 | } 224 | 225 | /** 226 | * List roles for an organization. 227 | * 228 | * @param string $organizationId WorkOS organization ID to fetch roles for 229 | * 230 | * @throws Exception\WorkOSException 231 | * 232 | * @return array{0: Resource\Role[]} An array containing the list of Role instances 233 | */ 234 | public function listOrganizationRoles($organizationId) 235 | { 236 | $organizationRolesPath = "organizations/{$organizationId}/roles"; 237 | 238 | $response = Client::request( 239 | Client::METHOD_GET, 240 | $organizationRolesPath, 241 | null, 242 | null, 243 | true 244 | ); 245 | 246 | $roles = []; 247 | foreach ($response["data"] as $responseData) { 248 | \array_push($roles, Resource\Role::constructFromResponse($responseData)); 249 | } 250 | 251 | return [$roles]; 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /lib/Passwordless.php: -------------------------------------------------------------------------------- 1 | $email, 32 | "type" => $type 33 | ]; 34 | 35 | if ($redirectUri) { 36 | $params["redirect_uri"] = $redirectUri; 37 | } 38 | 39 | if ($state) { 40 | $params["state"] = $state; 41 | } 42 | 43 | if ($connection) { 44 | $params["connection"] = $connection; 45 | } 46 | 47 | if ($expiresIn) { 48 | $params["expires_in"] = $expiresIn; 49 | } 50 | 51 | $response = Client::request(Client::METHOD_POST, $createSessionPath, null, $params, true); 52 | 53 | return Resource\PasswordlessSession::constructFromResponse($response); 54 | } 55 | 56 | /** 57 | * Send a passwordless link via email from WorkOS. 58 | * 59 | * @param Resource\PasswordlessSession $session Passwordless session generated through Passwordless->createSession 60 | * 61 | * @throws Exception\WorkOSException 62 | * 63 | * @return true 64 | */ 65 | public function sendSession($session) 66 | { 67 | $sendSessionPath = "passwordless/sessions/$session->id/send"; 68 | Client::request(Client::METHOD_POST, $sendSessionPath, null, null, true); 69 | 70 | return true; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/Portal.php: -------------------------------------------------------------------------------- 1 | $organization, 31 | "intent" => $intent, 32 | "return_url" => $returnUrl, 33 | "success_url" => $successUrl 34 | ]; 35 | 36 | $response = Client::request(Client::METHOD_POST, $generateLinkPath, null, $params, true); 37 | 38 | return Resource\PortalLink::constructFromResponse($response); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/RequestClient/CurlRequestClient.php: -------------------------------------------------------------------------------- 1 | 1]; 30 | 31 | switch ($method) { 32 | case Client::METHOD_GET: 33 | if (!empty($params)) { 34 | $url .= "?" . http_build_query($params); 35 | } 36 | 37 | break; 38 | 39 | case Client::METHOD_POST: 40 | \array_push($headers, "Content-Type: application/json"); 41 | 42 | $opts[\CURLOPT_POST] = 1; 43 | 44 | if (!empty($params)) { 45 | $opts[\CURLOPT_POSTFIELDS] = \json_encode($params); 46 | } 47 | 48 | break; 49 | 50 | case Client::METHOD_DELETE: 51 | 52 | $opts[\CURLOPT_CUSTOMREQUEST] = 'DELETE'; 53 | 54 | if (!empty($params)) { 55 | $encoded = Util\Util::encodeParameters($params); 56 | $absUrl = "{$absUrl}?{$encoded}"; 57 | } 58 | 59 | break; 60 | 61 | case Client::METHOD_PUT: 62 | 63 | \array_push($headers, "Content-Type: application/json"); 64 | 65 | $opts[\CURLOPT_CUSTOMREQUEST] = 'PUT'; 66 | 67 | $opts[\CURLOPT_POST] = 1; 68 | 69 | if (!empty($params)) { 70 | $opts[\CURLOPT_POSTFIELDS] = \json_encode($params); 71 | } 72 | 73 | break; 74 | } 75 | 76 | $opts[\CURLOPT_HTTPHEADER] = $headers; 77 | $opts[\CURLOPT_URL] = $url; 78 | 79 | return self::execute($opts); 80 | } 81 | 82 | private function execute($opts) 83 | { 84 | $curl = \curl_init(); 85 | 86 | $headers = array(); 87 | $headerCallback = function ($curl, $header_line) use (&$headers) { 88 | if (false === \strpos($header_line, ":")) { 89 | return \strlen($header_line); 90 | } 91 | 92 | list($key, $value) = \explode(":", \trim($header_line), 2); 93 | $headers[\trim($key)] = \trim($value); 94 | 95 | return \strlen($header_line); 96 | }; 97 | $opts[\CURLOPT_HEADERFUNCTION] = $headerCallback; 98 | \curl_setopt_array($curl, $opts); 99 | 100 | $result = \curl_exec($curl); 101 | 102 | // I think this is for some sort of internal error 103 | // Any kind of response that returns a status code won"t hit this block 104 | if ($result === false) { 105 | $errno = \curl_errno($curl); 106 | $msg = \curl_error($curl); 107 | \curl_close($curl); 108 | 109 | throw new GenericException($msg, ["curlErrno" => $errno]); 110 | } else { 111 | // Unsure how versions of cURL and PHP correlate so using the legacy 112 | // reference for getting the last response code 113 | $statusCode = \curl_getinfo($curl, \CURLINFO_RESPONSE_CODE); 114 | \curl_close($curl); 115 | 116 | return [$result, $headers, $statusCode]; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /lib/RequestClient/RequestClientInterface.php: -------------------------------------------------------------------------------- 1 | "success" 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /lib/Resource/AuditLogExport.php: -------------------------------------------------------------------------------- 1 | "object", 23 | "id" => "id", 24 | "state" => "state", 25 | "url" => "url", 26 | "created_at" => "createdAt", 27 | "updated_at" => "updatedAt" 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /lib/Resource/AuthenticationChallengeSms.php: -------------------------------------------------------------------------------- 1 | "object", 23 | "id" => "id", 24 | "created_at" => "createdAt", 25 | "updated_at" => "updatedAt", 26 | "expires_at" => "expiresAt", 27 | "authentication_factor_id" => "authenticationFactorId" 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /lib/Resource/AuthenticationChallengeTotp.php: -------------------------------------------------------------------------------- 1 | "object", 23 | "id" => "id", 24 | "created_at" => "createdAt", 25 | "updated_at" => "updatedAt", 26 | "expires_at" => "expiresAt", 27 | "authentication_factor_id" => "authenticationFactorId" 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /lib/Resource/AuthenticationFactorAndChallengeTotp.php: -------------------------------------------------------------------------------- 1 | "authenticationFactor", 20 | "authentication_challenge" => "authenticationChallenge" 21 | ]; 22 | 23 | public static function constructFromResponse($response) 24 | { 25 | $instance = parent::constructFromResponse($response); 26 | $instance->values["authenticationFactor"] = AuthenticationFactorTotp::constructFromResponse($response["authentication_factor"]); 27 | $instance->values["authenticationChallenge"] = AuthenticationChallengeTotp::constructFromResponse($response["authentication_challenge"]); 28 | 29 | return $instance; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/Resource/AuthenticationFactorSms.php: -------------------------------------------------------------------------------- 1 | "object", 23 | "id" => "id", 24 | "created_at" => "createdAt", 25 | "updated_at" => "updatedAt", 26 | "type" => "type", 27 | "sms" => "sms" 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /lib/Resource/AuthenticationFactorTotp.php: -------------------------------------------------------------------------------- 1 | "object", 23 | "id" => "id", 24 | "created_at" => "createdAt", 25 | "updated_at" => "updatedAt", 26 | "type" => "type", 27 | "totp" => "totp" 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /lib/Resource/AuthenticationResponse.php: -------------------------------------------------------------------------------- 1 | "organizationId", 26 | "access_token" => "accessToken", 27 | "refresh_token" => "refreshToken", 28 | ]; 29 | 30 | public static function constructFromResponse($response) 31 | { 32 | $instance = parent::constructFromResponse($response); 33 | 34 | $instance->values["user"] = User::constructFromResponse($response["user"]); 35 | 36 | if (isset($response["impersonator"])) { 37 | $instance->values["impersonator"] = Impersonator::constructFromResponse( 38 | $response["impersonator"] 39 | ); 40 | } 41 | 42 | return $instance; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/Resource/BaseWorkOSResource.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | protected const RESPONSE_TO_RESOURCE_KEY = []; 14 | 15 | /** 16 | * List of attributes available in this resource. 17 | * Child classes should override this constant. 18 | * 19 | * @var array 20 | */ 21 | protected const RESOURCE_ATTRIBUTES = []; 22 | 23 | /** 24 | * @var array $values; 25 | */ 26 | protected $values; 27 | 28 | /** 29 | * @var array $raw; 30 | */ 31 | public $raw; 32 | 33 | private function __construct() 34 | { 35 | } 36 | 37 | /** 38 | * Creates a Resource from a Response. 39 | * 40 | * @param array $response 41 | * 42 | * @return static 43 | */ 44 | public static function constructFromResponse($response) 45 | { 46 | $instance = new static(); 47 | 48 | $instance->raw = $response; 49 | $instance->values = []; 50 | 51 | foreach (static::RESPONSE_TO_RESOURCE_KEY as $responseKey => $resourceKey) { 52 | try { 53 | $instance->values[$resourceKey] = $instance->raw[$responseKey] ?? null; 54 | } catch (\OutOfBoundsException $e) { 55 | $instance->values[$resourceKey] = null; 56 | } 57 | } 58 | 59 | return $instance; 60 | } 61 | 62 | public function toArray() 63 | { 64 | return \array_reduce(static::RESOURCE_ATTRIBUTES, function ($arr, $key) { 65 | $arr[$key] = $this->values[$key]; 66 | return $arr; 67 | }, []); 68 | } 69 | 70 | /** 71 | * Magic method overrides. 72 | */ 73 | public function __set($key, $value) 74 | { 75 | if (\in_array($key, static::RESOURCE_ATTRIBUTES)) { 76 | $this->values[$key] = $value; 77 | } 78 | 79 | $msg = "{$key} does not exist on " . static::class; 80 | throw new \WorkOS\Exception\UnexpectedValueException($msg); 81 | } 82 | 83 | public function __isset($key) 84 | { 85 | return isset($this->values[$key]); 86 | } 87 | 88 | public function __unset($key) 89 | { 90 | unset($this->values[$key]); 91 | } 92 | 93 | public function &__get($key) 94 | { 95 | if (\in_array($key, static::RESOURCE_ATTRIBUTES)) { 96 | return $this->values[$key]; 97 | } 98 | 99 | if (isset($this->raw[$key])) { 100 | return $this->raw[$key]; 101 | } 102 | 103 | $msg = "{$key} does not exist on " . static::class; 104 | throw new \WorkOS\Exception\UnexpectedValueException($msg); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/Resource/Connection.php: -------------------------------------------------------------------------------- 1 | "id", 24 | "state" => "state", 25 | "status" => "status", 26 | "name" => "name", 27 | "connection_type" => "connectionType", 28 | "organization_id" => "organizationId", 29 | ]; 30 | 31 | public static function constructFromResponse($response) 32 | { 33 | $instance = parent::constructFromResponse($response); 34 | 35 | $rawDomains = $response["domains"]; 36 | $domains = []; 37 | foreach ($rawDomains as $rawDomain) { 38 | \array_push($domains, Domain::constructFromResponse($rawDomain)); 39 | } 40 | 41 | $instance->values["domains"] = $domains; 42 | 43 | return $instance; 44 | } 45 | 46 | public function toArray() 47 | { 48 | $connectionArray = parent::toArray(); 49 | 50 | $domains = []; 51 | foreach ($connectionArray["domains"] as $domain) { 52 | \array_push($domains, $domain->toArray()); 53 | } 54 | 55 | $connectionArray["domains"] = $domains; 56 | 57 | return $connectionArray; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/Resource/ConnectionType.php: -------------------------------------------------------------------------------- 1 | "id", 24 | "external_key" => "externalKey", 25 | "organization_id" => "organizationId", 26 | "state" => "state", 27 | "type" => "type", 28 | "name" => "name", 29 | "domain" => "domain" 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /lib/Resource/DirectoryGroup.php: -------------------------------------------------------------------------------- 1 | "id", 21 | "name" => "name", 22 | "directory_id" => "directoryId", 23 | "organization_id" => "organizationId" 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /lib/Resource/DirectoryUser.php: -------------------------------------------------------------------------------- 1 | "id", 46 | "raw_attributes" => "rawAttributes", 47 | "custom_attributes" => "customAttributes", 48 | "first_name" => "firstName", 49 | "email" => "email", 50 | "emails" => "emails", 51 | "username" => "username", 52 | "last_name" => "lastName", 53 | "job_title" => "jobTitle", 54 | "state" => "state", 55 | "idp_id" => "idpId", 56 | "groups" => "groups", 57 | "directory_id" => "directoryId", 58 | "organization_id" => "organizationId" 59 | ]; 60 | 61 | /** 62 | * @deprecated 4.22.0 Use `email` property instead. 63 | * 64 | * @return string|null The primary email address if found, null otherwise 65 | */ 66 | public function primaryEmail() 67 | { 68 | $response = $this; 69 | 70 | if (count($response->raw["emails"]) == 0) { 71 | return; 72 | }; 73 | 74 | foreach ($response->raw["emails"] as $value) { 75 | if ($value["primary"] == true) { 76 | return $value["value"]; 77 | }; 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/Resource/Domain.php: -------------------------------------------------------------------------------- 1 | "id", 19 | "domain" => "domain" 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /lib/Resource/EmailVerification.php: -------------------------------------------------------------------------------- 1 | "object", 26 | "id" => "id", 27 | "user_id" => "userId", 28 | "email" => "email", 29 | "expires_at" => "expiresAt", 30 | "code" => "code", 31 | "created_at" => "createdAt", 32 | "updated_at" => "updatedAt" 33 | ]; 34 | } 35 | -------------------------------------------------------------------------------- /lib/Resource/Impersonator.php: -------------------------------------------------------------------------------- 1 | "email", 22 | "reason" => "reason", 23 | ]; 24 | } 25 | -------------------------------------------------------------------------------- /lib/Resource/Invitation.php: -------------------------------------------------------------------------------- 1 | "object", 31 | "id" => "id", 32 | "email" => "email", 33 | "state" => "state", 34 | "accepted_at" => "acceptedAt", 35 | "revoked_at" => "revokedAt", 36 | "expires_at" => "expiresAt", 37 | "token" => "token", 38 | "accept_invitation_url" => "acceptInvitationUrl", 39 | "organization_id" => "organizationId", 40 | "inviter_user_id" => "inviterUserId", 41 | "created_at" => "createdAt", 42 | "updated_at" => "updatedAt" 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /lib/Resource/MagicAuth.php: -------------------------------------------------------------------------------- 1 | "object", 26 | "id" => "id", 27 | "user_id" => "userId", 28 | "email" => "email", 29 | "expires_at" => "expiresAt", 30 | "code" => "code", 31 | "created_at" => "createdAt", 32 | "updated_at" => "updatedAt" 33 | ]; 34 | } 35 | -------------------------------------------------------------------------------- /lib/Resource/Order.php: -------------------------------------------------------------------------------- 1 | "id", 24 | "name" => "name", 25 | "allow_profiles_outside_organization" => "allowProfilesOutsideOrganization", 26 | "domains" => "domains", 27 | "external_id" => "externalId", 28 | "metadata" => "metadata" 29 | ]; 30 | } 31 | -------------------------------------------------------------------------------- /lib/Resource/OrganizationMembership.php: -------------------------------------------------------------------------------- 1 | "object", 34 | "id" => "id", 35 | "user_id" => "userId", 36 | "organization_id" => "organizationId", 37 | "role" => "role", 38 | "status" => "status", 39 | "created_at" => "createdAt", 40 | "updated_at" => "updatedAt" 41 | ]; 42 | } 43 | -------------------------------------------------------------------------------- /lib/Resource/PasswordReset.php: -------------------------------------------------------------------------------- 1 | "object", 26 | "id" => "id", 27 | "user_id" => "userId", 28 | "email" => "email", 29 | "password_reset_token" => "passwordResetToken", 30 | "password_reset_url" => "passwordResetUrl", 31 | "expires_at" => "expiresAt", 32 | "created_at" => "createdAt", 33 | ]; 34 | } 35 | -------------------------------------------------------------------------------- /lib/Resource/PasswordlessSession.php: -------------------------------------------------------------------------------- 1 | "id", 22 | "email" => "email", 23 | "expires_at" => "expiresAt", 24 | "link" => "link", 25 | "object" => "object" 26 | ]; 27 | } 28 | -------------------------------------------------------------------------------- /lib/Resource/PortalLink.php: -------------------------------------------------------------------------------- 1 | "link" 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /lib/Resource/Profile.php: -------------------------------------------------------------------------------- 1 | "id", 43 | "email" => "email", 44 | "first_name" => "firstName", 45 | "last_name" => "lastName", 46 | "organization_id" => "organizationId", 47 | "connection_id" => "connectionId", 48 | "connection_type" => "connectionType", 49 | "idp_id" => "idpId", 50 | "role" => "role", 51 | "groups" => "groups", 52 | "custom_attributes" => "customAttributes", 53 | "raw_attributes" => "rawAttributes" 54 | ]; 55 | 56 | public static function constructFromResponse($response) 57 | { 58 | $instance = parent::constructFromResponse($response); 59 | 60 | if (isset($response["role"])) { 61 | $instance->values["role"] = new RoleResponse($response["role"]["slug"]); 62 | } 63 | 64 | return $instance; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/Resource/ProfileAndToken.php: -------------------------------------------------------------------------------- 1 | "accessToken" 20 | ]; 21 | 22 | public static function constructFromResponse($response) 23 | { 24 | $instance = parent::constructFromResponse($response); 25 | 26 | $instance->values["profile"] = Profile::constructFromResponse($response["profile"]); 27 | 28 | return $instance; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/Resource/Response.php: -------------------------------------------------------------------------------- 1 | body = $body; 35 | $this->headers = $headers; 36 | $this->statusCode = $statusCode; 37 | } 38 | 39 | public function json() 40 | { 41 | if (!isset($json)) { 42 | $this->json = \json_decode($this->body, true); 43 | } 44 | 45 | return $this->json; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/Resource/Role.php: -------------------------------------------------------------------------------- 1 | "id", 25 | "name" => "name", 26 | "slug" => "slug", 27 | "description" => "description", 28 | "type" => "type", 29 | "created_at" => "created_at", 30 | "updated_at" => "updated_at" 31 | ]; 32 | } 33 | -------------------------------------------------------------------------------- /lib/Resource/RoleResponse.php: -------------------------------------------------------------------------------- 1 | slug = $slug; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/Resource/User.php: -------------------------------------------------------------------------------- 1 | "object", 29 | "id" => "id", 30 | "email" => "email", 31 | "first_name" => "firstName", 32 | "last_name" => "lastName", 33 | "email_verified" => "emailVerified", 34 | "profile_picture_url" => "profilePictureUrl", 35 | "last_sign_in_at" => "lastSignInAt", 36 | "created_at" => "createdAt", 37 | "updated_at" => "updatedAt", 38 | "external_id" => "externalId", 39 | "metadata" => "metadata" 40 | ]; 41 | } 42 | -------------------------------------------------------------------------------- /lib/Resource/UserAndToken.php: -------------------------------------------------------------------------------- 1 | "token" 20 | ]; 21 | 22 | public static function constructFromResponse($response) 23 | { 24 | $instance = parent::constructFromResponse($response); 25 | 26 | $instance->values["user"] = User::constructFromResponse($response["user"]); 27 | 28 | return $instance; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/Resource/UserAuthenticationFactorTotp.php: -------------------------------------------------------------------------------- 1 | "object", 24 | "id" => "id", 25 | "user_id" => "userId", 26 | "created_at" => "createdAt", 27 | "updated_at" => "updatedAt", 28 | "type" => "type", 29 | "totp" => "totp" 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /lib/Resource/UserResponse.php: -------------------------------------------------------------------------------- 1 | values["user"] = User::constructFromResponse($response["user"]); 23 | 24 | return $instance; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/Resource/VerificationChallenge.php: -------------------------------------------------------------------------------- 1 | "challenge", 19 | "valid" => "valid" 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /lib/Resource/Webhook.php: -------------------------------------------------------------------------------- 1 | , 51 | * created_at: string, 52 | * updated_at: string 53 | * } $organization Organization details for authentication events 54 | * @property-read ?object{ 55 | * object: 'organization_membership', 56 | * id: string, 57 | * user_id: string, 58 | * organization_id: string, 59 | * role: array{slug: string}, 60 | * status: string, 61 | * created_at: string, 62 | * updated_at: string 63 | * } $organization_membership Organization membership details for authentication events 64 | * 65 | * Common Properties 66 | * @property-read string $ip_address IP address of the event 67 | * @property-read string $user_agent User agent string of the event 68 | * @property-read string $device_fingerprint Device fingerprint of the event 69 | */ 70 | class Webhook 71 | { 72 | /** 73 | * Creates a webhook object from a payload. 74 | * 75 | * @param string $payload JSON string containing webhook data 76 | * @return static 77 | */ 78 | public static function constructFromPayload($payload) 79 | { 80 | $jsonPayload = json_decode($payload); 81 | $object = (object) $jsonPayload; 82 | 83 | return $object; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/Resource/WebhookResponse.php: -------------------------------------------------------------------------------- 1 | object = $type; 64 | 65 | $payload = [ 66 | 'timestamp' => time() * 1000, 67 | 'verdict' => $verdict 68 | ]; 69 | 70 | if ($verdict === self::VERDICT_DENY) { 71 | $payload['error_message'] = $errorMessage; 72 | } 73 | 74 | $instance->payload = $payload; 75 | 76 | $timestamp = $payload['timestamp']; 77 | $payloadString = json_encode($payload); 78 | $instance->signature = (new Webhook())->computeSignature($timestamp, $payloadString, $secret); 79 | 80 | return $instance; 81 | } 82 | 83 | /** 84 | * Get the response as an array 85 | * 86 | * @return array 87 | */ 88 | public function toArray() 89 | { 90 | $response = [ 91 | 'object' => $this->object, 92 | 'payload' => $this->payload 93 | ]; 94 | 95 | if ($this->signature) { 96 | $response['signature'] = $this->signature; 97 | } 98 | 99 | return $response; 100 | } 101 | 102 | /** 103 | * Get the response as a JSON string 104 | * 105 | * @return string 106 | */ 107 | public function toJson() 108 | { 109 | return json_encode($this->toArray()); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/Resource/WidgetScope.php: -------------------------------------------------------------------------------- 1 | "token" 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /lib/SSO.php: -------------------------------------------------------------------------------- 1 | WorkOS::getClientId(), 56 | "response_type" => "code" 57 | ]; 58 | 59 | if ($domain) { 60 | $params["domain"] = $domain; 61 | } 62 | 63 | if ($redirectUri) { 64 | $params["redirect_uri"] = $redirectUri; 65 | } 66 | 67 | if (null !== $state && !empty($state)) { 68 | $params["state"] = \json_encode($state); 69 | } 70 | 71 | if ($provider) { 72 | $params["provider"] = $provider; 73 | } 74 | 75 | if ($connection) { 76 | $params["connection"] = $connection; 77 | } 78 | 79 | if ($organization) { 80 | $params["organization"] = $organization; 81 | } 82 | 83 | if ($domainHint) { 84 | $params["domain_hint"] = $domainHint; 85 | } 86 | 87 | if ($loginHint) { 88 | $params["login_hint"] = $loginHint; 89 | } 90 | 91 | return Client::generateUrl($authorizationPath, $params); 92 | } 93 | 94 | /** 95 | * Verify that SSO has been completed successfully and retrieve the identity of the user. 96 | * 97 | * @param string $code Code returned by WorkOS on completion of OAuth 2.0 flow 98 | * 99 | * @throws Exception\WorkOSException 100 | * 101 | * @return Resource\ProfileAndToken 102 | */ 103 | public function getProfileAndToken($code) 104 | { 105 | $profilePath = "sso/token"; 106 | 107 | $params = [ 108 | "client_id" => WorkOS::getClientId(), 109 | "client_secret" => WorkOS::getApikey(), 110 | "code" => $code, 111 | "grant_type" => "authorization_code" 112 | ]; 113 | 114 | $response = Client::request(Client::METHOD_POST, $profilePath, null, $params); 115 | 116 | return Resource\ProfileAndToken::constructFromResponse($response); 117 | } 118 | 119 | /** 120 | * Verify that SSO has been completed successfully and retrieve the identity of the user. 121 | * 122 | * @param string $accessToken, the token used to authenticate the API call 123 | * 124 | * @throws Exception\GenericException 125 | * 126 | * @return Resource\Profile 127 | */ 128 | public function getProfile($accessToken) 129 | { 130 | $getProfilePath = "sso/profile"; 131 | 132 | $params = [ 133 | "access_token" => $accessToken 134 | ]; 135 | 136 | $method = Client::METHOD_GET; 137 | 138 | $url = "https://api.workos.com/sso/profile"; 139 | 140 | $requestHeaders = ["Authorization: Bearer " . $accessToken]; 141 | 142 | list($result) = Client::requestClient()->request( 143 | $method, 144 | $url, 145 | $requestHeaders, 146 | null 147 | ); 148 | 149 | $decodedResponse = json_decode($result, true); 150 | 151 | $profile = Resource\Profile::constructFromResponse($decodedResponse); 152 | 153 | return $profile->json(); 154 | } 155 | 156 | /** 157 | * Delete a Connection. 158 | * 159 | * @param string $connection Connection ID 160 | * 161 | * @throws Exception\WorkOSException 162 | * 163 | * @return Resource\Response 164 | */ 165 | public function deleteConnection($connection) 166 | { 167 | $connectionPath = "connections/{$connection}"; 168 | 169 | $response = Client::request( 170 | Client::METHOD_DELETE, 171 | $connectionPath, 172 | null, 173 | null, 174 | true 175 | ); 176 | 177 | return $response; 178 | } 179 | 180 | /** 181 | * Get a Connection. 182 | * 183 | * @param string $connection Connection ID 184 | * 185 | * @throws Exception\WorkOSException 186 | * 187 | * @return Resource\Connection 188 | */ 189 | public function getConnection($connection) 190 | { 191 | $connectionPath = "connections/{$connection}"; 192 | 193 | $response = Client::request( 194 | Client::METHOD_GET, 195 | $connectionPath, 196 | null, 197 | null, 198 | true 199 | ); 200 | 201 | return Resource\Connection::constructFromResponse($response); 202 | } 203 | 204 | public const DEFAULT_PAGE_SIZE = 10; 205 | 206 | /** 207 | * List Connections. 208 | * 209 | * @param null|string $domain Domain of a Connection 210 | * @param null|string $connectionType Authentication service provider descriptor 211 | * @param null|string $organizationId Organization ID of the Connection(s) 212 | * @param int $limit Maximum number of records to return 213 | * @param null|string $before Connection ID to look before 214 | * @param null|string $after Connection ID to look after 215 | * @param Resource\Order $order The Order in which to paginate records 216 | * 217 | * @return array{?string, ?string, Resource\Connection[]} An array containing the Directory Connection ID to use as before and after cursor, and an array of Connection instances 218 | * 219 | * @throws Exception\WorkOSException 220 | */ 221 | public function listConnections( 222 | $domain = null, 223 | $connectionType = null, 224 | $organizationId = null, 225 | $limit = self::DEFAULT_PAGE_SIZE, 226 | $before = null, 227 | $after = null, 228 | $order = null 229 | ) { 230 | $connectionsPath = "connections"; 231 | $params = [ 232 | "limit" => $limit, 233 | "before" => $before, 234 | "after" => $after, 235 | "domain" => $domain, 236 | "connection_type" => $connectionType, 237 | "organization_id" => $organizationId, 238 | "order" => $order 239 | ]; 240 | 241 | $response = Client::request( 242 | Client::METHOD_GET, 243 | $connectionsPath, 244 | null, 245 | $params, 246 | true 247 | ); 248 | 249 | $connections = []; 250 | list($before, $after) = Util\Request::parsePaginationArgs($response); 251 | foreach ($response["data"] as $responseData) { 252 | \array_push($connections, Resource\Connection::constructFromResponse($responseData)); 253 | } 254 | 255 | return [$before, $after, $connections]; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /lib/UserManagement.php: -------------------------------------------------------------------------------- 1 | $metadata The user's metadata. 31 | * 32 | * @throws Exception\WorkOSException 33 | * 34 | * @return Resource\User 35 | */ 36 | 37 | public function createUser( 38 | $email, 39 | $password = null, 40 | $firstName = null, 41 | $lastName = null, 42 | $emailVerified = null, 43 | $passwordHash = null, 44 | $passwordHashType = null, 45 | $externalId = null, 46 | $metadata = null 47 | ) { 48 | $path = "user_management/users"; 49 | $params = [ 50 | "email" => $email, 51 | "password" => $password, 52 | "first_name" => $firstName, 53 | "last_name" => $lastName, 54 | "email_verified" => $emailVerified, 55 | "password_hash" => $passwordHash, 56 | "password_hash_type" => $passwordHashType, 57 | "external_id" => $externalId, 58 | "metadata" => $metadata 59 | ]; 60 | 61 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 62 | 63 | return Resource\User::constructFromResponse($response); 64 | } 65 | 66 | /** 67 | * Get a User. 68 | * 69 | * @param string $userId user ID 70 | * 71 | * @throws Exception\WorkOSException 72 | * 73 | * @return Resource\User 74 | */ 75 | public function getUser($userId) 76 | { 77 | $path = "user_management/users/{$userId}"; 78 | 79 | $response = Client::request(Client::METHOD_GET, $path, null, null, true); 80 | 81 | return Resource\User::constructFromResponse($response); 82 | } 83 | 84 | /** 85 | * Get a User by external ID. 86 | * 87 | * @param string $externalId The external ID of the user. 88 | * 89 | * @throws Exception\WorkOSException 90 | * 91 | * @return Resource\User 92 | */ 93 | public function getUserByExternalId($externalId) 94 | { 95 | $path = "user_management/users/external_id/{$externalId}"; 96 | 97 | $response = Client::request(Client::METHOD_GET, $path, null, null, true); 98 | 99 | return Resource\User::constructFromResponse($response); 100 | } 101 | 102 | /** 103 | * Update a User 104 | * 105 | * @param string $userId The unique ID of the user. 106 | * @param string|null $firstName The first name of the user. 107 | * @param string|null $lastName The last name of the user. 108 | * @param boolean|null $emailVerified A boolean declaring if the user's email has been verified. 109 | * @param string|null $password The password to set for the user. 110 | * @param string|null $passwordHash The hashed password to set for the user. 111 | * @param string|null $passwordHashType The algorithm originally used to hash the password. Valid values are `bcrypt`, `ssha`, and `firebase-scrypt`. 112 | * @param string|null $externalId The user's external ID. 113 | * @param array|null $metadata The user's metadata. 114 | * @param string|null $email The email address of the user. 115 | * 116 | * @throws Exception\WorkOSException 117 | * 118 | * @return Resource\User 119 | */ 120 | public function updateUser( 121 | $userId, 122 | $firstName = null, 123 | $lastName = null, 124 | $emailVerified = null, 125 | $password = null, 126 | $passwordHash = null, 127 | $passwordHashType = null, 128 | $externalId = null, 129 | $metadata = null, 130 | $email = null 131 | ) { 132 | $path = "user_management/users/{$userId}"; 133 | 134 | $params = [ 135 | "first_name" => $firstName, 136 | "last_name" => $lastName, 137 | "email_verified" => $emailVerified, 138 | "password" => $password, 139 | "password_hash" => $passwordHash, 140 | "password_hash_type" => $passwordHashType, 141 | "external_id" => $externalId, 142 | "metadata" => $metadata, 143 | "email" => $email 144 | ]; 145 | 146 | $response = Client::request(Client::METHOD_PUT, $path, null, $params, true); 147 | 148 | return Resource\User::constructFromResponse($response); 149 | } 150 | 151 | /** 152 | * List Users. 153 | * 154 | * @param null|string $email 155 | * @param null|string $organizationId Organization users are a member of 156 | * @param int $limit Maximum number of records to return 157 | * @param null|string $before User ID to look before 158 | * @param null|string $after User ID to look after 159 | * @param Resource\Order $order The Order in which to paginate records 160 | * 161 | * @return array{?string, ?string, Resource\User[]} An array containing the Directory User ID to use as before and after cursor, and an array of User instances 162 | * 163 | * @throws Exception\WorkOSException 164 | */ 165 | public function listUsers( 166 | $email = null, 167 | $organizationId = null, 168 | $limit = self::DEFAULT_PAGE_SIZE, 169 | $before = null, 170 | $after = null, 171 | $order = null 172 | ) { 173 | $path = "user_management/users"; 174 | 175 | $params = [ 176 | "email" => $email, 177 | "organization_id" => $organizationId, 178 | "limit" => $limit, 179 | "before" => $before, 180 | "after" => $after, 181 | "order" => $order 182 | ]; 183 | 184 | $response = Client::request( 185 | Client::METHOD_GET, 186 | $path, 187 | null, 188 | $params, 189 | true 190 | ); 191 | 192 | $users = []; 193 | list($before, $after) = Util\Request::parsePaginationArgs($response); 194 | foreach ($response["data"] as $responseData) { 195 | \array_push($users, Resource\User::constructFromResponse($responseData)); 196 | } 197 | 198 | return [$before, $after, $users]; 199 | } 200 | 201 | /** 202 | * Delete a user. 203 | * 204 | * @param string $userId Unique ID of a user 205 | * 206 | * @throws Exception\WorkOSException 207 | * 208 | * @return Resource\Response 209 | */ 210 | public function deleteUser($userId) 211 | { 212 | $path = "user_management/users/{$userId}"; 213 | 214 | $response = Client::request(Client::METHOD_DELETE, $path, null, null, true); 215 | 216 | return $response; 217 | } 218 | 219 | /** 220 | * Add a User to an Organization. 221 | * 222 | * @param string $userId User ID 223 | * @param string $organizationId Organization ID 224 | * @param string|null $roleSlug Role Slug 225 | * 226 | * @throws Exception\WorkOSException 227 | * 228 | * @return Resource\OrganizationMembership 229 | */ 230 | public function createOrganizationMembership($userId, $organizationId, $roleSlug = null) 231 | { 232 | $path = "user_management/organization_memberships"; 233 | 234 | $params = [ 235 | "organization_id" => $organizationId, 236 | "user_id" => $userId, 237 | "role_slug" => $roleSlug 238 | ]; 239 | 240 | $response = Client::request( 241 | Client::METHOD_POST, 242 | $path, 243 | null, 244 | $params, 245 | true 246 | ); 247 | 248 | return Resource\OrganizationMembership::constructFromResponse($response); 249 | } 250 | 251 | /** 252 | * Get an Organization Membership. 253 | * 254 | * @param string $organizationMembershipId Organization Membership ID 255 | * 256 | * @throws Exception\WorkOSException 257 | * 258 | * @return Resource\OrganizationMembership 259 | */ 260 | public function getOrganizationMembership($organizationMembershipId) 261 | { 262 | $path = "user_management/organization_memberships/{$organizationMembershipId}"; 263 | 264 | $response = Client::request( 265 | Client::METHOD_GET, 266 | $path, 267 | null, 268 | null, 269 | true 270 | ); 271 | 272 | return Resource\OrganizationMembership::constructFromResponse($response); 273 | } 274 | 275 | /** 276 | * Remove a user from an organization. 277 | * 278 | * @param string $organizationMembershipId Organization Membership ID 279 | * 280 | * @throws Exception\WorkOSException 281 | * 282 | * @return Resource\Response 283 | */ 284 | public function deleteOrganizationMembership($organizationMembershipId) 285 | { 286 | $path = "user_management/organization_memberships/{$organizationMembershipId}"; 287 | 288 | $response = Client::request( 289 | Client::METHOD_DELETE, 290 | $path, 291 | null, 292 | null, 293 | true 294 | ); 295 | 296 | return $response; 297 | } 298 | 299 | /** 300 | * Update a User organization membership. 301 | * 302 | * @param string $organizationMembershipId Organization Membership ID 303 | * @param string|null $role_slug The unique role identifier. 304 | * 305 | * @throws Exception\WorkOSException 306 | * 307 | * @return Resource\OrganizationMembership 308 | */ 309 | public function updateOrganizationMembership($organizationMembershipId, $roleSlug = null) 310 | { 311 | $path = "user_management/organization_memberships/{$organizationMembershipId}"; 312 | 313 | $params = [ 314 | "role_slug" => $roleSlug 315 | ]; 316 | 317 | $response = Client::request( 318 | Client::METHOD_PUT, 319 | $path, 320 | null, 321 | $params, 322 | true 323 | ); 324 | 325 | return Resource\OrganizationMembership::constructFromResponse($response); 326 | } 327 | 328 | /** 329 | * List organization memberships. 330 | * 331 | * @param string|null $userId User ID 332 | * @param string|null $organizationId Organization ID 333 | * @param array|null $statuses Organization Membership statuses to filter 334 | * @param int $limit Maximum number of records to return 335 | * @param string|null $before Organization Membership ID to look before 336 | * @param string|null $after Organization Membership ID to look after 337 | * @param Resource\Order $order The Order in which to paginate records 338 | * 339 | * @throws Exception\WorkOSException 340 | * 341 | * @return array{?string, ?string, Resource\OrganizationMembership[]} An array containing the Organization Membership ID to use as before and after cursor, and a list of Organization Memberships instances 342 | */ 343 | public function listOrganizationMemberships( 344 | $userId = null, 345 | $organizationId = null, 346 | $statuses = null, 347 | $limit = self::DEFAULT_PAGE_SIZE, 348 | $before = null, 349 | $after = null, 350 | $order = null 351 | ) { 352 | $path = "user_management/organization_memberships"; 353 | 354 | if (isset($statuses)) { 355 | if (!is_array($statuses)) { 356 | $msg = "Invalid argument: statuses must be an array or null."; 357 | throw new Exception\UnexpectedValueException($msg); 358 | } 359 | 360 | $statuses = join(",", $statuses); 361 | } 362 | 363 | $params = [ 364 | "organization_id" => $organizationId, 365 | "user_id" => $userId, 366 | "statuses" => $statuses, 367 | "limit" => $limit, 368 | "before" => $before, 369 | "after" => $after, 370 | "order" => $order 371 | ]; 372 | 373 | $response = Client::request( 374 | Client::METHOD_GET, 375 | $path, 376 | null, 377 | $params, 378 | true 379 | ); 380 | 381 | $organizationMemberships = []; 382 | 383 | foreach ($response["data"] as $responseData) { 384 | \array_push($organizationMemberships, Resource\OrganizationMembership::constructFromResponse($responseData)); 385 | } 386 | 387 | list($before, $after) = Util\Request::parsePaginationArgs($response); 388 | 389 | return [$before, $after, $organizationMemberships]; 390 | } 391 | 392 | /** 393 | * Deactivate an Organization Membership. 394 | * 395 | * @param string $organizationMembershipId Organization Membership ID 396 | * 397 | * @throws Exception\WorkOSException 398 | * 399 | * @return Resource\OrganizationMembership 400 | */ 401 | public function deactivateOrganizationMembership($organizationMembershipId) 402 | { 403 | $path = "user_management/organization_memberships/{$organizationMembershipId}/deactivate"; 404 | 405 | $response = Client::request( 406 | Client::METHOD_PUT, 407 | $path, 408 | null, 409 | null, 410 | true 411 | ); 412 | 413 | return Resource\OrganizationMembership::constructFromResponse($response); 414 | } 415 | 416 | /** 417 | * Reactivate an Organization Membership. 418 | * 419 | * @param string $organizationMembershipId Organization Membership ID 420 | * 421 | * @throws Exception\WorkOSException 422 | * 423 | * @return Resource\OrganizationMembership 424 | */ 425 | public function reactivateOrganizationMembership($organizationMembershipId) 426 | { 427 | $path = "user_management/organization_memberships/{$organizationMembershipId}/reactivate"; 428 | 429 | $response = Client::request( 430 | Client::METHOD_PUT, 431 | $path, 432 | null, 433 | null, 434 | true 435 | ); 436 | 437 | return Resource\OrganizationMembership::constructFromResponse($response); 438 | } 439 | 440 | /** 441 | * Sends an Invitation 442 | * 443 | * @param string $email The email address of the invitee 444 | * @param string|null $organizationId Organization ID 445 | * @param int|null $expiresInDays expiration delay in days 446 | * @param string|null $inviterUserId User ID of the inviter 447 | * @param string|null $roleSlug Slug of the role to assign to the invitee User 448 | * 449 | * @throws Exception\WorkOSException 450 | * 451 | * @return Resource\Invitation 452 | */ 453 | public function sendInvitation( 454 | $email, 455 | $organizationId = null, 456 | $expiresInDays = null, 457 | $inviterUserId = null, 458 | $roleSlug = null 459 | ) { 460 | $path = "user_management/invitations"; 461 | 462 | $params = [ 463 | "email" => $email, 464 | "organization_id" => $organizationId, 465 | "expires_in_days" => $expiresInDays, 466 | "inviter_user_id" => $inviterUserId, 467 | "role_slug" => $roleSlug 468 | ]; 469 | 470 | $response = Client::request( 471 | Client::METHOD_POST, 472 | $path, 473 | null, 474 | $params, 475 | true 476 | ); 477 | 478 | return Resource\Invitation::constructFromResponse($response); 479 | } 480 | 481 | /** 482 | * Get an Invitation 483 | * 484 | * @param string $invitationId ID of the Invitation 485 | * 486 | * @throws Exception\WorkOSException 487 | * 488 | * @return Resource\Invitation 489 | */ 490 | public function getInvitation($invitationId) 491 | { 492 | $path = "user_management/invitations/{$invitationId}"; 493 | 494 | $response = Client::request( 495 | Client::METHOD_GET, 496 | $path, 497 | null, 498 | null, 499 | true 500 | ); 501 | 502 | return Resource\Invitation::constructFromResponse($response); 503 | } 504 | 505 | /** 506 | * Find an Invitation by Token 507 | * 508 | * @param string $invitationToken The token of the Invitation 509 | * 510 | * @throws Exception\WorkOSException 511 | * 512 | * @return Resource\Invitation 513 | */ 514 | public function findInvitationByToken($invitationToken) 515 | { 516 | $path = "user_management/invitations/by_token/{$invitationToken}"; 517 | 518 | $response = Client::request( 519 | Client::METHOD_GET, 520 | $path, 521 | null, 522 | null, 523 | true 524 | ); 525 | 526 | return Resource\Invitation::constructFromResponse($response); 527 | } 528 | 529 | /** 530 | * List Invitations 531 | * 532 | * @param string|null $email Email of the invitee 533 | * @param string|null $organizationId Organization ID 534 | * @param int $limit Maximum number of records to return 535 | * @param string|null $before Organization Membership ID to look before 536 | * @param string|null $after Organization Membership ID to look after 537 | * @param Resource\Order $order The Order in which to paginate records 538 | * 539 | * @throws Exception\WorkOSException 540 | * 541 | * @return array{?string, ?string, Resource\Invitation[]} An array containing the Invitation ID to use as before and after cursor, and a list of Invitations instances 542 | */ 543 | public function listInvitations( 544 | $email = null, 545 | $organizationId = null, 546 | $limit = self::DEFAULT_PAGE_SIZE, 547 | $before = null, 548 | $after = null, 549 | $order = null 550 | ) { 551 | $path = "user_management/invitations"; 552 | 553 | $params = [ 554 | "email" => $email, 555 | "organization_id" => $organizationId, 556 | "limit" => $limit, 557 | "before" => $before, 558 | "after" => $after, 559 | "order" => $order 560 | ]; 561 | 562 | $response = Client::request( 563 | Client::METHOD_GET, 564 | $path, 565 | null, 566 | $params, 567 | true 568 | ); 569 | 570 | $invitations = []; 571 | 572 | foreach ($response["data"] as $responseData) { 573 | \array_push($invitations, Resource\Invitation::constructFromResponse($responseData)); 574 | } 575 | 576 | list($before, $after) = Util\Request::parsePaginationArgs($response); 577 | 578 | return [$before, $after, $invitations]; 579 | } 580 | 581 | /** 582 | * Revoke an Invitation 583 | * 584 | * @param string $invitationId ID of the Invitation 585 | * 586 | * @throws Exception\WorkOSException 587 | * 588 | * @return Resource\Invitation 589 | */ 590 | public function revokeInvitation($invitationId) 591 | { 592 | $path = "user_management/invitations/{$invitationId}/revoke"; 593 | 594 | $response = Client::request( 595 | Client::METHOD_POST, 596 | $path, 597 | null, 598 | null, 599 | true 600 | ); 601 | 602 | return Resource\Invitation::constructFromResponse($response); 603 | } 604 | 605 | /** 606 | * Generates an OAuth 2.0 authorization URL used to initiate the SSO flow with WorkOS. 607 | * 608 | * @param string $redirectUri URI to direct the user to upon successful completion of SSO 609 | * @param null|array $state Associative array containing state that will be returned from WorkOS as a json encoded string 610 | * @param null|string $provider Service provider that handles the identity of the user 611 | * @param null|string $connectionId Unique identifier for a WorkOS Connection 612 | * @param null|string $organizationId Unique identifier for a WorkOS Organization 613 | * @param null|string $domainHint DDomain hint that will be passed as a parameter to the IdP login page 614 | * @param null|string $loginHint Username/email hint that will be passed as a parameter to the to IdP login page 615 | * @param null|string $screenHint The page that the user will be redirected to when the provider is authkit 616 | * 617 | * @throws Exception\UnexpectedValueException 618 | * @throws Exception\ConfigurationException 619 | * 620 | * @return string 621 | */ 622 | public function getAuthorizationUrl( 623 | $redirectUri, 624 | $state = null, 625 | $provider = null, 626 | $connectionId = null, 627 | $organizationId = null, 628 | $domainHint = null, 629 | $loginHint = null, 630 | $screenHint = null 631 | ) { 632 | $path = "user_management/authorize"; 633 | 634 | if (!isset($provider) && !isset($connectionId) && !isset($organizationId)) { 635 | $msg = "Either \$provider, \$connectionId, or \$organizationId is required"; 636 | throw new Exception\UnexpectedValueException($msg); 637 | } 638 | 639 | $supportedProviders = [ 640 | self::AUTHORIZATION_PROVIDER_AUTHKIT, 641 | self::AUTHORIZATION_PROVIDER_APPLE_OAUTH, 642 | self::AUTHORIZATION_PROVIDER_GITHUB_OAUTH, 643 | self::AUTHORIZATION_PROVIDER_GOOGLE_OAUTH, 644 | self::AUTHORIZATION_PROVIDER_MICROSOFT_OAUTH 645 | ]; 646 | 647 | if (isset($provider) && !\in_array($provider, $supportedProviders)) { 648 | $msg = "Only " . implode("','", $supportedProviders) . " providers are supported"; 649 | throw new Exception\UnexpectedValueException($msg); 650 | } 651 | 652 | $params = [ 653 | "client_id" => WorkOS::getClientId(), 654 | "response_type" => "code" 655 | ]; 656 | 657 | if ($redirectUri) { 658 | $params["redirect_uri"] = $redirectUri; 659 | } 660 | 661 | if (null !== $state && !empty($state)) { 662 | $params["state"] = \json_encode($state); 663 | } 664 | 665 | if ($provider) { 666 | $params["provider"] = $provider; 667 | } 668 | 669 | if ($connectionId) { 670 | $params["connection_id"] = $connectionId; 671 | } 672 | 673 | if ($organizationId) { 674 | $params["organization_id"] = $organizationId; 675 | } 676 | 677 | if ($domainHint) { 678 | $params["domain_hint"] = $domainHint; 679 | } 680 | 681 | if ($loginHint) { 682 | $params["login_hint"] = $loginHint; 683 | } 684 | 685 | if ($screenHint !== null) { 686 | if ($provider !== self::AUTHORIZATION_PROVIDER_AUTHKIT) { 687 | throw new Exception\UnexpectedValueException("A 'screenHint' can only be provided when the provider is 'authkit'."); 688 | } 689 | $params["screen_hint"] = $screenHint; 690 | } 691 | 692 | return Client::generateUrl($path, $params); 693 | } 694 | 695 | /** 696 | * Authenticate a User with Password 697 | * 698 | * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. 699 | * @param string $email The email address of the user. 700 | * @param string $password The password of the user. 701 | * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. 702 | * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. 703 | * 704 | * @throws Exception\WorkOSException 705 | * 706 | * @return Resource\AuthenticationResponse 707 | */ 708 | public function authenticateWithPassword($clientId, $email, $password, $ipAddress = null, $userAgent = null) 709 | { 710 | $path = "user_management/authenticate"; 711 | $params = [ 712 | "client_id" => $clientId, 713 | "email" => $email, 714 | "password" => $password, 715 | "ip_address" => $ipAddress, 716 | "user_agent" => $userAgent, 717 | "grant_type" => "password", 718 | "client_secret" => WorkOS::getApiKey() 719 | ]; 720 | 721 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 722 | 723 | return Resource\AuthenticationResponse::constructFromResponse($response); 724 | } 725 | 726 | /** 727 | * Authenticate a User with Selected Organization 728 | * 729 | * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. 730 | * @param string $pendingAuthenticationToken Token returned from a failed authentication attempt due to organization selection being required. 731 | * @param string $organizationId The Organization ID the user selected. 732 | * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. 733 | * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. 734 | * 735 | * @throws Exception\WorkOSException 736 | * 737 | * @return Resource\AuthenticationResponse 738 | */ 739 | public function authenticateWithSelectedOrganization( 740 | $clientId, 741 | $pendingAuthenticationToken, 742 | $organizationId, 743 | $ipAddress = null, 744 | $userAgent = null 745 | ) { 746 | $path = "user_management/authenticate"; 747 | $params = [ 748 | "client_id" => $clientId, 749 | "pending_authentication_token" => $pendingAuthenticationToken, 750 | "organization_id" => $organizationId, 751 | "ip_address" => $ipAddress, 752 | "user_agent" => $userAgent, 753 | "grant_type" => "urn:workos:oauth:grant-type:organization-selection", 754 | "client_secret" => WorkOS::getApiKey() 755 | ]; 756 | 757 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 758 | 759 | return Resource\AuthenticationResponse::constructFromResponse($response); 760 | } 761 | 762 | /** 763 | * Authenticate an OAuth or SSO User with a Code 764 | * This should be used for "Hosted AuthKit" and "OAuth or SSO" UserAuthentications 765 | * 766 | * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. 767 | * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. 768 | * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. 769 | * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. 770 | * 771 | * @throws Exception\WorkOSException 772 | * 773 | * @return Resource\AuthenticationResponse 774 | */ 775 | public function authenticateWithCode($clientId, $code, $ipAddress = null, $userAgent = null) 776 | { 777 | $path = "user_management/authenticate"; 778 | $params = [ 779 | "client_id" => $clientId, 780 | "code" => $code, 781 | "ip_address" => $ipAddress, 782 | "user_agent" => $userAgent, 783 | "grant_type" => "authorization_code", 784 | "client_secret" => WorkOS::getApiKey() 785 | ]; 786 | 787 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 788 | 789 | return Resource\AuthenticationResponse::constructFromResponse($response); 790 | } 791 | 792 | /** 793 | * Authenticates a user with an unverified email and verifies their email address. 794 | * 795 | * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. 796 | * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. 797 | * @param string $pendingAuthenticationToken Token returned from a failed authentication attempt due to organization selection being required. 798 | * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. 799 | * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. 800 | * 801 | * @throws Exception\WorkOSException 802 | * 803 | * @return Resource\AuthenticationResponse 804 | */ 805 | public function authenticateWithEmailVerification($clientId, $code, $pendingAuthenticationToken, $ipAddress = null, $userAgent = null) 806 | { 807 | $path = "user_management/authenticate"; 808 | $params = [ 809 | "client_id" => $clientId, 810 | "code" => $code, 811 | "pending_authentication_token" => $pendingAuthenticationToken, 812 | "ip_address" => $ipAddress, 813 | "user_agent" => $userAgent, 814 | "grant_type" => "urn:workos:oauth:grant-type:email-verification:code", 815 | "client_secret" => WorkOS::getApiKey() 816 | ]; 817 | 818 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 819 | 820 | return Resource\AuthenticationResponse::constructFromResponse($response); 821 | } 822 | 823 | /** 824 | * Authenticate with Magic Auth 825 | * 826 | * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. 827 | * @param string $code The authorization value which was passed back as a query parameter in the callback to the Redirect URI. 828 | * @param string $userId The unique ID of the user. 829 | * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. 830 | * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. 831 | * 832 | * @throws Exception\WorkOSException 833 | * 834 | * @return Resource\AuthenticationResponse 835 | */ 836 | 837 | public function authenticateWithMagicAuth( 838 | $clientId, 839 | $code, 840 | $userId, 841 | $ipAddress = null, 842 | $userAgent = null 843 | ) { 844 | $path = "user_management/authenticate"; 845 | $params = [ 846 | "client_id" => $clientId, 847 | "code" => $code, 848 | "user_id" => $userId, 849 | "ip_address" => $ipAddress, 850 | "user_agent" => $userAgent, 851 | "grant_type" => "urn:workos:oauth:grant-type:magic-auth:code", 852 | "client_secret" => WorkOS::getApiKey() 853 | ]; 854 | 855 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 856 | 857 | return Resource\AuthenticationResponse::constructFromResponse($response); 858 | } 859 | 860 | /** 861 | * Authenticate with Refresh Token 862 | * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. 863 | * @param string $refreshToken The refresh token used to obtain a new access token 864 | * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. 865 | * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. 866 | * @param string|null $organizationId The user agent of the request from the user who is attempting to authenticate. 867 | * 868 | * @throws Exception\WorkOSException 869 | * 870 | * @return Resource\AuthenticationResponse 871 | */ 872 | public function authenticateWithRefreshToken( 873 | $clientId, 874 | $refreshToken, 875 | $ipAddress = null, 876 | $userAgent = null, 877 | $organizationId = null 878 | ) { 879 | $path = "user_management/authenticate"; 880 | $params = [ 881 | "client_id" => $clientId, 882 | "refresh_token" => $refreshToken, 883 | "organization_id" => $organizationId, 884 | "ip_address" => $ipAddress, 885 | "user_agent" => $userAgent, 886 | "grant_type" => "refresh_token", 887 | "client_secret" => WorkOS::getApiKey() 888 | ]; 889 | 890 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 891 | 892 | return Resource\AuthenticationResponse::constructFromResponse($response); 893 | } 894 | 895 | /** 896 | * Authenticate with TOTP 897 | * 898 | * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. 899 | * @param string $pendingAuthenticationToken 900 | * @param string $authenticationChallengeId 901 | * @param string $code 902 | * @param string|null $ipAddress The IP address of the request from the user who is attempting to authenticate. 903 | * @param string|null $userAgent The user agent of the request from the user who is attempting to authenticate. 904 | * 905 | * @throws Exception\WorkOSException 906 | * 907 | * @return Resource\AuthenticationResponse 908 | */ 909 | public function authenticateWithTotp( 910 | $clientId, 911 | $pendingAuthenticationToken, 912 | $authenticationChallengeId, 913 | $code, 914 | $ipAddress = null, 915 | $userAgent = null 916 | ) { 917 | $path = "user_management/authenticate"; 918 | $params = [ 919 | "client_id" => $clientId, 920 | "pending_authentication_token" => $pendingAuthenticationToken, 921 | "authentication_challenge_id" => $authenticationChallengeId, 922 | "code" => $code, 923 | "ip_address" => $ipAddress, 924 | "user_agent" => $userAgent, 925 | "grant_type" => "urn:workos:oauth:grant-type:mfa-totp", 926 | "client_secret" => WorkOS::getApiKey() 927 | ]; 928 | 929 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 930 | 931 | return Resource\AuthenticationResponse::constructFromResponse($response); 932 | } 933 | 934 | /** 935 | * Enroll An Authentication Factor. 936 | * 937 | * @param string $userId The unique ID of the user. 938 | * @param string $type The type of MFA factor used to authenticate. 939 | * @param string|null $totpIssuer Your application or company name, this helps users distinguish between factors in authenticator apps. 940 | * @param string|null $totpUser Used as the account name in authenticator apps. 941 | * 942 | * @throws Exception\WorkOSException 943 | * 944 | * @return Resource\AuthenticationFactorAndChallengeTotp 945 | */ 946 | public function enrollAuthFactor($userId, $type, $totpIssuer = null, $totpUser = null) 947 | { 948 | $path = "user_management/users/{$userId}/auth_factors"; 949 | 950 | $params = [ 951 | "type" => $type, 952 | "totp_user" => $totpUser, 953 | "totp_issuer" => $totpIssuer 954 | ]; 955 | 956 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 957 | 958 | return Resource\AuthenticationFactorAndChallengeTotp::constructFromResponse($response); 959 | } 960 | 961 | /** 962 | * List a User's Authentication Factors. 963 | * 964 | * @param string $userId The unique ID of the user. 965 | * 966 | * @throws Exception\WorkOSException 967 | * 968 | * @return Resource\UserAuthenticationFactorTotp[] $authFactors A list of user's authentication factors 969 | */ 970 | public function listAuthFactors($userId) 971 | { 972 | $path = "user_management/users/{$userId}/auth_factors"; 973 | 974 | $response = Client::request(Client::METHOD_GET, $path, null, null, true); 975 | 976 | $authFactors = []; 977 | 978 | foreach ($response["data"] as $responseData) { 979 | \array_push($authFactors, Resource\UserAuthenticationFactorTotp::constructFromResponse($responseData)); 980 | } 981 | 982 | return $authFactors; 983 | } 984 | 985 | /** 986 | * Get an email verification object 987 | * 988 | * @param string $emailVerificationId ID of the email verification object 989 | * 990 | * @throws Exception\WorkOSException 991 | * 992 | * @return Resource\EmailVerification 993 | */ 994 | public function getEmailVerification($emailVerificationId) 995 | { 996 | $path = "user_management/email_verification/{$emailVerificationId}"; 997 | 998 | $response = Client::request( 999 | Client::METHOD_GET, 1000 | $path, 1001 | null, 1002 | null, 1003 | true 1004 | ); 1005 | 1006 | return Resource\EmailVerification::constructFromResponse($response); 1007 | } 1008 | 1009 | /** 1010 | * Create Email Verification Challenge. 1011 | * 1012 | * @param string $userId The unique ID of the User whose email address will be verified. 1013 | * 1014 | * @throws Exception\WorkOSException 1015 | * 1016 | * @return Resource\UserResponse 1017 | */ 1018 | public function sendVerificationEmail($userId) 1019 | { 1020 | $path = "user_management/users/{$userId}/email_verification/send"; 1021 | 1022 | $response = Client::request(Client::METHOD_POST, $path, null, null, true); 1023 | 1024 | return Resource\UserResponse::constructFromResponse($response); 1025 | } 1026 | 1027 | /** 1028 | * Complete Email Verification. 1029 | * 1030 | * @param string $userId The unique ID of the user. 1031 | * @param string $code The one-time code emailed to the user. 1032 | * 1033 | * @throws Exception\WorkOSException 1034 | * 1035 | * @return Resource\UserResponse 1036 | */ 1037 | public function verifyEmail($userId, $code) 1038 | { 1039 | $path = "user_management/users/{$userId}/email_verification/confirm"; 1040 | 1041 | $params = [ 1042 | "code" => $code 1043 | ]; 1044 | 1045 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 1046 | 1047 | return Resource\UserResponse::constructFromResponse($response); 1048 | } 1049 | 1050 | /** 1051 | * Get a password reset object 1052 | * 1053 | * @param string $passwordResetId ID of the password reset object 1054 | * 1055 | * @throws Exception\WorkOSException 1056 | * 1057 | * @return Resource\PasswordReset 1058 | */ 1059 | public function getPasswordReset($passwordResetId) 1060 | { 1061 | $path = "user_management/password_reset/{$passwordResetId}"; 1062 | 1063 | $response = Client::request( 1064 | Client::METHOD_GET, 1065 | $path, 1066 | null, 1067 | null, 1068 | true 1069 | ); 1070 | 1071 | return Resource\PasswordReset::constructFromResponse($response); 1072 | } 1073 | 1074 | /** 1075 | * Creates a password reset token 1076 | * 1077 | * @param string $email The email address of the user 1078 | * 1079 | * @throws Exception\WorkOSException 1080 | * 1081 | * @return Resource\PasswordReset 1082 | */ 1083 | public function createPasswordReset( 1084 | $email 1085 | ) { 1086 | $path = "user_management/password_reset"; 1087 | 1088 | $params = [ 1089 | "email" => $email 1090 | ]; 1091 | 1092 | $response = Client::request( 1093 | Client::METHOD_POST, 1094 | $path, 1095 | null, 1096 | $params, 1097 | true 1098 | ); 1099 | 1100 | return Resource\PasswordReset::constructFromResponse($response); 1101 | } 1102 | 1103 | /** 1104 | * @deprecated 4.9.0 Use `createPasswordReset` instead. This method will be removed in a future major version. 1105 | * Create Password Reset Email. 1106 | * 1107 | * @param string $email The email of the user that wishes to reset their password. 1108 | * @param string $passwordResetUrl The URL that will be linked to in the email. 1109 | * 1110 | * @throws Exception\WorkOSException 1111 | * 1112 | * @return Resource\Response 1113 | */ 1114 | public function sendPasswordResetEmail($email, $passwordResetUrl) 1115 | { 1116 | $msg = "'sendPasswordResetEmail' is deprecated. Please use 'createPasswordReset' instead. This method will be removed in a future major version."; 1117 | 1118 | error_log($msg); 1119 | 1120 | $path = "user_management/password_reset/send"; 1121 | 1122 | $params = [ 1123 | "email" => $email, 1124 | "password_reset_url" => $passwordResetUrl 1125 | ]; 1126 | 1127 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 1128 | 1129 | return $response; 1130 | } 1131 | 1132 | /** 1133 | * Complete Password Reset. 1134 | * 1135 | * @param string $token The reset token emailed to the user. 1136 | * @param string $newPassword The new password to be set for the user. 1137 | * 1138 | * @throws Exception\WorkOSException 1139 | * 1140 | * @return Resource\UserResponse 1141 | */ 1142 | public function resetPassword($token, $newPassword) 1143 | { 1144 | $path = "user_management/password_reset/confirm"; 1145 | 1146 | $params = [ 1147 | "token" => $token, 1148 | "new_password" => $newPassword 1149 | ]; 1150 | 1151 | $response = Client::request(Client::METHOD_POST, $path, null, $params, true); 1152 | 1153 | return Resource\UserResponse::constructFromResponse($response); 1154 | } 1155 | 1156 | /** 1157 | * Get a Magic Auth object 1158 | * 1159 | * @param string $magicAuthId ID of the Magic Auth object 1160 | * 1161 | * @throws Exception\WorkOSException 1162 | * 1163 | * @return Resource\MagicAuth 1164 | */ 1165 | public function getMagicAuth($magicAuthId) 1166 | { 1167 | $path = "user_management/magic_auth/{$magicAuthId}"; 1168 | 1169 | $response = Client::request( 1170 | Client::METHOD_GET, 1171 | $path, 1172 | null, 1173 | null, 1174 | true 1175 | ); 1176 | 1177 | return Resource\MagicAuth::constructFromResponse($response); 1178 | } 1179 | 1180 | /** 1181 | * Creates a Magic Auth code 1182 | * 1183 | * @param string $email The email address of the user 1184 | * @param string|null $invitationToken The token of an Invitation, if required. 1185 | * 1186 | * @throws Exception\WorkOSException 1187 | * 1188 | * @return Resource\MagicAuth 1189 | */ 1190 | public function createMagicAuth( 1191 | $email, 1192 | $invitationToken = null 1193 | ) { 1194 | $path = "user_management/magic_auth"; 1195 | 1196 | $params = [ 1197 | "email" => $email, 1198 | "invitation_token" => $invitationToken 1199 | ]; 1200 | 1201 | $response = Client::request( 1202 | Client::METHOD_POST, 1203 | $path, 1204 | null, 1205 | $params, 1206 | true 1207 | ); 1208 | 1209 | return Resource\MagicAuth::constructFromResponse($response); 1210 | } 1211 | 1212 | /** 1213 | * @deprecated 4.6.0 Use `createMagicAuth` instead. This method will be removed in a future major version. 1214 | * Creates a one-time Magic Auth code and emails it to the user. 1215 | * 1216 | * @param string $email The email address the one-time code will be sent to. 1217 | * 1218 | * @throws Exception\WorkOSException 1219 | * 1220 | * @return Resource\Response 1221 | */ 1222 | public function sendMagicAuthCode($email) 1223 | { 1224 | $path = "user_management/magic_auth/send"; 1225 | 1226 | $params = [ 1227 | "email" => $email, 1228 | ]; 1229 | 1230 | $msg = "'sendMagicAuthCode' is deprecated. Please use 'createMagicAuth' instead. This method will be removed in a future major version."; 1231 | 1232 | error_log($msg); 1233 | 1234 | $response = Client::request( 1235 | Client::METHOD_POST, 1236 | $path, 1237 | null, 1238 | $params, 1239 | true 1240 | ); 1241 | 1242 | return $response; 1243 | } 1244 | 1245 | /** 1246 | * Returns the public key host that is used for verifying access tokens. 1247 | * 1248 | * @param string $clientId This value can be obtained from the API Keys page in the WorkOS dashboard. 1249 | * 1250 | * @throws Exception\UnexpectedValueException 1251 | * 1252 | * @return string 1253 | */ 1254 | public function getJwksUrl(string $clientId) 1255 | { 1256 | if (!isset($clientId) || empty($clientId)) { 1257 | throw new Exception\UnexpectedValueException("clientId must not be empty"); 1258 | } 1259 | 1260 | $baseUrl = WorkOS::getApiBaseUrl(); 1261 | 1262 | return "{$baseUrl}sso/jwks/{$clientId}"; 1263 | } 1264 | 1265 | /** 1266 | * Returns the logout URL to end a user's session and redirect to your home page. 1267 | * 1268 | * @param string $sessionId The session ID of the user. 1269 | * @param string $return_to The URL to redirect to after the user logs out. 1270 | * 1271 | * @return string 1272 | */ 1273 | public function getLogoutUrl(string $sessionId, string $return_to = null) 1274 | { 1275 | if (!isset($sessionId) || empty($sessionId)) { 1276 | throw new Exception\UnexpectedValueException("sessionId must not be empty"); 1277 | } 1278 | 1279 | $params = [ "session_id" => $sessionId ]; 1280 | if ($return_to) { 1281 | $params["return_to"] = $return_to; 1282 | } 1283 | 1284 | return Client::generateUrl("user_management/sessions/logout", $params); 1285 | } 1286 | } 1287 | -------------------------------------------------------------------------------- /lib/Util/Request.php: -------------------------------------------------------------------------------- 1 | verifyHeader($sigHeader, $payload, $secret, $tolerance); 22 | 23 | if ($eventResult == 'pass') { 24 | return Resource\Webhook::constructFromPayload($payload); 25 | } else { 26 | return $eventResult; 27 | } 28 | } 29 | 30 | /** 31 | * Verifies the header returned from WorkOS contains a valid timestamp 32 | * no older than 3 minutes, and computes the signature. 33 | * 34 | * @param string $sigHeader WorkOS header containing v1 signature and timestamp 35 | * @param string $payload Body of the webhook 36 | * @param string $secret Webhook secret from the WorkOS dashboard 37 | * @param int $tolerance Number of seconds old the webhook can be before it's invalid 38 | * @return bool true 39 | */ 40 | public function verifyHeader($sigHeader, $payload, $secret, $tolerance) 41 | { 42 | $timestamp = (int) $this->getTimeStamp($sigHeader); 43 | $signature = $this->getSignature($sigHeader); 44 | 45 | $currentTime = time(); 46 | $expectedSignature = $this->computeSignature($timestamp, $payload, $secret); 47 | 48 | if (empty($timestamp)) { 49 | return 'No Timestamp available'; 50 | } elseif (empty($signature)) { 51 | return 'No signature hash found with expected scheme v1'; 52 | } elseif ($timestamp < $currentTime - $tolerance) { 53 | return 'Timestamp outside of tolerance'; 54 | } elseif ($signature != $expectedSignature) { 55 | return 'Constructed signature '.$expectedSignature.'Does not match WorkOS Header Signature '.$signature; 56 | } else { 57 | return 'pass'; 58 | } 59 | } 60 | 61 | /** 62 | * Splits WorkOS header's two values and pulls out timestamp value and returns it 63 | * 64 | * @param string $sigHeader WorkOS header containing v1 signature and timestamp 65 | * @return $timestamp 66 | */ 67 | public function getTimeStamp($sigHeader) 68 | { 69 | $workosHeadersSplit = explode(',', $sigHeader, 2); 70 | $timestamp = substr($workosHeadersSplit[0], 2); 71 | 72 | return $timestamp; 73 | } 74 | 75 | /** 76 | * Splits WorkOS headers two values and pulls out the signature value and returns it 77 | * 78 | * @param string $sigHeader WorkOS header containing v1 signature and timestamp 79 | * @return string 80 | */ 81 | public function getSignature($sigHeader) 82 | { 83 | $workosHeadersSplit = explode(',', $sigHeader, 2); 84 | $signature = substr($workosHeadersSplit[1], 4); 85 | 86 | return $signature; 87 | } 88 | 89 | /** 90 | * Computes a signature for a webhook payload using the provided timestamp and secret 91 | * 92 | * @param int $timestamp Unix timestamp to use in signature 93 | * @param string $payload The payload to sign 94 | * @param string $secret Secret key used for signing 95 | * @return string The computed HMAC SHA-256 signature 96 | */ 97 | public function computeSignature($timestamp, $payload, $secret) 98 | { 99 | $signedPayload = $timestamp . '.' . $payload; 100 | return hash_hmac('sha256', $signedPayload, $secret, false); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/Widgets.php: -------------------------------------------------------------------------------- 1 | $organization_id, 28 | "user_id" => $user_id, 29 | "scopes" => $scopes, 30 | ]; 31 | 32 | $response = Client::request(Client::METHOD_POST, $getTokenPath, null, $params, true); 33 | 34 | return Resource\WidgetTokenResponse::constructFromResponse($response); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/WorkOS.php: -------------------------------------------------------------------------------- 1 |