From 13974545c0e8ca212cf0c3c1cdb2318c5b1ff231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robson=20Ten=C3=B3rio?= Date: Mon, 4 Feb 2019 17:32:11 -0200 Subject: [PATCH] Allows authentication without "users" table. --- .gitignore | 3 ++- README.md | 14 +++++++++--- config/keycloak.php | 2 ++ src/KeycloakGuard.php | 46 +++++++++++++++++++++----------------- tests/AuthenticateTest.php | 46 ++++++++++++++++++++++++++++---------- 5 files changed, 75 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 18d4f26..a2674ac 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ docs vendor .php_cs.cache coverage -.phpunit.result.cache \ No newline at end of file +.phpunit.result.cache +docker-compose.yml \ No newline at end of file diff --git a/README.md b/README.md index 4050d4f..e8a2dd3 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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'), @@ -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** diff --git a/config/keycloak.php b/config/keycloak.php index 638a7a8..fed0460 100644 --- a/config/keycloak.php +++ b/config/keycloak.php @@ -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'), diff --git a/src/KeycloakGuard.php b/src/KeycloakGuard.php index 60769a5..28fb8de 100644 --- a/src/KeycloakGuard.php +++ b/src/KeycloakGuard.php @@ -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); @@ -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; } } diff --git a/tests/AuthenticateTest.php b/tests/AuthenticateTest.php index bbbaaf5..068ca07 100644 --- a/tests/AuthenticateTest.php +++ b/tests/AuthenticateTest.php @@ -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' => [ @@ -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' => [ @@ -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' => [