diff --git a/README.md b/README.md index 7034012..0a8a66c 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,8 @@ This package _silently_ enables authentication using 6 digits codes, without Int + [Deactivation](#deactivation) * [Events](#events) * [Middleware](#middleware) +* [Validation](#validation) +* [Translations](#translations) * [Protecting the Login](#protecting-the-login) * [Configuration](#configuration) + [Listener](#listener) @@ -35,6 +37,7 @@ This package _silently_ enables authentication using 6 digits codes, without Int + [Cache Store](#cache-store) + [Recovery](#recovery) + [Safe devices](#safe-devices) + + [Confirmation Middleware](#confirmation-middleware) + [Secret length](#secret-length) + [TOTP configuration](#totp-configuration) + [QR Code Configuration](#qr-code-configuration) @@ -58,17 +61,15 @@ This package was made to be the less invasive possible, but you can go full manu ## Usage -First, publish the migration with: - - php artisan vendor:publish --provider="DarkGhostHunter\Laraguard\LaraguardServiceProvider" --tag="migrations" - -> The default migration assumes you are using integers for your user model IDs. If you are using UUIDs, or some other format, adjust the format of the morphs `authenticatable` fields in the published migration before continuing. - -After publishing the migration, you can create the `two_factor_authentications` table by running the migration: +First, create the `two_factor_authentications` table by running the migration: php artisan migrate -This will create a table to handle the Two Factor Authentication information for each model you set. +This will create a table to handle the Two Factor Authentication information for each model you want to attach to 2FA. + +> If you need to modify the migration from this package, you can publish it to override whatever you need. +> +> php artisan vendor:publish --provider="DarkGhostHunter\Laraguard\LaraguardServiceProvider" --tag="migrations" Add the `TwoFactorAuthenticatable` _contract_ and the `TwoFactorAuthentication` trait to the User model, or any other model you want to make Two Factor Authentication available. @@ -114,14 +115,16 @@ public function prepareTwoFactor(Request $request) > When you use `createTwoFactorAuth()` on someone with Two Factor Authentication already enabled, the previous data becomes permanently invalid. This ensures a User **never** has two Shared Secrets enabled at any given time. -Then, the User must confirm the Shared Secret with a Code generated by their Authenticator app. This `confirmTwoFactorAuth()` method will automatically enable it if the code is valid. +Then, the User must confirm the Shared Secret with a Code generated by their Authenticator app. The `confirmTwoFactorAuth()` method will automatically enable it if the code is valid. ```php public function confirmTwoFactor(Request $request) { - return $request->user()->confirmTwoFactorAuth( + $activated = $request->user()->confirmTwoFactorAuth( $request->input('2fa_code') ); + + // ... } ``` @@ -146,7 +149,7 @@ public function confirmTwoFactor(Request $request) You're free on how to show these codes to the User, but **ensure** you show them one time after a successfully enabling Two Factor Authentication, and ask him to print them somewhere. -> These Recovery Codes are handled automatically when the User issues a Code. If it's a recovery code, the package will use it and invalidate it. +> These Recovery Codes are handled automatically when the User validates one. If it's a recovery code, the package will use and mark it as invalid. The User can generate a fresh batch of codes using `generateRecoveryCodes()`, which automatically invalidates the previous batch. @@ -194,27 +197,93 @@ The following events are fired in addition to the default Authentication events. * `TwoFactorRecoveryCodesGenerated`: An User has generated a new set of Recovery Codes. * `TwoFactorDisabled`: An User has disabled Two Factor Authentication. -> You can use `TwoFactorRecoveryCodesDepleted` to notify the User to create more Recovery Codes, send him to his email a new batch of codes. +> You can use `TwoFactorRecoveryCodesDepleted` to tell the User to create more Recovery Codes. ## Middleware -If you need to ensure the User has Two Factor Authentication enabled before entering a given route, you can use the `2fa` middleware. +Laraguard comes with two middleware for your routes: `2fa.require` and `2fa.confirm`. + +> To avoid unexpected results, these middleware only act on your users models with `TwoFactorAuthenticatable`. If a user model doesn't implements it, the middleware bypass any 2FA logic. + +### Require 2FA + +If you need to ensure the User has Two Factor Authentication enabled before entering a given route, you can use the `2fa.require` middleware. ```php Route::get('system/settings') ->uses('SystemSettingsController@show') - ->middleware('2fa'); + ->middleware('2fa.require'); ``` This middleware works much like the `verified` middleware: if the User has not enabled Two Factor Authentication, it will be redirected to a route name containing the warning, which is `2fa.notice` by default. -You can implement this easily using this package: +You can implement the view easily with the one included in this package: + +```php +Route::view('2fa-required', 'laraguard::notice')->name('2fa.notice'); +``` + +Alternatively, you can use a custom controller action to also include a link to where he can enable Two Factor Authentication. + +```php +public function notice() +{ + return view('2fa.notice', [ + 'url' => url('account/settings') + ]); +} +``` + +### Confirm 2FA + +Much like the [`password.confirm` middleware](https://laravel.com/docs/authentication#password-confirmation), you can also ask the user to confirm an action using `2fa.confirm`. + +```php +Route::get('api/token') + ->uses('ApiTokenController@show') + ->middleware('2fa.confirm'); +``` + +Laraguard automatically uses the [`Confirm2FACodeController`](src/Http/Controllers/Confirm2FACodeController.php) to handle the form view and the code confirmation for you. + +Alternatively, [you can point your own controller actions](#confirmation-middleware) to handle the form view and confirmation. Better yet, you can start with the [`Confirms2FACode`](src/Http/Controllers/Confirms2FACode.php) trait to avoid reinventing the wheel. + +## Validation + +Sometimes you may want to manually trigger a TOTP validation in any part of your application for the authenticated user. You can validate a TOTP code for the authenticated user using the `totp_code` rule. + +```php +public function checkTotp(Request $request) +{ + $request->validate([ + 'code' => 'required|totp_code' + ]); + + // ... +} +``` + +This rule will succeed if the user is authenticated, is has Two Factor Authentication enabled, and the code is correct. + +## Translations + +Laraguard comes with translation files (only for english) that you can use immediately in your application. These are also used for the [validation rule](#validation). ```php -Route::view('2fa-required', 'laraguard::notice') - ->name('2fa.notice'); +public function disableTwoFactorAuth() +{ + // ... + + session()->flash('2fa_disabled', trans('laraguard::messages.disabled')); + + return back(); +} ``` +To add your own in your language, publish the translation files. These will be located in `resources/vendor/laraguard`: + + php artisan vendor:publish --provider="DarkGhostHunter\Laraguard\LaraguardServiceProvider" --tag="translations" + ## Protecting the Login Two Factor Authentication can be victim of brute-force attacks. The attacker will need between 16.000~34.000 requests each second to get the correct code, or less depending on the lifetime of the code. @@ -269,7 +338,12 @@ return [ 'enabled' => false, 'max_devices' => 3, 'expiration_days' => 14, - ], + ], + 'confirm' => [ + 'timeout' => 10800, + 'view' => 'DarkGhostHunter\Laraguard\Http\Controllers\Confirm2FACodeController@showConfirmForm', + 'action' => 'DarkGhostHunter\Laraguard\Http\Controllers\Confirm2FACodeController@confirm' + ], 'secret_length' => 20, 'issuer' => env('OTP_TOTP_ISSUER'), 'totp' => [ @@ -376,6 +450,24 @@ You can change the maximum number of devices saved and the amount of days of val > When re-enabling Two Factor Authentication, the list of devices is automatically invalidated. +### Confirmation Middleware + +```php +return [ + 'confirm' => [ + 'timeout' => 10800, // 3 hours + 'view' => 'DarkGhostHunter\Laraguard\Http\Controllers\Confirm2FACodeController@showConfirmForm', + 'action' => 'DarkGhostHunter\Laraguard\Http\Controllers\Confirm2FACodeController@confirm' + ], +]; +``` + +If the `view` or `action` are not `null`, the `2fa/notice` and `2fa/confirm` routes will be registered to handle 2FA code notice and confirmation for the [`2fa.confirm` middleware](#confirm-2fa). If you disable it, you will have to register the routes and controller actions yourself. + +This array also sets by how much to "remember" the 2FA Code confirmation, and the actions used to show the view to confirm the 2FA Code with also the action to handle the confirmation. + +You may want to change these, specially if you want your own view to show the confirmation form. + ### Secret length ```php @@ -416,7 +508,7 @@ This configuration values are always passed down to the authentication app as UR These values are printed to each 2FA data record inside the application. Changes will only take effect for new activations. -> It's not recommended to edit these parameters if you plan to use publicly available Authenticator apps, since some of them **may not support non-standard configuration**, like more digits, different period of seconds or other algorithms. +> Do not edit these parameters if you plan to use publicly available Authenticator apps, since some of them **may not support non-standard configuration**, like more digits, different period of seconds or other algorithms. ### QR Code Configuration @@ -438,9 +530,9 @@ This controls the size and margin used to create the QR Code, which are created You can override the view, which handles the Two Factor Code verification for the User. It receives this data: * `$action`: The full URL where the form should send the login credentials. -* `$credentials`: An `array|null` containing the User credentials used for the login. +* `$credentials`: An `array` containing the User credentials used for the login. * `$user`: The User instance trying to authenticate. -* `$error`: If the Two Factor Code is invalid. +* `$error`: If the Two Factor Code is invalid. * `$remember`: If the "remember" checkbox has been filled. The way it works is very simple: it will hold the User credentials in a hidden input while it asks for the Two Factor Code. The User will send everything again along with the Code, the application will ensure its correct, and complete the log in. diff --git a/config/laraguard.php b/config/laraguard.php index b3f4c6f..d855747 100644 --- a/config/laraguard.php +++ b/config/laraguard.php @@ -53,8 +53,8 @@ */ 'cache' => [ - 'store' => null, - 'prefix' => '2fa.code' + 'store' => null, + 'prefix' => '2fa.code', ], /* @@ -70,8 +70,8 @@ 'recovery' => [ 'enabled' => true, - 'codes' => 10, - 'length' => 8, + 'codes' => 10, + 'length' => 8, ], /* @@ -86,11 +86,28 @@ */ 'safe_devices' => [ - 'enabled' => false, - 'max_devices' => 3, + 'enabled' => false, + 'max_devices' => 3, 'expiration_days' => 14, ], + /* + |-------------------------------------------------------------------------- + | Require Two Factor Middleware + |-------------------------------------------------------------------------- + | + | When using the "2fa.confirm" middleware a view with a form will be used + | to ask the user for a TOTP code, an a controller action to receive it. + | You can change both actions and also when to forget the confirmation. + | + */ + + 'confirm' => [ + 'timeout' => 10800, // 3 hours + 'view' => 'DarkGhostHunter\Laraguard\Http\Controllers\Confirm2FACodeController@showConfirmForm', + 'action' => 'DarkGhostHunter\Laraguard\Http\Controllers\Confirm2FACodeController@confirm' + ], + /* |-------------------------------------------------------------------------- | Secret Length @@ -118,9 +135,9 @@ 'issuer' => env('OTP_TOTP_ISSUER'), 'totp' => [ - 'digits' => 6, - 'seconds' => 30, - 'window' => 1, + 'digits' => 6, + 'seconds' => 30, + 'window' => 1, 'algorithm' => 'sha1', ], @@ -136,7 +153,7 @@ */ 'qr_code' => [ - 'size' => 400, - 'margin' => 4 + 'size' => 400, + 'margin' => 4, ], ]; diff --git a/resources/lang/en/messages.php b/resources/lang/en/messages.php new file mode 100644 index 0000000..a6dce76 --- /dev/null +++ b/resources/lang/en/messages.php @@ -0,0 +1,22 @@ + 'Two Factor Authentication is required.', + 'continue' => 'To continue, open up your Authenticator app and issue your 2FA code.', + 'enable' => 'You need to enable Two Factor Authentication.', + + 'fail_confirm' => 'The code to activate Two Factor Authentication is invalid.', + 'enabled' => 'Two Factor Authentication has been enabled for your account.', + 'disabled' => 'Two Factor Authentication has been disabled for your account.', + + 'safe_device' => 'We won\'t ask you for Two Factor Authentication codes in this device for some time.', + + 'confirm' => 'Confirm code', + 'switch_on' => 'Go to enable Two Factor Authentication.', + + 'recovery_code' => [ + 'used' => 'You have used a Recovery Code. Remember to regenerate them if you have used almost all.', + 'depleted' => 'You have used all your Recovery Codes. Please use alternate authentication methods to continue.', + 'generated' => 'You have generated a new set of Recovery Codes. Any previous set of codes have been invalidated.', + ], +]; \ No newline at end of file diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php new file mode 100644 index 0000000..46e2815 --- /dev/null +++ b/resources/lang/en/validation.php @@ -0,0 +1,5 @@ + 'The Code is invalid or has expired.', +]; \ No newline at end of file diff --git a/resources/views/auth.blade.php b/resources/views/auth.blade.php index d7473f5..3b1c467 100644 --- a/resources/views/auth.blade.php +++ b/resources/views/auth.blade.php @@ -9,26 +9,26 @@ @if($remember) @endif -

- {{ __('To log in, open up your Authenticator app and issue the 6-digit code.') }} + {{ trans('laraguard::messages.continue') }}

- @if($error)
- {{ __('The Code is invalid or has expired.') }} + {{ trans('laraguard::validation.totp_code') }}
@endif
-
-
- +
+
+ +
@endsection diff --git a/resources/views/confirm.blade.php b/resources/views/confirm.blade.php new file mode 100644 index 0000000..2047024 --- /dev/null +++ b/resources/views/confirm.blade.php @@ -0,0 +1,32 @@ +@extends('laraguard::layout') + +@section('card-body') +
+ @csrf +

+ {{ trans('laraguard::messages.continue') }} +

+
+ @if($errors->hasAny()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+ +
+
+
+ +
+
+
+@endsection diff --git a/resources/views/layout.blade.php b/resources/views/layout.blade.php index 0c1c4bc..80469e3 100644 --- a/resources/views/layout.blade.php +++ b/resources/views/layout.blade.php @@ -5,8 +5,7 @@ - + Two Factor Authentication