diff --git a/docs/en/authenticators.md b/docs/en/authenticators.md index 26c66792..0f2e9beb 100644 --- a/docs/en/authenticators.md +++ b/docs/en/authenticators.md @@ -182,7 +182,7 @@ public function getAuthenticationService(ServerRequestInterface $request): Authe // ... $service->loadAuthenticator('Authentication.Jwt', [ 'identifier' => 'Authentication.JwtSubject', - 'secretKey' => file_get_contents(CONFIG . '/jwt.key'), + 'secretKey' => file_get_contents(CONFIG . 'jwt.key'), 'algorithm' => 'RS256', 'returnPayload' => false ]); @@ -196,11 +196,11 @@ In your `UsersController`: ``` php use Firebase\JWT\JWT; -public function login() +public function login(): void { $result = $this->Authentication->getResult(); if ($result->isValid()) { - $privateKey = file_get_contents(CONFIG . '/jwt.key'); + $privateKey = file_get_contents(CONFIG . 'jwt.key'); $user = $result->getData(); $payload = [ 'iss' => 'myapp', @@ -260,16 +260,19 @@ distribute it via a JWKS endpoint by configuring your app as follows: ``` php // config/routes.php $builder->setExtensions('json'); -$builder->connect('/.well-known/:controller/*', [ +$builder->connect('/.well-known/{controller}', [ 'action' => 'index', ], [ - 'controller' => '(jwks)', + 'controller' => 'jwks', + 'pass' => [], ]); // connect /.well-known/jwks.json to JwksController // controller/JwksController.php +use Firebase\JWT\JWT; + public function index() { - $pubKey = file_get_contents(CONFIG . './jwt.pem'); + $pubKey = file_get_contents(CONFIG . 'jwt.pem'); $res = openssl_pkey_get_public($pubKey); $detail = openssl_pkey_get_details($res); $key = [ @@ -313,7 +316,7 @@ Configuration options: - **realm**: Default is `null` - **qop**: Default is `auth` -- **nonce**: Default is `uniqid(''),` +- **nonce**: Default is `uniqid('')` - **opaque**: Default is `null` ## Cookie Authenticator aka "Remember Me" @@ -344,7 +347,7 @@ Configuration options: - **samesite**: String/null The value for the same site attribute. The defaults for the various options besides `cookie.name` will be those - set for the `Cake\Http\Cookie\Cookie` class. See [Cookie::setDefaults()](https://api.cakephp.org/4.0/class-Cake.Http.Cookie.Cookie.html#setDefaults) + set for the `Cake\Http\Cookie\Cookie` class. See [Cookie::setDefaults()](https://api.cakephp.org/5/class-Cake.Http.Cookie.Cookie.html#setDefaults) for the default values. - **fields**: Array that maps `username` and `password` to the @@ -369,7 +372,7 @@ Configuration options: The cookie authenticator can be added to a Form & Session based authentication system. Cookie authentication will automatically re-login users after their session expires for as long as the cookie is valid. If a user is -explicity logged out via `AuthenticationComponent::logout()` the +explicitly logged out via `AuthenticationComponent::logout()` the authentication cookie is **also destroyed**. An example configuration would be: ``` php @@ -377,8 +380,8 @@ authentication cookie is **also destroyed**. An example configuration would be: // Reuse fields in multiple authenticators. $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + PasswordIdentifier::CREDENTIAL_USERNAME => 'email', + PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', ]; // Put form authentication first so that users can re-login via @@ -389,9 +392,7 @@ $service->loadAuthenticator('Authentication.Form', [ 'loginUrl' => '/users/login', ]); // Then use sessions if they are active. -$service->loadAuthenticator('Authentication.Session', [ - 'identifier' => 'Authentication.Password', -]); +$service->loadAuthenticator('Authentication.Session'); // If the user is on the login page, check for a cookie as well. $service->loadAuthenticator('Authentication.Cookie', [ @@ -441,9 +442,6 @@ $service->loadAuthenticator('Authentication.Environment', [ ]); ``` -::: info Added in version 2.10.0 -`EnvironmentAuthenticator` was added. -::: ## Events @@ -549,9 +547,7 @@ $passwordIdentifier = [ ]; // Load the authenticators leaving Basic as the last one. -$service->loadAuthenticator('Authentication.Session', [ - 'identifier' => $passwordIdentifier, -]); +$service->loadAuthenticator('Authentication.Session'); $service->loadAuthenticator('Authentication.Form', [ 'identifier' => $passwordIdentifier, ]); @@ -589,7 +585,7 @@ Then in your controller's login method you can use `getLoginRedirect()` to get the redirect target safely from the query string parameter: ``` php -public function login() +public function login(): ?\Cake\Http\Response { $result = $this->Authentication->getResult(); @@ -600,8 +596,11 @@ public function login() if (!$target) { $target = ['controller' => 'Pages', 'action' => 'display', 'home']; } + return $this->redirect($target); } + + return null; } ``` @@ -622,7 +621,7 @@ public function getAuthenticationService( // Configuration common to both the API and web goes here. - if ($request->getParam('prefix') == 'Api') { + if ($request->getParam('prefix') === 'Api') { // Include API specific authenticators } else { // Web UI specific authenticators. diff --git a/docs/en/contents.md b/docs/en/contents.md index 80b75fcf..1f699378 100644 --- a/docs/en/contents.md +++ b/docs/en/contents.md @@ -12,6 +12,8 @@ - [Testing with Authentication](testing) - [User Impersonation](impersonation) - [URL Checkers](url-checkers) +- [Redirect Validation](redirect-validation) - [View Helper](view-helper) - [Migration from the AuthComponent](migration-from-the-authcomponent) - [Upgrading from 2.x to 3.x](upgrade-2-to-3) +- [Upgrading from 3.x to 4.x](upgrade-3-to-4) diff --git a/docs/en/identifiers.md b/docs/en/identifiers.md index 826a5371..1f83caad 100644 --- a/docs/en/identifiers.md +++ b/docs/en/identifiers.md @@ -1,8 +1,7 @@ # Identifiers -Identifiers will identify an user or service based on the information -that was extracted from the request by the authenticators. Identifiers -can take options in the `loadIdentifier` method. A holistic example of +Identifiers will identify a user or service based on the information +that was extracted from the request by the authenticators. A holistic example of using the Password Identifier looks like: ``` php @@ -199,15 +198,7 @@ $identifier = [ ]; ``` -Or injected using a setter: - -``` php -$resolver = new \App\Identifier\Resolver\CustomResolver(); -$identifier = $service->loadIdentifier('Authentication.Password'); -$identifier->setResolver($resolver); -``` - -As of 3.3.0, you should pass the constructed resolver into the identifier: +Or pass the constructed resolver directly into the identifier configuration: ``` php $resolver = new \App\Identifier\Resolver\CustomResolver(); diff --git a/docs/en/identity-object.md b/docs/en/identity-object.md index 88b89439..e6e5096c 100644 --- a/docs/en/identity-object.md +++ b/docs/en/identity-object.md @@ -80,7 +80,7 @@ class User extends Entity implements IdentityInterface /** * Authentication\IdentityInterface method */ - public function getIdentifier() + public function getIdentifier(): array|string|int|null { return $this->id; } @@ -88,7 +88,7 @@ class User extends Entity implements IdentityInterface /** * Authentication\IdentityInterface method */ - public function getOriginalData() + public function getOriginalData(): \ArrayAccess|array { return $this; } diff --git a/docs/en/impersonation.md b/docs/en/impersonation.md index 1e59dace..9a2247eb 100644 --- a/docs/en/impersonation.md +++ b/docs/en/impersonation.md @@ -1,9 +1,5 @@ # User Impersonation -::: info Added in version 2.10.0 -User impersonation was added. -::: - After deploying your application, you may occasionally need to 'impersonate' another user in order to debug problems that your customers report or to see the application in the state that your customers are seeing it. @@ -16,7 +12,7 @@ user from your application's database: ``` php // In a controller -public function impersonate() +public function impersonate(): \Cake\Http\Response { $this->request->allowMethod(['POST']); @@ -29,9 +25,9 @@ public function impersonate() } // Fetch the user we want to impersonate. - $targetUser = $this->Users->findById( - $this->request->getData('user_id') - )->firstOrFail(); + $targetUser = $this->fetchTable('Users') + ->findById($this->request->getData('user_id')) + ->firstOrFail(); // Enable impersonation. $this->Authentication->impersonate($targetUser); @@ -50,7 +46,7 @@ back to your previous identity using `AuthenticationComponent`: ``` php // In a controller -public function revertIdentity() +public function revertIdentity(): \Cake\Http\Response { $this->request->allowMethod(['POST']); @@ -59,6 +55,8 @@ public function revertIdentity() throw new NotFoundException(); } $this->Authentication->stopImpersonating(); + + return $this->redirect($this->referer()); } ``` @@ -68,4 +66,4 @@ There are a few limitations to impersonation. 1. Your application must be using the `Session` authenticator. 2. You cannot impersonate another user while impersonation is active. Instead - you must `stopImpersonation()` and then start it again. + you must `stopImpersonating()` and then start it again. diff --git a/docs/en/index.md b/docs/en/index.md index 72228cbd..2956e3ee 100644 --- a/docs/en/index.md +++ b/docs/en/index.md @@ -26,7 +26,7 @@ imports: use Authentication\AuthenticationService; use Authentication\AuthenticationServiceInterface; use Authentication\AuthenticationServiceProviderInterface; -use Authentication\Identifier\AbstractIdentifier; +use Authentication\Identifier\PasswordIdentifier; use Authentication\Middleware\AuthenticationMiddleware; use Cake\Http\MiddlewareQueue; use Cake\Routing\Router; @@ -93,8 +93,8 @@ public function getAuthenticationService(ServerRequestInterface $request): Authe ]); $fields = [ - AbstractIdentifier::CREDENTIAL_USERNAME => 'email', - AbstractIdentifier::CREDENTIAL_PASSWORD => 'password', + PasswordIdentifier::CREDENTIAL_USERNAME => 'email', + PasswordIdentifier::CREDENTIAL_PASSWORD => 'password', ]; // Load the authenticators. Session should be first. @@ -109,7 +109,7 @@ public function getAuthenticationService(ServerRequestInterface $request): Authe 'fields' => $fields, 'loginUrl' => Router::url([ 'prefix' => false, - 'plugin' => null, + 'plugin' => false, 'controller' => 'Users', 'action' => 'login', ]), @@ -135,7 +135,7 @@ Next, in your `AppController` load the [Authentication Component](authentication ``` php // in src/Controller/AppController.php -public function initialize() +public function initialize(): void { parent::initialize(); @@ -156,7 +156,7 @@ $this->Authentication->allowUnauthenticated(['view', 'index']); ## Building a Login Action Once you have the middleware applied to your application you'll need a way for -users to login. Please ensure your database has been created with the Users table structure used in [tutorial](tutorials-and-examples/cms/database). First generate a Users model and controller with bake: +users to login. Please ensure your database has been created with the Users table structure used in the [CMS tutorial](https://book.cakephp.org/5/en/tutorials-and-examples/cms/database.html). First generate a Users model and controller with bake: ``` bash bin/cake bake model Users @@ -168,17 +168,20 @@ like: ``` php // in src/Controller/UsersController.php -public function login() +public function login(): ?\Cake\Http\Response { $result = $this->Authentication->getResult(); // If the user is logged in send them away. if ($result && $result->isValid()) { $target = $this->Authentication->getLoginRedirect() ?? '/home'; + return $this->redirect($target); } if ($this->request->is('post')) { $this->Flash->error('Invalid username or password'); } + + return null; } ``` @@ -188,7 +191,7 @@ unauthenticated users are able to access it: ``` php // in src/Controller/UsersController.php -public function beforeFilter(\Cake\Event\EventInterface $event) +public function beforeFilter(\Cake\Event\EventInterface $event): void { parent::beforeFilter($event); @@ -216,9 +219,10 @@ Then add a simple logout action: ``` php // in src/Controller/UsersController.php -public function logout() +public function logout(): \Cake\Http\Response { $this->Authentication->logout(); + return $this->redirect(['controller' => 'Users', 'action' => 'login']); } ``` @@ -240,9 +244,10 @@ class User extends Entity // ... other methods // Automatically hash passwords when they are changed. - protected function _setPassword(string $password) + protected function _setPassword(string $password): string { $hasher = new DefaultPasswordHasher(); + return $hasher->hash($password); } } diff --git a/docs/en/middleware.md b/docs/en/middleware.md index 80769dde..4feb64c8 100644 --- a/docs/en/middleware.md +++ b/docs/en/middleware.md @@ -37,7 +37,7 @@ public function getAuthenticationService(ServerRequestInterface $request): Authe $path = $request->getPath(); $service = new AuthenticationService(); - if (strpos($path, '/api') === 0) { + if (str_starts_with($path, '/api')) { // Accept API tokens only $service->loadAuthenticator('Authentication.Token', [ 'identifier' => 'Authentication.Token', @@ -48,9 +48,7 @@ public function getAuthenticationService(ServerRequestInterface $request): Authe // Web authentication // Support sessions and form login. - $service->loadAuthenticator('Authentication.Session', [ - 'identifier' => 'Authentication.Password', - ]); + $service->loadAuthenticator('Authentication.Session'); $service->loadAuthenticator('Authentication.Form', [ 'identifier' => 'Authentication.Password', ]); diff --git a/docs/en/migration-from-the-authcomponent.md b/docs/en/migration-from-the-authcomponent.md index 06592823..fcc63090 100644 --- a/docs/en/migration-from-the-authcomponent.md +++ b/docs/en/migration-from-the-authcomponent.md @@ -38,9 +38,7 @@ authenticators. - **Authenticators** take the incoming request and try to extract identification credentials from it. If credentials are found, they - are passed to a collection of identifiers where the user is located. - For that reason authenticators take an IdentifierCollection as first - constructor argument. + are passed to an identifier where the user is located. - **Identifiers** verify identification credentials against a storage system. eg. (ORM tables, LDAP etc) and return identified user data. @@ -75,7 +73,6 @@ use Authentication\AuthenticationService; use Authentication\AuthenticationServiceInterface; use Authentication\AuthenticationServiceProviderInterface; use Authentication\Middleware\AuthenticationMiddleware; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; // Add the authentication interface. @@ -85,10 +82,9 @@ class Application extends BaseApplication implements AuthenticationServiceProvid * Returns a service provider instance. * * @param \Psr\Http\Message\ServerRequestInterface $request Request - * @param \Psr\Http\Message\ResponseInterface $response Response * @return \Authentication\AuthenticationServiceInterface */ - public function getAuthenticationService(ServerRequestInterface $request) : AuthenticationServiceInterface + public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface { $service = new AuthenticationService(); // Configure the service. (see below for more details) @@ -101,7 +97,7 @@ Next add the `AuthenticationMiddleware` to your application: ``` php // in src/Application.php -public function middleware($middlewareQueue) +public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue { // Various other middlewares for error handling, routing etc. added here. @@ -147,13 +143,11 @@ $service = new AuthenticationService(); ], ]; - // Load the authenticators - $service->loadAuthenticator('Authentication.Session', [ - 'identifier' => $passwordIdentifier, - ]); - $service->loadAuthenticator('Authentication.Form', [ - 'identifier' => $passwordIdentifier, - ]); +// Load the authenticators. Session should be first. +$service->loadAuthenticator('Authentication.Session'); +$service->loadAuthenticator('Authentication.Form', [ + 'identifier' => $passwordIdentifier, +]); ``` If you have customized the `userModel` you can use the following @@ -190,20 +184,23 @@ upon a successful login, change your login action to check the new identity results: ``` php -public function login() +public function login(): ?\Cake\Http\Response { $result = $this->Authentication->getResult(); - // regardless of POST or GET, redirect if user is logged in + // Regardless of POST or GET, redirect if user is logged in if ($result->isValid()) { $target = $this->Authentication->getLoginRedirect(); + return $this->redirect($target); } - // display error if user submitted and authentication failed - if ($this->request->is(['post'])) { + // Display error if user submitted and authentication failed + if ($this->request->is('post')) { $this->Flash->error('Invalid username or password'); } + + return null; } ``` @@ -238,7 +235,7 @@ use `setIdentity()`: ``` php // Assume you need to read a user by access token -$user = $this->Users->find('byToken', ['token' => $token])->first(); +$user = $this->Users->find('byToken', token: $token)->first(); // Persist the user into configured authenticators. $this->Authentication->setIdentity($user); @@ -295,7 +292,7 @@ Then in your controller's login method you can use `getLoginRedirect()` to get the redirect target safely from the query string parameter: ``` php -public function login() +public function login(): ?\Cake\Http\Response { $result = $this->Authentication->getResult(); @@ -306,8 +303,11 @@ public function login() if (!$target) { $target = ['controller' => 'Pages', 'action' => 'display', 'home']; } + return $this->redirect($target); } + + return null; } ``` @@ -318,11 +318,11 @@ functionality. You can replicate that logic with this plugin by leveraging the `AuthenticationService`: ``` php -public function login() +public function login(): ?\Cake\Http\Response { $result = $this->Authentication->getResult(); - // regardless of POST or GET, redirect if user is logged in + // Regardless of POST or GET, redirect if user is logged in if ($result->isValid()) { $authService = $this->Authentication->getAuthenticationService(); @@ -330,17 +330,21 @@ public function login() $identifier = $authService->getIdentificationProvider(); if ($identifier !== null && $identifier->needsPasswordRehash()) { // Rehash happens on save. - $user = $this->Users->get($this->Authentication->getIdentityData('id')); + $user = $this->fetchTable('Users')->get( + $this->Authentication->getIdentityData('id') + ); $user->password = $this->request->getData('password'); - $this->Users->save($user); + $this->fetchTable('Users')->saveOrFail($user); } // Redirect to a logged in page return $this->redirect([ 'controller' => 'Pages', 'action' => 'display', - 'home' + 'home', ]); } + + return null; } ``` diff --git a/docs/en/password-hashers.md b/docs/en/password-hashers.md index bac0a15c..eb69cc37 100644 --- a/docs/en/password-hashers.md +++ b/docs/en/password-hashers.md @@ -60,21 +60,26 @@ access the `Password` identifier and check if the current user’s password needs to be upgraded: ``` php -public function login() +public function login(): ?\Cake\Http\Response { $authentication = $this->request->getAttribute('authentication'); $result = $authentication->getResult(); - // regardless of POST or GET, redirect if user is logged in + // Regardless of POST or GET, redirect if user is logged in if ($result->isValid()) { if ($authentication->getIdentificationProvider()->needsPasswordRehash()) { // Rehash happens on save. - $user = $this->Users->get($authentication->getIdentity()->getIdentifier()); + $user = $this->fetchTable('Users')->get( + $authentication->getIdentity()->getIdentifier() + ); $user->password = $this->request->getData('password'); - $this->Users->saveOrFail($user); + $this->fetchTable('Users')->saveOrFail($user); } // Redirect or display a template. + return $this->redirect('/'); } + + return null; } ``` diff --git a/docs/en/testing.md b/docs/en/testing.md index f9e626fb..da1104ae 100644 --- a/docs/en/testing.md +++ b/docs/en/testing.md @@ -19,7 +19,7 @@ class ArticlesControllerTest extends TestCase ``` Based on the type of authentication you're using you will need to -simulate credentials differently. Lets review a few more common types of +simulate credentials differently. Let's review a few more common types of authentication. ## Session based authentication @@ -29,10 +29,9 @@ normally would be found in the session. In your test cases you can define a helper method that lets you 'login': ``` php -protected function login($userId = 1) +protected function login(int $userId = 1): void { - $users = TableRegistry::getTableLocator()->get('Users'); - $user = $users->get($userId); + $user = $this->fetchTable('Users')->get($userId); $this->session(['Auth' => $user]); } ``` @@ -81,14 +80,14 @@ variables that [PHP creates](https://php.net/manual/en/features.http-auth.php) automatically: ``` php -public function testGet() +public function testGet(): void { - $this->configRequest([ - 'environment' => [ - 'PHP_AUTH_USER' => 'username', - 'PHP_AUTH_PW' => 'password', - ] - ]); + $this->configRequest([ + 'environment' => [ + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'password', + ], + ]); $this->get('/api/bookmarks'); $this->assertResponseOk(); }