Skip to content

Commit

Permalink
Merge pull request #6 from robsontenorio/optional-user-fetch
Browse files Browse the repository at this point in the history
Allows authentication without "users" table.
  • Loading branch information
robsontenorio authored Feb 4, 2019
2 parents e8f4675 + 1397454 commit 513c29c
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 36 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ docs
vendor
.php_cs.cache
coverage
.phpunit.result.cache
.phpunit.result.cache
docker-compose.yml
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ This package helps you authenticate users on a Laravel API based on JWT tokens g

✔️ I will not use Laravel Passport for authentication, because Keycloak Server will do the job.

✔️ I already have an "users table", with unique identifiers, on my database.

✔️ The frontend is a separated project.

✔️ The frontend users authenticate **directly on Keycloak Server** to obtain a JWT token. This process have nothing to do with the Laravel API.
Expand Down Expand Up @@ -81,6 +79,8 @@ The Keycloak Guard configuration can be handled from Laravel `.env` file. ⚠️
return [
'realm_public_key' => env('KEYCLOAK_REALM_PUBLIC_KEY', null),

'load_user_from_database' => env('KEYCLOAK_LOAD_USER_FROM_DATABASE', true),

'user_provider_credential' => env('KEYCLOAK_USER_PROVIDER_CREDENTIAL', 'username'),

'token_principal_attribute' => env('KEYCLOAK_TOKEN_PRINCIPAL_ATTRIBUTE', 'preferred_username'),
Expand All @@ -98,12 +98,20 @@ return [

The Keycloak Server realm public key (string).

✔️ **load_user_from_database**

*Required. Default is `true`.*

If you do not have an `users` table you must disable this.

It fetchs user from database and fill values into authenticated user object. If enabled, it will work together with `user_provider_credential` and `user_provider_credential`.

✔️ **user_provider_credential**

*Required. Default is `username`.*


Any field from "users" table that contains the user unique identifier (eg. username, email, nickname). This will be confronted against `token_principal_attribute` attribute, while authenticating.
The field from "users" table that contains the user unique identifier (eg. username, email, nickname). This will be confronted against `token_principal_attribute` attribute, while authenticating.

✔️ **token_principal_attribute**

Expand Down
2 changes: 2 additions & 0 deletions config/keycloak.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
return [
'realm_public_key' => env('KEYCLOAK_REALM_PUBLIC_KEY', null),

'load_user_from_database' => env('KEYCLOAK_LOAD_USER_FROM_DATABASE', true),

'user_provider_credential' => env('KEYCLOAK_USER_PROVIDER_CREDENTIAL', 'username'),

'token_principal_attribute' => env('KEYCLOAK_TOKEN_PRINCIPAL_ATTRIBUTE', 'preferred_username'),
Expand Down
46 changes: 26 additions & 20 deletions src/KeycloakGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,15 @@ public function validate(array $credentials = [])

$this->validateResources();

$user = $this->provider->retrieveByCredentials($credentials);

if (!$user) {
throw new UserNotFoundException("User not found. Credentials: " . json_encode($credentials));
if ($this->config['load_user_from_database']) {
$user = $this->provider->retrieveByCredentials($credentials);

if (!$user) {
throw new UserNotFoundException("User not found. Credentials: " . json_encode($credentials));
}
} else {
$class = $this->provider->getModel();
$user = new $class();
}

$this->setUser($user);
Expand Down Expand Up @@ -173,21 +178,22 @@ public function token()
}

/**
* Check if authenticated user has a especific role into resource
* @param string $resource
* @param string $role
* @return bool
*/
public function hasRole($resource, $role) {
$token_resource_access = (array) $this->decodedToken->resource_access;
if(array_key_exists($resource, $token_resource_access)) {
$token_resource_values = (array)$token_resource_access[$resource];

if(array_key_exists('roles', $token_resource_values) &&
in_array($role, $token_resource_values['roles'])) {
return true;
}
}
return false;
* Check if authenticated user has a especific role into resource
* @param string $resource
* @param string $role
* @return bool
*/
public function hasRole($resource, $role)
{
$token_resource_access = (array)$this->decodedToken->resource_access;
if (array_key_exists($resource, $token_resource_access)) {
$token_resource_values = (array)$token_resource_access[$resource];

if (array_key_exists('roles', $token_resource_values) &&
in_array($role, $token_resource_values['roles'])) {
return true;
}
}
return false;
}
}
46 changes: 34 additions & 12 deletions tests/AuthenticateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,38 @@ public function it_does_not_appends_token_to_the_user()
$this->assertNull(Auth::user()->token);
}

/** @test */
public function it_does_not_load_user_from_database()
{
config(['keycloak.load_user_from_database' => false]);

$response = $this->withToken()->json('GET', '/foo/secret');

$this->assertEquals(count(Auth::user()->getAttributes()), 0);
}

/** @test */
public function it_does_not_load_user_from_database_but_appends_decoded_token()
{
config(['keycloak.load_user_from_database' => false]);
config(['keycloak.append_decoded_token' => true]);

$response = $this->withToken()->json('GET', '/foo/secret');

$this->assertArrayHasKey('token', Auth::user()->toArray());
}


/** @test */
public function it_check_user_has_role_in_resource()
{
$this->buildCustomToken([
'resource_access' => [
'myapp-backend' => [
'roles' => [
'myapp-backend-role1',
'myapp-backend-role2'
]
'roles' => [
'myapp-backend-role1',
'myapp-backend-role2'
]
],
'myapp-frontend' => [
'roles' => [
Expand All @@ -116,10 +138,10 @@ public function it_check_user_no_has_role_in_resource()
$this->buildCustomToken([
'resource_access' => [
'myapp-backend' => [
'roles' => [
'myapp-backend-role1',
'myapp-backend-role2'
]
'roles' => [
'myapp-backend-role1',
'myapp-backend-role2'
]
],
'myapp-frontend' => [
'roles' => [
Expand All @@ -140,10 +162,10 @@ public function it_prevent_cross_roles_resources()
$this->buildCustomToken([
'resource_access' => [
'myapp-backend' => [
'roles' => [
'myapp-backend-role1',
'myapp-backend-role2'
]
'roles' => [
'myapp-backend-role1',
'myapp-backend-role2'
]
],
'myapp-frontend' => [
'roles' => [
Expand Down

0 comments on commit 513c29c

Please sign in to comment.