diff --git a/docs/0 - install.md b/docs/0 - install.md index 3a65dd318..4bcd16815 100644 --- a/docs/0 - install.md +++ b/docs/0 - install.md @@ -1,5 +1,7 @@ # Installation +These instructions assume that you have already [installed the CodeIgniter 4 app starter](https://codeigniter.com/user_guide/installation/installing_composer.html) as the basis for your new project, set up your `.env` file, and created a database that you can access via the Spark CLI script. + Installation is done through [Composer](https://getcomposer.org). The example assumes you have it installed globally. If you have it installed as a phar, or othewise you will need to adjust the way you call composer itself. @@ -75,11 +77,12 @@ public $ruleSets = [ ## Controller Filters -Shield provides 3 [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can +Shield provides 4 [Controller Filters](https://codeigniter.com/user_guide/incoming/filters.html) you can use to protect your routes, `session`, `tokens`, and `chained`. The first two cover the `Session` and `AccessTokens` authenticators, respectively. The `chained` filter will check both authenticators in sequence to see if the user is logged in through either of authenticators, allowing a single API endpoint to -work for both an SPA using session auth, and a mobile app using access tokens. +work for both an SPA using session auth, and a mobile app using access tokens. The fourth, `auth-rates`, +provides a good basis for rate limiting of auth-related routes. These filters are already loaded for you by the registrar class located at `src/Config/Registrar.php`. @@ -110,54 +113,3 @@ public $filters = [ ] ]; ``` - -## Further Customization - -### Route Configuration - -If you need to customize how any of the auth features are handled, you will likely need to update the routes to point to the correct controllers. You can still use the `service('auth')->routes()` helper, but you will need to pass the `except` option with a list of routes to customize: - -```php -service('auth')->routes($routes, ['except' => ['login', 'register']]); -``` - -Then add the routes to your customized controllers: - -```php -$routes->get('login', '\App\Controllers\Auth\LoginController::loginView'); -$routes->get('register', '\App\Controllers\Auth\RegisterController::registerView'); -``` - -### Extending the Controllers - -Shield has the following controllers that can be extended to handle -various parts of the authentication process: - -- **ActionController** handles the after login and after-registration actions that can be ran, like Two Factor Authentication and Email Verification. - -- **LoginController** handles the login process. - -- **RegisterController** handles the registration process. Overriding this class allows you to customize the User Provider, the User Entity, and the validation rules. - -- **MagicLinkController** handles the "lost password" process that allows a user to login with a link sent to their email. Allows you to -override the message that is displayed to a user to describe what is happening, if you'd like to provide more information than simply swapping out the view used. - -It is not recommended to copy the entire controller into app and change it's namespace. Instead, you should create a new controller that extends -the existing controller and then only override the methods needed. This allows the other methods to always stay up to date with any security -updates that might happen in the controllers. - -```php -userProvider`. +You can use your own models to handle user persistence. Shield calls this the "User Provider" class. A default model +is provided for you at `CodeIgniter\Shield\Models\UserModel`. You can change this in the `Config\Auth->userProvider` setting. +The only requirement is that your new class MUST extend the provided `UserModel`. ```php public $userProvider = 'CodeIgniter\Shield\Models\UserModel'; @@ -23,19 +27,24 @@ public $userProvider = 'CodeIgniter\Shield\Models\UserModel'; ## User Identities -User accounts are stored separately from the information needed to identify that user. These identifying pieces of data we call User Identities. By default, the library has two types of identities: one for standard email/password information, and one for access tokens. +User accounts are stored separately from the information needed to identify that user. These identifying pieces of data are +called User Identities. By default, the library has two types of identities: one for standard email/password information, +and one for access tokens. -By keeping the identity information loosely coupled from the user account itself, it frees the system up to more easily integrate third-party sign-in systems, JWT systems, and more, all on a single user. With small overrides you could even allow a single user to have multiple email/password combinations if your needs demands the functionality. +Keeping these identities loosely coupled from the user account itself facilitates integrations with third-party sign-in systems, JWT systems, and more - all on a single user. -While this has the potential to make the system more complex, the `email` and `password` fields are automatically looked up for you when attempting to access from the User entity. Caution should be used to craft queries that will pull in the `email` field when you need to display it to the user, as you could easily run into some n+1 slow queries otherwise. +While this has the potential to make the system more complex, the `email` and `password` fields are automatically +looked up for you when attempting to access them from the User entity. Caution should be used to craft queries that will pull +in the `email` field when you need to display it to the user, as you could easily run into some n+1 slow queries otherwise. -When you `save($user)` a `User` instance in the `UserModel`, the email/password identity will automatically be updated. If no email/password identity exists, you must -pass both the email and the password to the User instance prior to calling `save()`. +When you `save($user)` a `User` instance in the `UserModel`, the email/password identity will automatically be updated. +If no email/password identity exists, you must pass both the email and the password to the User instance prior to calling `save()`. ## Password Validators -When registering a user account, the user's password must be validated to ensure it matches the security requirements of your application. To make the system as flexible as possible Shield uses a pipeline of -Validators to handle the validation. This allows you turn on or off any validation systems that are appropriate for your application. The following Validators are available: +When registering a user account, the user's password must be validated to ensure it matches the security requirements of +your application. Shield uses a pipeline of Validators to handle the validation. This allows you turn on or off any validation +systems that are appropriate for your application. The following Validators are available: - **CompositionValidator** validates the makeup of the password itself. This used to include things like ensuring it contained a symbol, a number, etc. According to the current @@ -50,11 +59,11 @@ Validators to handle the validation. This allows you turn on or off any validati `Config\Auth->maxSimilarity`. The default value is 50, but see the docblock in the config file for more details. This is enabled by default. - **DictionaryValidator** will compare the password against a provided file with about 600,000 - frequently used passwords as has been seen in various data dumps over the years. If the + frequently used passwords that have been seen in various data dumps over the years. If the chosen password matches any found in the file, it will be rejected. This is enabled by default. - **PwnedValidator** is like the `DictionaryValidator`. Instead of comparing to a local file, it uses a third-party site, [Have I Been Pwned](https://haveibeenpwned.com/Passwords) to check - against a list of over 500 million leaked passwords from many data dumps across the web. + against a list of over 630 million leaked passwords from many data dumps across the web. The search is done securely, and provides more information than the simple dictionary version. However, this does require an API call to a third-party which not every application will find acceptable. You should use either this validator or the `DictionaryValidator`, not both. diff --git a/docs/2 - authentication.md b/docs/2 - authentication.md index d7bb369fd..eb09e22eb 100644 --- a/docs/2 - authentication.md +++ b/docs/2 - authentication.md @@ -1,12 +1,14 @@ # Authentication -Shield provides a flexible, secure, authentication system for your web apps and API's. +Authentication is the process of determining that a visitor actually belongs to your website, +and identifying them. Shield provides a flexible and secure authentication system for your +web apps and APIs. ## Available Authenticators Shield ships with 2 authenticators that will serve several typical situations within web app development: the Session authenticator, which uses username/email/password to authenticate against and stores it in the session, -and Access Tokens authenticator which uses private access tokens passed in the headers. +and the Access Tokens authenticator which uses private access tokens passed in the headers. The available authenticators are defined in `Config\Auth`: @@ -37,9 +39,9 @@ helper('auth'); auth()->user(); // get the current user's id -user_id() -// or auth()->id() +// or +user_id() ``` ## Authenticator Responses @@ -152,6 +154,8 @@ auth()->logout(); The `forget` method will purge all remember-me tokens for the current user, making it so they will not be remembered on the next visit to the site. + + ## Access Token Authenticator The Access Token authenticator supports the use of revoke-able API tokens without using OAuth. These are commonly @@ -185,6 +189,7 @@ This creates the token using a cryptographically secure random string. The token is hashed (sha256) before saving it to the database. The method returns an instance of `CodeIgniters\Shield\Authentication\Entities\AccessToken`. The only time a plain text version of the token is available is in the `AccessToken` returned immediately after creation. + **The plain text version should be displayed to the user immediately so they can copy it for their use.** If a user loses it, they cannot see the raw version anymore, but they can generate a new token to use. @@ -236,7 +241,7 @@ Tokens will expire after a specified amount of time has passed since they have b By default, this is set to 1 year. You can change this value by setting the `accessTokenLifetime` value in the `Auth` config file. This is in seconds so that you can use the [time constants](https://codeigniter.com/user_guide/general/common_functions.html#time-constants) -CodeIgniter provides. +that CodeIgniter provides. ```php public $unusedTokenLifetime = YEAR; diff --git a/docs/3 - auth_actions.md b/docs/3 - auth_actions.md index a1605dddd..3da7d7c36 100644 --- a/docs/3 - auth_actions.md +++ b/docs/3 - auth_actions.md @@ -51,9 +51,6 @@ Views for all of these pages are defined in the `Auth` config file, with the `$v ]; ``` -NOTE: a session flag is set with the current action step and the user cannot continue until that -flag has been cleared. - ## Defining New Actions While the provided email-based activation and 2FA will work for many sites, others will have different @@ -72,9 +69,8 @@ told the user would be happening. For example, in the `Email2FA` class, this met sends the email to the user, and then displays the form the user should enter the 6 digit code into. **verify()** is the final step in the action's journey. It verifies the information the user provided -and provides feedback. One important task is to remove the `auth_action` field from the session so -that a user can proceed through the site like normal. In the `Email2FA` class, it verifies the code -against what is saved in the database and either sends them back to the previous form to try again -or redirects the user to the page that a `login` task would have redirected them to anyway. +and provides feedback. In the `Email2FA` class, it verifies the code against what is saved in the +database and either sends them back to the previous form to try again or redirects the user to the +page that a `login` task would have redirected them to anyway. -All methods should return either a RedirectResponse or string of a view, like through the `view()` method. +All methods should return either a `RedirectResponse` or a view string (e.g. using the `view()` function). diff --git a/docs/4 - authorization.md b/docs/4 - authorization.md index 923e163fd..a839ddc68 100644 --- a/docs/4 - authorization.md +++ b/docs/4 - authorization.md @@ -1,8 +1,11 @@ # Authorization -Shield provides a flexible role-based access control that allows users to belong to multiple groups at once. +Authorization happens once a user has been identified through authentication. It is the process of +determining what actions a user is allowed to do within your site. + +Shield provides a flexible role-based access control (RBAC) that allows users to belong to multiple groups at once. Groups can be thought of as traditional roles (admin, moderator, user, etc), but can also group people together -around features, like Beta feature access, or used to provide discrete groups of users within a forum, etc. +around features, like Beta feature access, or used to provide discrete groups of users within a forum, etc. ## Defining Available Groups @@ -19,7 +22,7 @@ public $groups = [ ``` The key of the `$groups` array is the common term of the group. This is what you would call when referencing the -group elsewhere, like checking if `$user->inGroup('superadmin')`. By default, the following groups are available: +group elsewhere, like checking if `$user->inGroup('superadmin')`. By default, the following groups are available: `superadmin`, `admin`, `developer`, `user`, and `beta`. ### Default User Group @@ -35,7 +38,7 @@ public $defaultGroup = 'users'; All permissions must be added to the `AuthGroups` config file, also. A permission is simply a string consisting of a scope and action, like `users.create`. The scope would be `users` and the action would be `create`. Each permission -can have a description for display within UIs if needed. +can have a description for display within UIs if needed. ```php public $permissions = [ @@ -53,7 +56,7 @@ public $permissions = [ In order to grant any permissions to a group, they must have the permission assigned to the group, within the `AuthGroups` config file, under the `$matrix` property. The matrix is an associative array with the group name as the key, -and array of permissions that should be applied to that group. +and an array of permissions that should be applied to that group. ```php public $matrix = [ @@ -61,7 +64,7 @@ public $matrix = [ ]; ``` -You can use a wildcard within a scope to allow all actions within that scope, by using a '*' in place of the action. +You can use a wildcard within a scope to allow all actions within that scope, by using a `*` in place of the action. ```php public $matrix = [ @@ -71,17 +74,17 @@ public $matrix = [ ## Authorizing Users -When the `Authorization` trait is applied to the user model, it provides the following methods to authorize your users. +The `Authorizable` trait on the `User` entity provides the following methods to authorize your users. #### can() -Allows you to check if a user is permitted to do a specific action. The only argument is the permission string. Returns +Allows you to check if a user is permitted to do a specific action. The only argument is the permission string. Returns boolean `true`/`false`. Will check the user's direct permissions first, and then check against all of the user's groups permissions to determine if they are allowed. ```php if ($user->can('users.create')) { - // + // } ``` @@ -97,15 +100,15 @@ if (! $user->inGroup('superadmin', 'admin')) { ## Managing User Permissions -Permissions can be granted on a user level as well as on a group level. Any user-level permissions granted will +Permissions can be granted on a user level as well as on a group level. Any user-level permissions granted will override the group, so it's possible that a user can perform an action that their groups cannot. None of the changes are saved on the User entity until you `save()` with the `UserModel`. #### addPermission() -Adds one or more permissions to the user. If a permission doesn't exist, a `CodeIgniter\Shield\Authorization\AuthorizationException` -is thrown. +Adds one or more permissions to the user. If a permission doesn't exist, a `CodeIgniter\Shield\Authorization\AuthorizationException` +is thrown. ```php $user->addPermission('users.create', 'users.edit'); @@ -114,7 +117,7 @@ $user->addPermission('users.create', 'users.edit'); #### removePermission() Removes one or more permissions from a user. If a permission doesn't exist, a `CodeIgniter\Shield\Authorization\AuthorizationException` -is thrown. +is thrown. ```php $user->removePermission('users.delete'); @@ -134,7 +137,7 @@ $user->syncPermissions(['admin.access', 'beta.access']); #### addGroup() Adds one or more groups to a user. If a group doesn't exist, a `CodeIgniter\Shield\Authorization\AuthorizationException` -is thrown. +is thrown. ```php $user->addGroup('admin', 'beta'); diff --git a/docs/5 - events.md b/docs/5 - events.md index 2ddd72992..cb4fbde38 100644 --- a/docs/5 - events.md +++ b/docs/5 - events.md @@ -1,12 +1,15 @@ # Events -Shield fires off several events during the lifecycle of the application that your code can tap into. +Shield fires off several events during the lifecycle of the application that your code can tap into. ## Responding to Events -When you want to respond to an event that Shield publishes, you will need to add it to your `app/Config/Events.php` file. Each of the following events provides a sample for responding that uses a class and method name. Other methods are available. See the [CodeIgniter 4 User Guide](https://codeigniter.com/user_guide/extending/events.html) for more information. +When you want to respond to an event that Shield publishes, you will need to add it to your `app/Config/Events.php` +file. Each of the following events provides a sample for responding that uses a class and method name. +Other methods are available. See the [CodeIgniter 4 User Guide](https://codeigniter.com/user_guide/extending/events.html) +for more information. -## Event List +### Event List #### register @@ -30,7 +33,7 @@ Events::on('login', 'SomeLibrary::handleLogin'); #### failedLogin -Triggered when a login attempt fails. It provides an array containing the credentials the user attempted to +Triggered when a login attempt fails. It provides an array containing the credentials the user attempted to sign in with, with the password removed from the array. ```php diff --git a/docs/7 - customization.md b/docs/7 - customization.md new file mode 100644 index 000000000..6d3603f63 --- /dev/null +++ b/docs/7 - customization.md @@ -0,0 +1,83 @@ +# Customizing Shield + +## Route Configuration + +If you need to customize how any of the auth features are handled, you will likely need to update the routes to point to the correct controllers. You can still use the `service('auth')->routes()` helper, but you will need to pass the `except` option with a list of routes to customize: + +```php +service('auth')->routes($routes, ['except' => ['login', 'register']]); +``` + +Then add the routes to your customized controllers: + +```php +$routes->get('login', '\App\Controllers\Auth\LoginController::loginView'); +$routes->get('register', '\App\Controllers\Auth\RegisterController::registerView'); +``` + + + +## Custom Redirect URLs + +By default, a successful login or register attempt will all redirect to `/`, while a logout action +will redirect to `/login`. You can change the default URLs used within the `Auth` config file: + +```php +public array $redirects = [ + 'register' => '/', + 'login' => '/', + 'logout' => 'login', +]; +``` + +Oftentimes, you will want to have different redirects for different user groups. A simple example +might be that you want admins redirected to `/admin` while all other groups redirect to `/`. +The `Auth` config file also includes methods that you can add additional logic to in order to +achieve this: + +```php +public function loginRedirect(): string +{ + if (auth()->user()->can('admin.access')) { + return '/admin'; + } + + $url = setting('Auth.redirects')['login']; + + return $this->getUrl($url); +} +``` + +## Extending the Controllers + +Shield has the following controllers that can be extended to handle +various parts of the authentication process: + +- **ActionController** handles the after-login and after-registration actions, like Two Factor Authentication and Email Verification. + +- **LoginController** handles the login process. + +- **RegisterController** handles the registration process. Overriding this class allows you to customize the User Provider, the User Entity, and the validation rules. + +- **MagicLinkController** handles the "lost password" process that allows a user to login with a link sent to their email. This allows you to +override the message that is displayed to a user to describe what is happening, if you'd like to provide more information than simply swapping out the view used. + +It is not recommended to copy the entire controller into **app/Controllers** and change its namespace. Instead, you should create a new controller that extends +the existing controller and then only override the methods needed. This allows the other methods to stay up to date with any security +updates that might happen in the controllers. + +```php +