|
| 1 | +# Basic Laravel MFA |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This is a bare-bones email-based 2FA package which can be configured to send out an email containing a signed link upon successful authentication. Any routes you place under the provided `mfa` middleware will be inaccessible until the link is clicked. |
| 6 | + |
| 7 | +## Installation |
| 8 | + |
| 9 | +```bash |
| 10 | +composer require theriftlab/laravel-mfa |
| 11 | +``` |
| 12 | + |
| 13 | +Optionally, publish the migration: |
| 14 | + |
| 15 | +```bash |
| 16 | +php artisan vendor:publish --tag=mfa-migrations |
| 17 | +``` |
| 18 | + |
| 19 | +Then: |
| 20 | + |
| 21 | +```bash |
| 22 | +php artisan migrate |
| 23 | +``` |
| 24 | + |
| 25 | +## Setup |
| 26 | + |
| 27 | +### Add to User Model |
| 28 | + |
| 29 | +First, you will need to mark your `User` model (or whatever model you are using for Auth) as ready for MFA: |
| 30 | + |
| 31 | +```diff |
| 32 | ++use Mfa\Contracts\MfaUser; |
| 33 | ++use Mfa\Concerns\Mfa; |
| 34 | +... |
| 35 | + |
| 36 | +-class User extends Authenticatable |
| 37 | ++class User extends Authenticatable implements MfaUser |
| 38 | +{ |
| 39 | + use HasApiTokens; |
| 40 | + use HasFactory; |
| 41 | ++ use Mfa; |
| 42 | + use Notifiable; |
| 43 | + ... |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +### Add to Auth Flow |
| 48 | + |
| 49 | +Due to the non-standard nature of Laravel's auth/login flow, it is up to you to decide where/when to trigger & end the MFA session using the `MfaAuth` facade, which expects an authenticated user to be present in order to work. |
| 50 | + |
| 51 | +For example, in a [Breeze](https://github.com/laravel/breeze) setup, you might add these lines into `app/Http/Controllers/Auth/AuthenticatedSessionController`: |
| 52 | + |
| 53 | +```diff |
| 54 | +use Mfa\Facades\MfaAuth; |
| 55 | + |
| 56 | +... |
| 57 | + |
| 58 | + public function store(LoginRequest $request) |
| 59 | + { |
| 60 | + $request->authenticate(); |
| 61 | + $request->session()->regenerate(); |
| 62 | + |
| 63 | ++ if (MfaAuth::isActive()) { |
| 64 | ++ MfaAuth::trigger(); |
| 65 | ++ return redirect()->route('mfa.sent'); |
| 66 | ++ } |
| 67 | + |
| 68 | + ... |
| 69 | + } |
| 70 | + |
| 71 | +... |
| 72 | + |
| 73 | + public function destroy(Request $request) |
| 74 | + { |
| 75 | ++ if (MfaAuth::isActive()) { |
| 76 | ++ MfaAuth::logout(); |
| 77 | ++ } |
| 78 | + |
| 79 | + Auth::guard('web')->logout(); |
| 80 | + ... |
| 81 | + } |
| 82 | +``` |
| 83 | + |
| 84 | +### Configure & Add Views |
| 85 | + |
| 86 | +The email containing the signed link is a very simple template, and can be published: |
| 87 | + |
| 88 | +```bash |
| 89 | +php artisan vendor:publish --tag=mfa-views |
| 90 | +``` |
| 91 | + |
| 92 | +There are also two view files which you will need to implement: `resources/views/auth/mfa-sent.blade.php` and `resources/views/auth/mfa-invalid.blade.php`. |
| 93 | + |
| 94 | +* `mfa-sent.blade.php` is shown when the user is first authorized by Laravel's default auth process and is waiting for the MFA signed link email. This template can optionally contain a link / button to POST to named route `mfa.resend`, which will resend the signed link email. The `$errors` session data will contain an error message if an invalid link is clicked, and `session('status')` will contain a message if the link email is resent. A logout link is also a good idea on this page to restart the whole process, in case the wrong account is logged in. |
| 95 | + |
| 96 | +* `mfa-invalid.blade.php` is shown when the user is *not* authorized and an invalid link is clicked, and therefore any resend / logout options are not available. |
| 97 | + |
| 98 | +**Note:** when the user is *not* authorized and a *valid* link is clicked from an email (eg. the initial default auth session might have timed out), the user will be automatically logged in. |
| 99 | + |
| 100 | +### Configuring Your Routes |
| 101 | + |
| 102 | +Finally, on whichever routes you wish to protect with MFA, you can add the `mfa` middleware after `auth` - for example: |
| 103 | + |
| 104 | +```php |
| 105 | +Route::get('/dashboard', function () { |
| 106 | + return view('dashboard'); |
| 107 | +})->middleware(['auth', 'mfa'])->name('dashboard'); |
| 108 | +``` |
| 109 | + |
| 110 | +This will redirect any `Auth`ed but un`MFA`ed user back to display your `auth.mfa-sent` view. |
| 111 | + |
| 112 | +## Configuration |
| 113 | + |
| 114 | +The default config is fairly self-explanatory and looks like this: |
| 115 | + |
| 116 | +```php |
| 117 | +// Whether MFA is active |
| 118 | +'active' => env('MFA_ACTIVE', true), |
| 119 | + |
| 120 | +// How many minutes the signed link lasts before timing out |
| 121 | +'link_timeout' => env('MFA_LINK_TIMEOUT', 60), |
| 122 | + |
| 123 | +// How many chars long the generated code should be |
| 124 | +'code_length' => env('MFA_CODE_LENGTH', 32), |
| 125 | + |
| 126 | +// URL to redirect to when link has been authorized |
| 127 | +'redirect_url' => env('MFA_REDIRECT_URL', '/'), |
| 128 | + |
| 129 | +// Which model will be adopting the MfaUser functionality |
| 130 | +'model' => env('MFA_MODEL', 'App\Models\User'), |
| 131 | +``` |
| 132 | + |
| 133 | +You may publish the config file if you wish to change the defaults: |
| 134 | + |
| 135 | +```bash |
| 136 | +php artisan vendor:publish --tag=mfa-config |
| 137 | +``` |
0 commit comments