overtrue / keycloak-rest-api-client-php
PHP client to interact with Keycloak's Admin REST API.
Installs: 3 587
Dependents: 1
Suggesters: 0
Security: 0
Stars: 2
Watchers: 0
Forks: 33
pkg:composer/overtrue/keycloak-rest-api-client-php
Requires
- php: ^8.4
- ext-json: *
- guzzlehttp/guzzle: ^7.9
- lcobucci/jwt: ^5.5
- psr/simple-cache: ^3.0
- symfony/cache: ^7.2
- symfony/property-access: ^7.2
- symfony/serializer: ^7.2
Requires (Dev)
- laravel/pint: ^1.21
- phpmetrics/phpmetrics: ^v3.0.0rc8
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpunit/phpunit: ^10
- ramsey/uuid: ^4.7
- symfony/var-dumper: ^7.2
- vimeo/psalm: ^6.8.8
README
Keycloak Admin REST API Client
PHP client to interact with Keycloak's Admin REST API.
Inspired by keycloak/keycloak-nodejs-admin-client.
This is a fork of fschmtt/keycloak-rest-api-client-php
Installation
Install via Composer:
composer require overtrue/keycloak-rest-api-client-php
Usage
Example:
$keycloak = new \Overtrue\Keycloak\Keycloak( baseUrl: 'http://keycloak:8080', username: 'admin', password: 'admin' ); $serverInfo = $keycloak->serverInfo()->get(); echo sprintf( 'Keycloak %s is running on %s/%s (%s) with %s/%s since %s and is currently using %s of %s (%s %%) memory.', $serverInfo->getSystemInfo()->getVersion(), $serverInfo->getSystemInfo()->getOsName(), $serverInfo->getSystemInfo()->getOsVersion(), $serverInfo->getSystemInfo()->getOsArchitecture(), $serverInfo->getSystemInfo()->getJavaVm(), $serverInfo->getSystemInfo()->getJavaVersion(), $serverInfo->getSystemInfo()->getUptime(), $serverInfo->getMemoryInfo()->getUsedFormated(), $serverInfo->getMemoryInfo()->getTotalFormated(), 100 - $serverInfo->getMemoryInfo()->getFreePercentage(), );
will print e.g.
Keycloak 26.0.0 is running on Linux/5.10.25-linuxkit (amd64) with OpenJDK 64-Bit Server VM/11.0.11 since 0 days, 2 hours, 37 minutes, 7 seconds and is currently using 139 MB of 512 MB (28 %) memory.
More examples can be found in the examples directory.
Caching
The Keycloak client supports powerful caching based on PSR-16 (Simple Cache) standard:
use DateInterval; use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\Cache\Psr16Cache; $keycloak = new \Overtrue\Keycloak\Keycloak( baseUrl: 'http://keycloak:8080', username: 'admin', password: 'admin', cache: new Psr16Cache(new RedisAdapter($redis)), cacheConfig: [ 'prefix' => 'myapp_', 'ttl' => [ 'version' => new DateInterval('PT6H'), 'server_info' => new DateInterval('PT1H'), 'access_token' => new DateInterval('PT1H'), 'refresh_token' => new DateInterval('P1D'), ] ] );
For detailed caching configuration and usage, see CACHE.md.
Customization
Custom representations & resources
You can register and use custom resources by providing your own representations and resources, e.g.:
class MyCustomRepresentation extends \Overtrue\Keycloak\Representation\Representation { public function __construct( protected ?string $id = null, protected ?string $name = null, ) { } } class MyCustomResource extends \Overtrue\Keycloak\Resource\Resource { public function myCustomEndpoint(): MyCustomRepresentation { return $this->queryExecutor->executeQuery( new \Overtrue\Keycloak\Http\Query( '/my-custom-endpoint', MyCustomRepresentation::class, ) ); } }
By extending the Resource class, you have access to both the QueryExecutor and CommandExecutor.
The CommandExecutor is designed to run state-changing commands against the server (without returning a response);
the QueryExecutor allows fetching resources and representations from the server.
To use your custom resource, pass the fully-qualified class name (FQCN) to the Keycloak::resource() method.
It provides you with an instance of your resource you can then work with:
$keycloak = new Keycloak( $_SERVER['KEYCLOAK_BASE_URL'] ?? 'http://keycloak:8080', 'admin', 'admin', ); $myCustomResource = $keycloak->resource(MyCustomResource::class); $myCustomRepresentation = $myCustomResource->myCustomEndpoint();
Available Resources
Attack Detection
| Endpoint | Response | API | 
|---|---|---|
| DELETE /admin/realms/{realm}/attack-detection/brute-force/users | ResponseInterface | AttackDetection::clear() | 
| GET /admin/realms/{realm}/attack-detection/brute-force/users/{userId} | StringMap | AttackDetection::userStatus() | 
| DELETE /admin/realms/{realm}/attack-detection/brute-force/users/{userId} | ResponseInterface | AttackDetection::clearUser() | 
Clients
| Endpoint | Response | API | 
|---|---|---|
| GET /admin/realms/{realm}/clients | ClientCollection | Clients::all() | 
| GET /admin/realms/{realm}/clients/{clientUuid} | ClientRepresentation | Clients::get() | 
| POST /admin/realms/{realm}/clients | ClientRepresentation | Clients::import() | 
| PUT /admin/realms/{realm}/clients/{clientUuid} | ClientRepresentation | Clients::update() | 
| DELETE /admin/realms/{realm}/clients/{clientUuid} | ResponseInterface | Clients::delete() | 
| GET /admin/realms/{realm}/clients/{clientUuid}/user-sessions | array | Clients::getUserSessions() | 
| GET /admin/realms/{realm}/clients/{clientUuid}/client-secret | Credential | Clients::getClientSecret() | 
Groups
| Endpoint | Response | API | 
|---|---|---|
| GET /admin/realms/{realm}/groups | GroupCollection | Groups::all() | 
| GET /admin/realms/{realm}/group-by-path/{path} | Group | Groups::byPath() | 
| GET /admin/realms/{realm}/groups/{groupId}/children | GroupCollection | Groups::children() | 
| GET /admin/realms/{realm}/groups/{groupId}/members | UserCollection | Groups::members() | 
| GET /admin/realms/{realm}/groups/{groupId} | Group | Groups::get() | 
| POST /admin/realms/{realm}/groups | Group | Groups::create() | 
| POST /admin/realms/{realm}/groups/{groupId}/children | Group | Groups::createChild() | 
| PUT /admin/realms/{realm}/groups/{groupId} | ResponseInterface | Groups::update() | 
| DELETE /admin/realms/{realm}/groups/{groupId} | ResponseInterface | Groups::delete() | 
Organizations
| Endpoint | Response | API | 
|---|---|---|
| GET /admin/realms/{realm}/organizations | OrganizationCollection | Organizations::all() | 
| GET /admin/realms/{realm}/organizations/{id} | Organization | Organizations::get() | 
| POST /admin/realms/{realm}/organizations | Organization | Organizations::create() | 
| PUT /admin/realms/{realm}/organizations/{id} | Organization | Organizations::update() | 
| DELETE /admin/realms/{realm}/organizations/{id} | ResponseInterface | Organizations::delete() | 
| GET /admin/realms/{realm}/organizations/{orgId}/members | MemberCollection | Organizations::members() | 
| GET /admin/realms/{realm}/organizations/{orgId}/members/count | int | Organizations::membersCount() | 
| GET /admin/realms/{realm}/organizations/{orgId}/members/{memberId} | Member | Organizations::member() | 
| POST /admin/realms/{realm}/organizations/{orgId}/members | ResponseInterface | Organizations::addMember() | 
| DELETE /admin/realms/{realm}/organizations/{orgId}/members/{memberId} | ResponseInterface | Organizations::deleteMember() | 
| GET /admin/realms/{realm}/organizations/members/{memberId}/organizations | OrganizationCollection | Organizations::memberOrganizations() | 
| GET /admin/realms/{realm}/organizations/{orgId}/members/{memberId}/organizations | OrganizationCollection | Organizations::orgMemberOrganizations() | 
| POST /admin/realms/{realm}/organizations/{id}/members/invite-user | ResponseInterface | Organizations::inviteUser() | 
| POST /admin/realms/{realm}/organizations/{id}/members/invite-existing-user | ResponseInterface | Organizations::inviteExistingUser() | 
| POST /admin/realms/{realm}/organizations/{id}/identity-providers | ResponseInterface | Organizations::linkIdp() | 
| DELETE /admin/realms/{realm}/organizations/{id}/identity-providers/{alias} | ResponseInterface | Organizations::unlinkIdp() | 
Realms Admin
| Endpoint | Response | API | 
|---|---|---|
| GET /admin/realms | RealmCollection | Realms::all() | 
| GET /admin/realms/{realm} | Realm | Realms::get() | 
| POST /admin/realms | Realm | Realms::import() | 
| PUT /admin/realms/{realm} | Realm | Realms::update() | 
| DELETE /admin/realms/{realm} | ResponseInterface | Realms::delete() | 
| GET /admin/realms/{realm}/admin-events | array | Realms::adminEvents() | 
| GET /admin/realms/{realm}/keys | KeysMetadata | Realms::keys() | 
| DELETE /admin/realms/{realm}/admin-events | ResponseInterface | Realms::deleteAdminEvents() | 
| POST /admin/realms/{realm}/clear-keys-cache | ResponseInterface | Realms::clearKeysCache() | 
| POST /admin/realms/{realm}/clear-realm-cache | ResponseInterface | Realms::clearRealmCache() | 
| POST /admin/realms/{realm}/clear-user-cache | ResponseInterface | Realms::clearUserCache() | 
Users
| Endpoint | Response | API | 
|---|---|---|
| GET /admin/realms/{realm}/users | UserCollection | Users::all() | 
| GET /admin/realms/{realm}/users/{userId} | UserRepresentation | Users::get() | 
| POST /admin/realms/{realm}/users | UserRepresentation | Users::create() | 
| PUT /admin/realms/{realm}/users/{userId} | UserRepresentation | Users::update() | 
| DELETE /admin/realms/{realm}/users/{userId} | ResponseInterface | Users::delete() | 
| GET /admin/realms/{realm}/users | UserCollection | Users::search() | 
| PUT /admin/realms/{realm}/users/{userId}/groups/{groupId} | ResponseInterface | Users::joinGroup() | 
| DELETE /admin/realms/{realm}/users/{userId}/groups/{groupId} | ResponseInterface | Users::leaveGroup() | 
| GET /admin/realms/{realm}/users/{userId}/groups | GroupCollection | Users::retrieveGroups() | 
| GET /admin/realms/{realm}/users/{userId}/role-mappings/realm | RoleCollection | Users::retrieveRealmRoles() | 
| GET /admin/realms/{realm}/users/{userId}/role-mappings/realm/available | RoleCollection | Users::retrieveAvailableRealmRoles() | 
| POST /admin/realms/{realm}/users/{userId}/role-mappings/realm | ResponseInterface | Users::addRealmRoles() | 
| DELETE /admin/realms/{realm}/users/{userId}/role-mappings/realm | ResponseInterface | Users::removeRealmRoles() | 
| PUT /admin/realms/{realm}/users/{userId}/execute-actions-email | ResponseInterface | Users::executeActionsEmail() | 
| POST /admin/realms/{realm}/users/{userId}/federated-identity/{provider} | ResponseInterface | Users::addFederatedIdentity() | 
| DELETE /admin/realms/{realm}/users/{userId}/federated-identity/{provider} | ResponseInterface | Users::removeFederatedIdentity() | 
| GET /admin/realms/{realm}/users/{userId}/credentials | CredentialCollection | Users::credentials() | 
Roles
| Endpoint | Response | API | 
|---|---|---|
| GET /admin/realms/{realm}/roles | RoleCollection | Roles::all() | 
| GET /admin/realms/{realm}/roles/{roleName} | Role | Roles::get() | 
| POST /admin/realms/{realm}/roles | Role | Roles::create() | 
| DELETE /admin/realms/{realm}/roles/{roleName} | ResponseInterface | Roles::delete() | 
| PUT /admin/realms/{realm}/roles/{roleName} | Role | Roles::update() | 
Root
| Endpoint | Response | API | 
|---|---|---|
| GET /admin/serverinfo | ServerInfoRepresentation | ServerInfo::get() | 
Local development and testing
Run docker compose up -d keycloak to start a local Keycloak instance listening on http://localhost:8080.
Run your script (e.g. examples/serverinfo.php) from within the php container:
docker compose run --rm php php examples/serverinfo.php
Composer scripts
- analyze: Run phpstan analysis
- fix: Fix coding style issues (Laravel pint)
- test: Run unit and integration tests
- test:unit: Run unit tests
- test:integration: Run integration tests (requires a fresh and running Keycloak instance)