1. Go to this page and download the library: Download axiam/axiam-sdk library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
axiam / axiam-sdk example snippets
use Axiam\Sdk\AxiamClient;
use Axiam\Sdk\Core\AuthError;
// `tenant` is a REQUIRED constructor argument — AXIAM is multi-tenant and there is no
// default tenant. There is no overload that lets you omit it. `login()`/`refresh()` also
// e',
orgSlug: 'acme',
);
try {
$result = $client->login('[email protected]', 'correct horse battery staple');
} catch (AuthError $e) {
// typed exception hierarchy (AuthError/AuthzError/NetworkError), never a bare status code
exit(1);
}
if ($result->mfaRequired) {
$result = $client->verifyMfa($result->challengeToken, $totpCode);
}
// Same $client instance — the authenticated session's cookies/CSRF are shared automatically.
// `can(action, resource)` — same argument order as every other AXIAM SDK (CONTRACT.md §1).
$allowed = $client->can('read', 'documents');
if ($result->organizationLevel) {
// This principal can act on another tenant by sending a different X-Tenant-ID on
// the next request — no re-login, because it already is a principal of every
// tenant in the organization. Offer the tenant selector.
}
// config/axiam.php (Laravel) — or the AXIAM_EXPECTED_* environment variables.
return [
'base_url' => env('AXIAM_BASE_URL'),
'tenant' => env('AXIAM_TENANT'),
// CONDITIONAL (§10.1 rules 5 and 6). Omit either to skip that check entirely.
'expected_issuer' => env('AXIAM_EXPECTED_ISSUER'),
'expected_audience' => env('AXIAM_EXPECTED_AUDIENCE'),
];
use Axiam\Sdk\Attributes\RequireAccess;
final class DocumentController
{
// Resolves the resource UUID from the {id} route parameter, checks 'read' for
// the REQUEST'S authenticated user (never the shared AxiamClient's own session),
// and returns 401/400/403/503 automatically on failure.
#[RequireAccess(action: 'read', resourceParam: 'id')]
public function show(string $id) { /* ... */ }
}
use Axiam\Sdk\AxiamClient;
use Axiam\Sdk\Core\AuthError;
$client = new AxiamClient(
baseUrl: 'https://api.axiam.example',
tenant: 'acme',
oidcClientId: 'my-app',
oidcClientSecret: getenv('AXIAM_OIDC_CLIENT_SECRET') ?: null, // omit for a public client
oidcTenantId: '11111111-1111-1111-1111-111111111111', // UUID for the /oauth2/* query param (§12.3 rule 4)
);
$configuration = $client->oidcDiscover();
$request = $client->oidcBegin($configuration, 'https://app.example/callback', scope: 'openid profile');
// Persist $request->state / $request->nonce / $request->codeVerifier YOURSELF — see below.
// ...redirect the browser to $request->url...
// On the callback, having checked the IdP's `state` matches:
try {
$tokens = $client->oidcExchange(
code: $callbackCode,
codeVerifier: $request->codeVerifier,
redirectUri: 'https://app.example/callback',
nonce: $request->nonce,
);
} catch (AuthError $e) {
// $e->getReason() is one of the §12.4 codes (invalid_alg, unknown_kid,
// invalid_signature, invalid_issuer, invalid_audience, token_expired,
// nonce_mismatch) when this was an ID-token validation failure, or an
// Axiam\Sdk\Core\OAuthProtocolError (an AuthError sub-type — existing
// catch(AuthError) blocks keep working) carrying ->error/->errorDescription.
}
echo $tokens->idClaims['sub']; // the validated ID-token subject
$tokens = $client->deviceLogin(
onUserCode: function (DeviceAuthorization $a): void {
// Called BEFORE the first poll. Display it however the device can — screen,
// QR code, e-ink panel. The SDK never prints it for you.
printf("visit %s and enter %s\n", $a->verificationUri, $a->userCode);
},
scope: 'openid profile',
);
// A PAT is a client-credentials token carrying `uma_protection` — never a user token,
// and never this client's own session (§20.2 rule 1).
$pat = $client->loginClientCredentials(scope: OidcClient::UMA_PROTECTION_SCOPE)->accessToken;
$resource = $client->umaRegisterResource($pat, 'invoice-7', 'document', ['view']);
// The returned id IS the AXIAM resource id — no translation step.
$ticket = $client->umaRequestTicket($pat, [
new RequestedPermission($resource->id, ['view']),
]);
header($client->umaChallengeHeader('invoices', $issuer, $ticket));
$challenger = new UmaChallenger('invoices', $client->oidcDiscover()->issuer, $pat, $client);
$enforcer = new AccessEnforcer($client, $logger, $challenger);
// A denied #[RequireAccess] now answers 403 with
// WWW-Authenticate: UMA realm="invoices", as_uri="…", ticket="…"
$url = $client->logoutUrl($storedIdToken);
// …and at your registered backchannel_logout_uri:
$verified = $client->verifyLogoutToken($logoutToken);
if ($verified->sid !== null) {
endSession($verified->sid); // that session ONLY
}
use Axiam\Sdk\Core\Sensitive;
use Axiam\Sdk\Webhook\AxiamWebhooks;
use Axiam\Sdk\Webhook\WebhookVerificationException;
// Read the RAW body BEFORE any framework parses it as JSON.
$rawBody = file_get_contents('php://input');
try {
$event = AxiamWebhooks::verify(
new Sensitive($webhookSecret),
$_SERVER['HTTP_X_AXIAM_SIGNATURE'] ?? '',
$rawBody,
);
} catch (WebhookVerificationException $e) {
http_response_code(400);
return;
}
// $event->eventType, $event->deliveryId, $event->timestamp, $event->body
use Axiam\Sdk\Core\Sensitive;
use Axiam\Sdk\Reactor\AmqpLibReactorTransport;
use Axiam\Sdk\Reactor\ReactorAnswer;
use Axiam\Sdk\Reactor\ReactorConfig;
use Axiam\Sdk\Reactor\ReactorEvent;
use Axiam\Sdk\Reactor\ReactorEvents;
use Axiam\Sdk\Reactor\ReactorServer;
$config = new ReactorConfig(
tenantId: $tenantId,
// §8.1 + §22.12: the tenant AMQP subkey from the management API, wrapped.
signingKey: new Sensitive($subkey),
// The queue name is derived from it — but the SERVER declared it.
reactorId: $reactorId,
);
$server = new ReactorServer(
config: $config,
// §8b: amqps:// only, optional CA bundle, no verification-skip switch anywhere.
transport: AmqpLibReactorTransport::connect('amqps://broker.example:5671', $caPath),
handler: function (ReactorEvent $event): ReactorAnswer {
switch ($event->event) {
case ReactorEvents::TOKEN_PRE_ISSUE:
// `ext.` is the COMPLETE allow-list for this event.
return ReactorAnswer::mutate(['ext.department' => 'eng']);
case ReactorEvents::LOGIN_POST_AUTH:
return fraudulent($event)
? ReactorAnswer::deny('embargoed region')
: ReactorAnswer::allow(); // or ReactorAnswer::allowWithStepUp()
}
return ReactorAnswer::allow();
},
);
$server->reactorServe(); // blocks; call $server->stop() from a signal handler
use Axiam\Sdk\Attributes\OnReactorEvent;
use Axiam\Sdk\Reactor\ReactorHandlers;
final class ClaimsReactor
{
#[OnReactorEvent(ReactorEvents::TOKEN_PRE_ISSUE)]
public function enrich(ReactorEvent $event): ReactorAnswer
{
return ReactorAnswer::mutate(['ext.department' => 'eng']);
}
#[OnReactorEvent(ReactorEvents::LOGIN_POST_AUTH)]
public function screen(ReactorEvent $event): ReactorAnswer
{
return fraudulent($event) ? ReactorAnswer::deny('embargoed region') : ReactorAnswer::allow();
}
}
$handlers = ReactorHandlers::of(new ClaimsReactor());
$server = new ReactorServer(config: $config, transport: $transport, handler: $handlers->handler());
$result = $client->login('[email protected]', $password);
if ($result->mfaSetupRequired) {
// The third outcome. The tenant ollment = $client->mfaSetupEnroll($result->setupToken);
renderQr($enrollment->totpUri->reveal());
$client->mfaSetupConfirm($result->setupToken, $code); // completes the LOGIN
}
$client->requestPasswordReset(new PasswordResetRequest('[email protected]'));
// returns void, whether or not that address has an account
$context = $client->passwordResetContext($token);
if ($context->opaque !== null) {
// This tenant runs §23. Build a registration record from these parameters;
// a plaintext password would be refused, and refused late (§25.4 rule 1).
}
$client->confirmPasswordReset(new PasswordResetConfirmation($token, $newPassword, $tenantId));
// No session — a sign-up screen. Returns whatever happened; that is the point.
$client->resendVerification('[email protected]', $tenantId);
// Signed in — a profile page. Says what happened, and names no address.
try {
$client->resendOwnVerification();
} catch (AuthzError) {
// 409: already verified, or an account state that must not be sent a live token.
} catch (NetworkError) {
// 429: the daily resend limit.
}
$config = $client->oidcDiscover();
if ($config->pushed_authorization_request_endpoint === null) {
// §26 is optional; fall back to the plain oidcBegin redirect.
}
$begun = $client->oidcBegin($config, $redirectUri, 'openid profile');
$pushed = $client->oidcPar($begun, $redirectUri, $config, 'openid profile');
header('Location: ' . $pushed->url); // exactly ?client_id=…&request_uri=…
// §27.2 — namespace handles, not 146 flat methods.
$page = $client->users()->listItems(new PageRequest(0, 50));
// Or reach the same handles behind one accessor.
$management = $client->management();
$same = $management->users()->listItems(new PageRequest(0, 50));
// §27.4 rule 4 — `total` is the SERVER's count, not count($page). Confusing the two is
// how a script silently processes the first fifty of four hundred users.
printf("%d of %d\n", count($page), $page->total);
// Every page. The walk stops on the first EMPTY page, never on a short one.
$users = $management->users();
foreach (ManagementTransport::walk(
static fn (PageRequest $p): Page => $users->listItems($p),
) as $user) { /* ... */ }
// §27.4 rule 5 — name what you mean to change. What you leave unset is OMITTED from the
// request, not sent as null; on a sparse update those say opposite things.
$management->users()->update($id, new Models\UpdateUserRequest(status: Models\UserStatus::Locked));
// §27.4 rule 3 — `{org_id}`/`{tenant_id}` come from the client; ->inOrg()/->forTenant()
// override them for one handle and return a COPY, leaving the original pointing where it did.
$management->caCertificates()->inOrg($otherOrgId)->listItems();
$page = $client->users()->listItems(new PageRequest(0, 50, 'ada'));
printf("%d of %d matches\n", count($page), $page->total); // total counts MATCHES, not rows
// And the whole filtered set: the term rides on the page request, so the walk carries it.
foreach (ManagementTransport::walk(
static fn (PageRequest $p): Page => $users->listItems($p),
new PageRequest(0, 50, 'ada'),
) as $match) { /* ... */ }
foreach ($client->tenants()->listItems() as $tenant) {
$label = match ($tenant->kind) {
Models\TenantKind::Organization => 'organization scope',
Models\TenantKind::Standard, null => 'tenant',
Models\TenantKind::Unknown => 'a kind this SDK predates — upgrade to name it',
};
}