klsoft / yii3-keycloak-authz
The package provides Keycloak authorization for the web service APIs of Yii 3.
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/klsoft/yii3-keycloak-authz
Requires
- php: >=8.0
- psr/container: ^1.0 || ^2.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0 || ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- yiisoft/http: ^1.2
- yiisoft/router: ^4
README
The package provides Keycloak authorization for the web service APIs of Yii 3.
See also:
- YII3-JWT-AUTH - The package provides a Yii 3 authentication method based on a JWT token
- PHP-KEYCLOAK-CLIENT - A PHP library that can be used to secure web applications with Keycloak
Requirement
- PHP 8.0 or higher.
Installation
composer require klsoft/yii3-keycloak-authz
How does it work
- A client requests a protected web service API method using an access token.
- The web service checks whether the access token contains the necessary permissions. If permissions exist, proceed to step 6.
- The web service obtains a permission ticket using the access token and the permissions of the API method. It then responds with the permission ticket:
HTTP/1.1 401 Unauthorized WWW-Authenticate: UMA realm="realm name", as_uri="realm URI", ticket="permission ticket" - The client obtains a Requesting Party Token (RPT) using the access token and the permission ticket.
- The client requests a protected web service API method with the RPT.
- The web service checks the RPT permissions.
How to use
1. Implement Klsoft\Yii3KeycloakAuthz\KeycloakRepositoryInterface
Example:
namespace MyNamespace; use Klsoft\Yii3KeycloakAuthz\KeycloakRepositoryInterface; use Klsoft\Yii3KeycloakAuthz\PermissionTicketResult; use Klsoft\Yii3KeycloakAuthz\PermissionTicketResponse; class KeycloakRepository implements KeycloakRepositoryInterface { public function __construct( private string $realm, private string $realmUri) { } function getPermissionTicket(string $accessToken, array $permissions): PermissionTicketResult { $url = "$this->realmUri/authz/protection/permission"; $options = [ 'http' => [ 'ignore_errors' => true, 'method' => 'POST', 'header' => [ 'Content-type: application/json', "Authorization: Bearer $accessToken"], 'content' => json_encode($permissions) ], ]; $responseData = file_get_contents($url, false, stream_context_create($options)); $responseStatusCode = $this->getHttpResponseStatusCode($http_response_header[0]); if (!empty($responseData)) { $responseArr = json_decode($responseData, true); if (isset($responseArr['ticket'])) { return new PermissionTicketResult(new PermissionTicketResponse( $this->realm, $this->realmUri, $responseArr['ticket'])); } return new PermissionTicketResult(null, $responseStatusCode, $responseArr); } return new PermissionTicketResult(null, $responseStatusCode); } private function getHttpResponseStatusCode(string $responseHeader): int { if (preg_match("/^HTTP\/[\d.]+\s+(\d{3})\s.*$/", $responseHeader, $matches)) { return intval($matches[1]); } return 0; } }
2. Add the realm and the realm URI to param.php
Example:
return [ 'realm' => 'myrealm', 'realmUri' => 'http://localhost:8080/realms/myrealm', ];
3. Register dependencies
Example:
use Klsoft\Yii3KeycloakAuthz\KeycloakRepositoryInterface; KeycloakRepositoryInterface::class => [ 'class' => KeycloakRepository::class, '__construct()' => [ 'realm' => $params['realm'], 'realmUri' => $params['realmUri'] ] ]
4. Apply permissions.
4.1. To an action.
First, add Authorization to the application middlewares:
use Yiisoft\Auth\Middleware\Authentication; use Klsoft\Yii3KeycloakAuthz\Middleware\Authorization; Application::class => [ '__construct()' => [ 'dispatcher' => DynamicReference::to([ 'class' => MiddlewareDispatcher::class, 'withMiddlewares()' => [ [ Authentication::class, Authorization::class, FormatDataResponseAsJson::class, static fn() => new ContentNegotiator([ 'application/xml' => new XmlDataResponseFormatter(), 'application/json' => new JsonDataResponseFormatter(), ]), ErrorCatcher::class, static fn(ExceptionResponderFactory $factory) => $factory->create(), RequestBodyParser::class, Router::class, NotFoundMiddleware::class, ], ], ]), ], ]
Then, apply permissions to an action:
use Klsoft\Yii3KeycloakAuthz\Permission; use Yiisoft\Http\Header; use Yiisoft\Http\Status; final class ProductController { public function __construct(private ProductPresenterInterface $productPresenter) { } #[Permission( 'product', ['create'] )] public function create(ServerRequestInterface $request): ResponseInterface { return $this->productPresenter->createProduct($request); } }
Example of a permission with claims:
#[Permission(
'product',
['create'],
['organization' => ['acme']]
)]
public function create(ServerRequestInterface $request): ResponseInterface
Example of a permission with an executing claim value:
#[Permission(
'product',
['create'],
['organization' => [
'__container_entry_identifier',
'organizationRepository',
'getCurrent',
['__request']]
]
)]
public function create(ServerRequestInterface $request): ResponseInterface
4.2. To a route.
First, define the set of permissions:
use Psr\Container\ContainerInterface; use Klsoft\Yii3KeycloakAuthz\Middleware\Authorization; use Klsoft\Yii3KeycloakAuthz\Permission; 'SomePermissions' => static function (ContainerInterface $container) { return $container ->get(Authorization::class) ->withPermissions([ new Permission('product', ['create']), new Permission('product', ['update']) ]); }
Then, you can apply this set to:
- A route:
Route::post('/product/create') ->middleware('SomePermissions') ->action([ProductController::class, 'create']) ->name('product/create')
- A group of routes:
Group::create() ->middleware('SomePermissions') ->routes( Route::post('/product/create') ->action([ProductController::class, 'create']) ->name('product/create'), Route::put('/product/update/{id}') ->action([ProductController::class, 'update']) ->name('product/update') )
- All routes in the application:
use Yiisoft\Auth\Middleware\Authentication; Application::class => [ '__construct()' => [ 'dispatcher' => DynamicReference::to([ 'class' => MiddlewareDispatcher::class, 'withMiddlewares()' => [ [ Authentication::class, 'SomePermissions', FormatDataResponseAsJson::class, static fn() => new ContentNegotiator([ 'application/xml' => new XmlDataResponseFormatter(), 'application/json' => new JsonDataResponseFormatter(), ]), ErrorCatcher::class, static fn(ExceptionResponderFactory $factory) => $factory->create(), RequestBodyParser::class, Router::class, NotFoundMiddleware::class, ], ], ]), ], ]