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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,34 @@ $response = $descopeSDK->auth->sso->exchangeToken("code");
print_r($response);
```

### OTP (One-Time Password)

The delivery method is one of `"email"`, `"sms"`, `"whatsapp"` or `"voice"`.

```php
// Send a code to a new or existing user
$descopeSDK->otp->signUp("email", "loginId", ["email" => "user@example.com"]);
$descopeSDK->otp->signIn("email", "loginId");
$descopeSDK->otp->signUpOrIn("email", "loginId");

// Verify the received code and get a session
$response = $descopeSDK->otp->verifyCode("email", "loginId", "123456");
print_r($response);
```

### Magic Link

```php
// Send a magic link containing the given redirect URI
$descopeSDK->magicLink->signUp("email", "loginId", "https://example.com/verify", ["email" => "user@example.com"]);
$descopeSDK->magicLink->signIn("email", "loginId", "https://example.com/verify");
$descopeSDK->magicLink->signUpOrIn("email", "loginId", "https://example.com/verify");

// Verify the token extracted from the magic link and get a session
$response = $descopeSDK->magicLink->verify("token");
print_r($response);
```

### Session Management

1. `DescopeSDK->verify($sessionToken)` - will validate the session token and return either **TRUE** or **FALSE**, depending on if the JWT is valid and expired.
Expand All @@ -254,6 +282,9 @@ print_r($response);

6. `DescopeSDK->getClaims($sessionToken)` - will validate the JWT signature and return all of the verified claims in an array format.
7. `DescopeSDK->getUserDetails($refreshToken)` - will return all of the user information (email, phone, verification status, etc.) using a provided refresh token.
8. `DescopeSDK->selectTenant($tenantId, $refreshToken)` - will return a new set of tokens scoped to the selected tenant.
9. `DescopeSDK->exchangeAccessKey($accessKey, $loginOptions)` - will exchange an access key for a session JWT.
10. `DescopeSDK->history($refreshToken)` - will return the current user's authentication history.

### User Management Functions

Expand Down Expand Up @@ -461,6 +492,133 @@ $descopeSDK->management->user->setTemporaryPassword("testuser1", new UserPasswor
$descopeSDK->management->user->setActivePassword("testuser1", new UserPassword(cleartext: "activePassword123"));
```

### Tenant Management Functions

Manage tenants for multi-tenant applications.

```php
// Create a tenant (id is optional; one is generated if omitted)
$response = $descopeSDK->management->tenant->create("My Tenant", null, ["example.com"], ["plan" => "pro"]);
$tenantId = $response["id"];

// Update a tenant (overwrites all fields)
$descopeSDK->management->tenant->update($tenantId, "My Renamed Tenant", ["example.com"]);

// Load a single tenant / all tenants
$tenant = $descopeSDK->management->tenant->load($tenantId);
$all = $descopeSDK->management->tenant->loadAll();

// Search tenants
$found = $descopeSDK->management->tenant->searchAll([], ["My Renamed Tenant"]);

// Delete a tenant (cascade removes its users/keys)
$descopeSDK->management->tenant->delete($tenantId, false);
```

### Role Management Functions

```php
$descopeSDK->management->role->create("My Role", "role description", ["Read", "Write"]);
$descopeSDK->management->role->update("My Role", "My Renamed Role", "updated", ["Read"]);
$roles = $descopeSDK->management->role->loadAll();
$matches = $descopeSDK->management->role->search([], ["My Renamed Role"]);
$descopeSDK->management->role->delete("My Renamed Role");
```

### Permission Management Functions

```php
$descopeSDK->management->permission->create("Read", "can read");
$descopeSDK->management->permission->update("Read", "ReadOnly", "can read only");
$permissions = $descopeSDK->management->permission->loadAll();
$descopeSDK->management->permission->delete("ReadOnly");
```

### Access Key Management Functions

```php
// Create an access key; the cleartext is only returned once, on creation
$response = $descopeSDK->management->accessKey->create("My Key", 0, ["My Role"]);
$cleartext = $response["cleartext"];
$keyId = $response["key"]["id"];

$descopeSDK->management->accessKey->load($keyId);
$descopeSDK->management->accessKey->searchAll();
$descopeSDK->management->accessKey->update($keyId, "My Renamed Key");
$descopeSDK->management->accessKey->deactivate($keyId);
$descopeSDK->management->accessKey->activate($keyId);
$descopeSDK->management->accessKey->delete($keyId);
```

### SSO Application Management Functions

Manage SSO (IdP) applications your project exposes to relying parties.

```php
// OIDC application
$response = $descopeSDK->management->ssoApplication->createOidcApplication("My OIDC App", "https://login.example.com");
$appId = $response["id"];
$descopeSDK->management->ssoApplication->updateOidcApplication($appId, "My OIDC App", "https://login.example.com");

// SAML application
$descopeSDK->management->ssoApplication->createSamlApplication("My SAML App", "https://login.example.com");

$descopeSDK->management->ssoApplication->load($appId);
$descopeSDK->management->ssoApplication->loadAll();
$descopeSDK->management->ssoApplication->delete($appId);
```

### SSO Settings (Tenant SSO Configuration)

Configure the SSO provider used by a tenant. `management->sso` is the tenant SSO configuration component (distinct from the auth-flow `$descopeSDK->sso`).

```php
$settings = $descopeSDK->management->sso->loadSettings("tenantId1");

// Configure OIDC for a tenant
$descopeSDK->management->sso->configureOIDCSettings("tenantId1", [
"name" => "MyOIDC",
"clientId" => "clientId",
"clientSecret" => "clientSecret",
"redirectUrl" => "https://example.com/callback",
"authUrl" => "https://idp.example.com/authorize",
"tokenUrl" => "https://idp.example.com/token",
"userDataUrl" => "https://idp.example.com/userinfo",
"scope" => ["openid", "email"],
], ["example.com"]);

// Configure SAML for a tenant
$descopeSDK->management->sso->configureSAMLSettings("tenantId1", [
"idpUrl" => "https://idp.example.com/sso",
"entityId" => "entityId",
"idpCert" => "-----BEGIN CERTIFICATE----- ...",
], "https://example.com/callback", ["example.com"]);

$descopeSDK->management->sso->deleteSettings("tenantId1");
```

### JWT Management Functions

```php
// Update a JWT with custom claims
$newJwt = $descopeSDK->management->jwt->updateJWT($jwt, ["myClaim" => "value"]);

// Impersonate a user (requires the impersonator to have permission)
$impersonatedJwt = $descopeSDK->management->jwt->impersonate("impersonatorId", "targetLoginId", true);
```

### Flow & Theme Management Functions

```php
$flows = $descopeSDK->management->flow->listFlows();
$exported = $descopeSDK->management->flow->exportFlow("sign-up-or-in");
$descopeSDK->management->flow->importFlow("sign-up-or-in", $exported["flow"], $exported["screens"] ?? []);
$descopeSDK->management->flow->delete(["old-flow-id"]);

$theme = $descopeSDK->management->flow->exportTheme();
$descopeSDK->management->flow->importTheme($theme["theme"]);
```

## Password Management

The SDK provides several classes for handling different types of passwords and password hashes. Here's how to use them:
Expand Down
2 changes: 2 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<file>src/tests/APIRetryTest.php</file>
<file>src/tests/StaticStateIsolationTest.php</file>
<file>src/tests/EndpointsTest.php</file>
<file>src/tests/Management/ManagementParityTest.php</file>
<file>src/tests/Auth/AuthParityTest.php</file>
</testsuite>
</testsuites>
</phpunit>
166 changes: 166 additions & 0 deletions src/SDK/Auth/MagicLink.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?php

declare(strict_types=1);

namespace Descope\SDK\Auth;

use Descope\SDK\Exception\AuthException;
use Descope\SDK\EndpointsV1;
use Descope\SDK\API;

/**
* Provides magic-link authentication flows.
*
* The delivery method is one of "email", "sms", "whatsapp" or "voice" and is
* appended to the base endpoint path, matching the Descope REST API. The magic
* link is delivered to the user; the resulting token is verified via verify().
*/
class MagicLink
{
private const METHODS = ['email', 'sms', 'whatsapp', 'voice'];

/**
* @var API The API object for making authenticated requests.
*/
private $api;

/**
* Constructor for MagicLink class.
*
* @param API $api API object for making authenticated requests.
*/
public function __construct(API $api)
{
$this->api = $api;
}

/**
* Sign up a new user and send them a magic link.
*
* @param string $deliveryMethod One of "email", "sms", "whatsapp", "voice".
* @param string $loginId The login ID of the user being signed up.
* @param string $uri The redirect URI embedded in the magic link.
* @param array|null $user Optional user details (e.g. email, phone, name).
* @param array|null $signUpOptions Optional sign-up options (customClaims, templateOptions).
* @return string The masked address the magic link was sent to.
* @throws AuthException
*/
public function signUp(string $deliveryMethod, string $loginId, string $uri, ?array $user = null, ?array $signUpOptions = null): string
{
$method = $this->validateMethod($deliveryMethod);
$this->validateLoginId($loginId);

$body = ['loginId' => $loginId, 'uri' => $uri];
if ($user !== null) {
$body['user'] = $user;
}
if ($signUpOptions !== null) {
$body['loginOptions'] = $signUpOptions;
}

$endpoint = EndpointsV1::$SIGN_UP_AUTH_MAGICLINK_PATH . '/' . $method;
$response = $this->api->doPost($endpoint, $body, false);

return $response[$method] ?? '';
}

/**
* Sign in an existing user and send them a magic link.
*
* @param string $deliveryMethod One of "email", "sms", "whatsapp", "voice".
* @param string $loginId The login ID of the user signing in.
* @param string $uri The redirect URI embedded in the magic link.
* @param array|null $loginOptions Optional login options.
* @param string|null $refreshToken Optional refresh token for step-up/MFA.
* @return string The masked address the magic link was sent to.
* @throws AuthException
*/
public function signIn(string $deliveryMethod, string $loginId, string $uri, ?array $loginOptions = null, ?string $refreshToken = null): string
{
$method = $this->validateMethod($deliveryMethod);
$this->validateLoginId($loginId);

$body = ['loginId' => $loginId, 'uri' => $uri];
if ($loginOptions !== null) {
$body['loginOptions'] = $loginOptions;
}

$endpoint = EndpointsV1::$SIGN_IN_AUTH_MAGICLINK_PATH . '/' . $method;
$response = $this->api->doPost($endpoint, $body, false, $refreshToken);

return $response[$method] ?? '';
}

/**
* Sign up or sign in a user (whichever applies) and send them a magic link.
*
* @param string $deliveryMethod One of "email", "sms", "whatsapp", "voice".
* @param string $loginId The login ID of the user.
* @param string $uri The redirect URI embedded in the magic link.
* @param array|null $loginOptions Optional login options.
* @return string The masked address the magic link was sent to.
* @throws AuthException
*/
public function signUpOrIn(string $deliveryMethod, string $loginId, string $uri, ?array $loginOptions = null): string
{
$method = $this->validateMethod($deliveryMethod);
$this->validateLoginId($loginId);

$body = ['loginId' => $loginId, 'uri' => $uri];
if ($loginOptions !== null) {
$body['loginOptions'] = $loginOptions;
}

$endpoint = EndpointsV1::$SIGN_UP_OR_IN_AUTH_MAGICLINK_PATH . '/' . $method;
$response = $this->api->doPost($endpoint, $body, false);

return $response[$method] ?? '';
}

/**
* Verify a magic-link token and complete the authentication.
*
* @param string $token The token extracted from the magic link.
* @return array JWT response array.
* @throws AuthException
*/
public function verify(string $token): array
{
if (empty($token)) {
throw new AuthException(400, 'invalid argument', 'token cannot be empty');
}

$response = $this->api->doPost(EndpointsV1::$VERIFY_MAGICLINK_AUTH_PATH, ['token' => $token], false);

return $this->api->generateJwtResponse($response, $response['refreshJwt'] ?? null, null);
}

/**
* Validates the delivery method and returns its normalized string form.
*
* @param string $deliveryMethod The requested delivery method.
* @return string The normalized (lowercase) delivery method.
* @throws AuthException
*/
private function validateMethod(string $deliveryMethod): string
{
$method = strtolower($deliveryMethod);
if (!in_array($method, self::METHODS, true)) {
throw new AuthException(400, 'invalid argument', "Unknown delivery method: $deliveryMethod");
}
return $method;
}

/**
* Validates the login ID.
*
* @param string $loginId The login ID.
* @throws AuthException
*/
private function validateLoginId(string $loginId): void
{
if (empty($loginId)) {
throw new AuthException(400, 'invalid argument', 'login_id cannot be empty');
}
}
}
Loading
Loading