Skip to content

Commit

Permalink
feat: Keycloak authentication role mapping and add composite role sup…
Browse files Browse the repository at this point in the history
…port
  • Loading branch information
dogukanoksuz authored Jan 16, 2025
1 parent 0fb614f commit 7a6f775
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 9 deletions.
39 changes: 30 additions & 9 deletions app/Classes/Authentication/KeycloakAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Keycloak\KeycloakClient;
use Keycloak\User\UserApi;
use Stevenmaguire\OAuth2\Client\Provider\Keycloak as KeycloakProvider;

class KeycloakAuthenticator implements AuthenticatorInterface
Expand All @@ -18,8 +17,6 @@ class KeycloakAuthenticator implements AuthenticatorInterface

private $oauthProvider;

private $kcUserApi;

public function __construct()
{
$this->kcClient = new KeycloakClient(
Expand All @@ -31,8 +28,6 @@ public function __construct()
''
);

$this->kcUserApi = new UserApi($this->kcClient);

$this->oauthProvider = new KeycloakProvider([
'authServerUrl' => env('KEYCLOAK_BASE_URL'),
'realm' => env('KEYCLOAK_REALM'),
Expand All @@ -54,10 +49,7 @@ public function authenticate($credentials, $request): JsonResponse

$resourceOwner = $this->oauthProvider->getResourceOwner($accessTokenObject);

$roles = collect($this->kcUserApi->getRoles($resourceOwner->getId()))
->map(function ($role) {
return $role->name;
})->toArray();
$roles = $resourceOwner->toArray()['realm_access']['roles'];
} catch (\Exception $e) {
Log::error('Keycloak authentication failed. '.$e->getMessage());

Expand Down Expand Up @@ -95,9 +87,38 @@ public function authenticate($credentials, $request): JsonResponse
'permissions' => $roles,
]);

try {
$allRealmRoles = $this->getAllRealmRoles();

foreach ($allRealmRoles as $role) {
if (in_array($role->name, $roles)) {
// If user role is matched with realm role, check attributes and assign user to liman role
if (isset($role->attributes->liman_role) && count($role->attributes->liman_role) > 0) {
// Assign all items in liman_role attribute to user
foreach ($role->attributes->liman_role as $limanRole) {
$user->assignRole($limanRole);
}
}
}
}
} catch (\Throwable $e) {
Log::warning('Failed to fetch realm roles from Keycloak. '.$e->getMessage());
}

return Authenticator::createNewToken(
auth('api')->login($user),
$request
);
}

private function getAllRealmRoles()
{
$response = $this->kcClient->sendRequest('GET', "roles?briefRepresentation=false");

if ($response->getStatusCode() !== 200) {
throw new \Exception('Failed to fetch composite roles');
}

return json_decode($response->getBody()->getContents());
}
}
22 changes: 22 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Tymon\JWTAuth\Contracts\JWTSubject as JWTSubject;
use Illuminate\Support\Str;

/**
* App\Models\User
Expand Down Expand Up @@ -190,6 +191,27 @@ public function authLogs()
return $this->hasMany('\App\Models\AuthLog');
}

/**
* Assign role to user
*
* @param string $id
* @return void
*/
public function assignRole($id)
{
// Check if id is valid for a role
if (Role::find($id) == null) {
return;
}

// Check if role is already assigned
if ($this->roles()->where('role_id', $id)->exists()) {
return;
}

$this->roles()->attach($id, ['id' => Str::uuid()]);
}

/**
* Interact with the user's OTP secret.
*
Expand Down

0 comments on commit 7a6f775

Please sign in to comment.