{"id":16202135,"url":"https://github.com/michaeldzjap/laravel-two-factor-authentication","last_synced_at":"2025-04-05T08:07:41.171Z","repository":{"id":62528016,"uuid":"92531374","full_name":"michaeldzjap/laravel-two-factor-authentication","owner":"michaeldzjap","description":"A two-factor authentication package for Laravel \u003e= 8","archived":false,"fork":false,"pushed_at":"2025-02-21T10:34:49.000Z","size":135,"stargazers_count":43,"open_issues_count":0,"forks_count":7,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-29T07:08:00.676Z","etag":null,"topics":["laravel","laravel-5-package","laravel-6-package","laravel-7-package","laravel-8-package","laravel-package","messagebird","php","two-factor-authentication"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michaeldzjap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-05-26T16:58:50.000Z","updated_at":"2025-02-21T10:32:08.000Z","dependencies_parsed_at":"2024-11-09T18:01:30.083Z","dependency_job_id":"3d2c9e5a-bd6a-4346-a5e0-ba0d83fd82a7","html_url":"https://github.com/michaeldzjap/laravel-two-factor-authentication","commit_stats":{"total_commits":116,"total_committers":3,"mean_commits":"38.666666666666664","dds":"0.15517241379310343","last_synced_commit":"d1678de73b89254dd9512964bb73c6b03667c176"},"previous_names":[],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldzjap%2Flaravel-two-factor-authentication","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldzjap%2Flaravel-two-factor-authentication/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldzjap%2Flaravel-two-factor-authentication/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeldzjap%2Flaravel-two-factor-authentication/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michaeldzjap","download_url":"https://codeload.github.com/michaeldzjap/laravel-two-factor-authentication/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247305934,"owners_count":20917208,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["laravel","laravel-5-package","laravel-6-package","laravel-7-package","laravel-8-package","laravel-package","messagebird","php","two-factor-authentication"],"created_at":"2024-10-10T09:45:38.660Z","updated_at":"2025-04-05T08:07:41.149Z","avatar_url":"https://github.com/michaeldzjap.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Latest Stable Version](https://poser.pugx.org/michaeldzjap/twofactor-auth/version)](https://packagist.org/packages/michaeldzjap/twofactor-auth)\n[![Total Downloads](https://poser.pugx.org/michaeldzjap/twofactor-auth/downloads)](https://packagist.org/packages/michaeldzjap/twofactor-auth)\n[![Latest Unstable Version](https://poser.pugx.org/michaeldzjap/twofactor-auth/v/unstable)](//packagist.org/packages/michaeldzjap/twofactor-auth)\n[![StyleCI](https://github.styleci.io/repos/92531374/shield?branch=develop)](https://github.styleci.io/repos/92531374?branch=master)\n[![License](https://poser.pugx.org/michaeldzjap/twofactor-auth/license)](https://packagist.org/packages/michaeldzjap/twofactor-auth)\n\n# laravel-two-factor-authentication\nA two-factor authentication package for _Laravel_ \u003e= 8 (for Laravel 5 to 7 you will need version 1 or 2 of this package)\n\n## Table of Contents\n\n- [Description](#description)\n- [Important](#important)\n  - [Optional Correction](#optional-correction)\n- [Installation](#installation)\n- [Changes to the Login Process](#changes-to-the-login-process)\n  - [Failed Verification Attempt Handling](#failed-verification-attempt-handling)\n- [Using a Custom Provider](#using-a-custom-provider)\n- [Errors and Exceptions](#errors-and-exceptions)\n- [Testing](#testing)\n\n## Description\nThis is a two-factor authentication package for _Laravel_. It is heavily inspired by the [Laravel Two-Factor Authentication](https://github.com/srmklive/laravel-twofactor-authentication) package. The main differences between this package and the aforementioned package are:\n\n- This package currently only works out of the box with the *MessageBird Verify* api or the `'null'` driver that goes through all the steps of the two-factor authentication process without actually doing any real verification. This could be useful for testing purposes. You can however, specify a custom provider yourself.\n- This package uses throttling to limit the number of unsuccessful authentication attempts in a certain amount of time.\n- The current version of this package is only guaranteed to work with _Laravel_ \u003e= 8. Version 2.* of this package works with _Laravel_ 5.5 to 7. Version 1.* of this package works with _Laravel_ 5.4. Versions of _Laravel_ prior to 5.4 have not been tested.\n\n## Important\nFrom _Laravel_ 5.8 and onwards, the default is to use `bigIncrements` instead of `increments` for the `id` column on the `users` table. As such, the default for this package is to use the same convention for the `user_id` column on the `two_factor_auths` table. If this is not what you want, you can change this to your liking by modifying the migration files that are published for this package.\n\nPublishing the package's migration files allows for more flexibility with regards to customising your database structure. However, it could also cause complications if you already have ran migrations as part of installing previous versions of this package. In this case you simply might want to bypass running the migrations again or only run them when in a specific environment. The `Schema::hasColumn()` and `Schema::hasTable()` methods should be of use here.\n\n### Optional Correction\nVersions of this package prior to v2.3.0 incorrectly created the `user_id` column on the `two_factor_auths` table using `increments` instead of `unsignedInteger`. Practically speaking, this error is of no concern. Although there is no need to have a _primary_ key for the `user_id` column, it doesn't cause any problems either. However, if for some reason you don't like this idea, it is safe to remove the _primary_ key using a migration of the form\n\n```php\n\u003c?php\n\nuse Illuminate\\Support\\Facades\\Schema;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Database\\Migrations\\Migration;\n\nclass RemovePrimaryFromTwoFactorAuthsTable extends Migration\n{\n    /**\n     * Run the migrations.\n     *\n     * @return void\n     */\n    public function up()\n    {\n        Schema::table('two_factor_auths', function (Blueprint $table) {\n            $table-\u003edropForeign(['user_id']);\n        });\n\n        Schema::table('two_factor_auths', function (Blueprint $table) {\n            $table-\u003eunsignedInteger('user_id')-\u003echange();\n            $table-\u003edropPrimary(['user_id']);\n            $table-\u003eforeign('user_id')-\u003ereferences('id')-\u003eon('users')-\u003eonDelete('cascade');\n        });\n    }\n\n    /**\n     * Reverse the migrations.\n     *\n     * @return void\n     */\n    public function down()\n    {\n        Schema::table('two_factor_auths', function (Blueprint $table) {\n            $table-\u003edropForeign(['user_id']);\n        });\n\n        Schema::table('two_factor_auths', function (Blueprint $table) {\n            $table-\u003eincrements('user_id')-\u003echange();\n            $table-\u003eforeign('user_id')-\u003ereferences('id')-\u003eon('users')-\u003eonDelete('cascade');\n        });\n    }\n}\n```\nNote that you will need the [doctrine/dbal](https://packagist.org/packages/doctrine/dbal) package for this migration to work. Furthermore, if the `id` column on your `users` table is of type `bigIncrements` you will have to change the lines `$table-\u003eunsignedInteger('user_id')-\u003echange();` to `$table-\u003eunsignedBigInteger('user_id')-\u003echange();` and `$table-\u003eincrements('user_id')-\u003echange();` to `$table-\u003ebigIncrements('user_id')-\u003echange();` respectively.\n\n## Installation\n1. To install using *Composer* run:\n ```shell\n composer require michaeldzjap/twofactor-auth\n ```\n If you want to use MessageBird Verify as the two-factor authentication provider then you also need to install the [MessageBird PHP api](https://github.com/messagebird/php-rest-api):\n ```shell\n composer require messagebird/php-rest-api\n ```\n and don't forget to add your `MESSAGEBIRD_ACCESS_KEY` and `TWO_FACTOR_AUTH_DRIVER=messagebird` variables to the `.env`. If you instead wish to use the `'null'` driver (default) then do **NOT** define the `TWO_FACTOR_AUTH_DRIVER` variable in your `.env`.\n\n From _Laravel_ 7 and onwards you will also need to install the [laravel/ui](https://github.com/laravel/ui) package:\n ```shell\n composer require laravel/ui\n ```\n2. Add the service provider to the `'providers'` array in `config/app.php`:\n ```php\n MichaelDzjap\\TwoFactorAuth\\TwoFactorAuthServiceProvider::class\n ```\n3. Run the following *artisan* command to publish the configuration, language and view files:\n ```shell\n php artisan vendor:publish\n ```\n If you want to publish only one of these file groups, for instance if you don't need the views or language files, you can append one of the following commands to the *artisan* command: `--tag=config`, `--tag=lang` or `--tag-views`.\n\n4. **Important**: Make sure you do this step _before_ you run any migrations for this package, as otherwise it might give you unexpected results.\n\n From _Laravel_ 5.8 and on, the default is to use `bigIncrements` instead of `increments` for the `id` column on the `users` table. As such, the default for this package is to use the same convention for the `user_id` column on the `two_factor_auths` table. If this is not what you want, you can modify the published migration files for this package.\n\n5. Run the following *artisan* command to run the database migrations\n ```shell\n php artisan migrate\n ```\n This will add a `mobile` column to the `users` table and create a `two_factor_auths` table.\n\n6. Add the following trait to your `User` model:\n ```php\n ...\n use MichaelDzjap\\TwoFactorAuth\\TwoFactorAuthenticable;\n\n class User extends Authenticatable\n {\n     use Notifiable, TwoFactorAuthenticable;\n ...\n ```\n Optionally, you might want to add `'mobile'` to your `$fillable` array.\n\n## Changes to the Login Process\nThe following two-factor authentication routes will be added automatically:\n```php\n$router-\u003egroup([\n    'middleware' =\u003e ['web', 'guest'],\n    'namespace' =\u003e 'App\\Http\\Controllers\\Auth',\n], function () use ($router) {\n    $router-\u003eget('/auth/token', 'TwoFactorAuthController@showTwoFactorForm')-\u003ename('auth.token');\n    $router-\u003epost('/auth/token', 'TwoFactorAuthController@verifyToken');\n});\n```\nThe first route is the route the user will be redirected to once the two-factor authentication process has been initiated. The second route is used to verify the two-factor authentication token that is to be entered by the user. The `showTwoFactorForm` controller method does exactly what it says. There do exist cases where you might want to respond differently however. For instance, instead of loading a view you might just want to return a `json` response. In that case you can simply overwrite `showTwoFactorForm` in the `TwoFactorAuthController` to be discussed below.\n\n1. Add the following import to `LoginController`:\n ```php\n ...\n use MichaelDzjap\\TwoFactorAuth\\Contracts\\TwoFactorProvider;\n\n class LoginController extends Controller\n {\n ...\n ```\n and also add the following functions:\n ```php\n /**\n  * The user has been authenticated.\n  *\n  * @param  \\Illuminate\\Http\\Request  $request\n  * @param  mixed  $user\n  * @return mixed\n  */\n protected function authenticated(Request $request, $user)\n {\n     if (resolve(TwoFactorProvider::class)-\u003eenabled($user)) {\n         return self::startTwoFactorAuthProcess($request, $user);\n     }\n\n     return redirect()-\u003eintended($this-\u003eredirectPath());\n }\n ```\n and\n ```php\n /**\n  * Log out the user and start the two factor authentication state.\n  *\n  * @param  \\Illuminate\\Http\\Request $request\n  * @param  \\App\\Models\\User $user\n  * @return \\Illuminate\\Http\\Response\n  */\n private function startTwoFactorAuthProcess(Request $request, $user)\n {\n     // Logout user, but remember user id\n     auth()-\u003elogout();\n     $request-\u003esession()-\u003eput(\n         'two-factor:auth', array_merge(['id' =\u003e $user-\u003eid], $request-\u003eonly('email', 'remember'))\n     );\n\n     self::registerUserAndSendToken($user);\n\n     return redirect()-\u003eroute('auth.token');\n }\n ```\n and lastly\n ```php\n /**\n  * Provider specific two-factor authentication logic. In the case of MessageBird\n  * we just want to send an authentication token via SMS.\n  *\n  * @param  \\App\\Models\\User $user\n  * @return mixed\n  */\n private function registerUserAndSendToken(User $user)\n {\n     // Custom, provider dependend logic for sending an authentication token\n     // to the user. In the case of MessageBird Verify this could simply be\n     // resolve(TwoFactorProvider::class)-\u003esendSMSToken($this-\u003euser)\n     // Here we assume this function is called from a queue'd job\n     dispatch(new SendSMSToken($user));\n }\n ```\n You can discard the third function if you do not want to send a two-factor authentication token automatically after a successful login attempt. Instead, you might want the user to instantiate this process from the form him/herself. In that case you would have to add the required route and controller method to trigger this function yourself. The best place for this would be the `TwoFactorAuthController` to be discussed next.\n\n2. Add a `TwoFactorAuthController` in `app/Http/Controllers/Auth` with the following content:\n ```php\n \u003c?php\n\n namespace App\\Http\\Controllers\\Auth;\n\n use App\\Http\\Controllers\\Controller;\n use App\\Providers\\RouteServiceProvider;\n use MichaelDzjap\\TwoFactorAuth\\Http\\Controllers\\TwoFactorAuthenticatesUsers;\n\n class TwoFactorAuthController extends Controller\n {\n     use TwoFactorAuthenticatesUsers;\n\n     /**\n      * The maximum number of attempts to allow.\n      *\n      * @var int\n      */\n     protected $maxAttempts = 5;\n\n     /**\n      * The number of minutes to throttle for.\n      *\n      * @var int\n      */\n     protected $decayMinutes = 1;\n\n     /**\n      * Where to redirect users after two-factor authentication passes.\n      *\n      * @var string\n      */\n     protected $redirectTo = RouteServiceProvider::HOME;\n }\n ```\n3. If you want to give textual feedback to the user when two-factor authentication fails due to an expired token or when throttling kicks in you may want to add this to `resources/views/auth/login.blade.php`:\n ```php\n ...\n \u003cform class=\"form-horizontal\" role=\"form\" method=\"POST\" action=\"{{ route('login') }}\"\u003e\n     @csrf\n\n     {{-- Add this block to show an error message in case of an expired token or user lockout --}}\n     @if ($errors-\u003ehas('token'))\n         \u003cdiv class=\"alert alert-danger alert-dismissible fade show\" role=\"alert\"\u003e\n             \u003cstrong\u003e{{ $errors-\u003efirst('token') }}\u003c/strong\u003e\n             \u003cbutton type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"\u003e\n                 \u003cspan aria-hidden=\"true\"\u003e\u0026times;\u003c/span\u003e\n             \u003c/button\u003e\n         \u003c/div\u003e\n     @endif\n ...\n ```\n\n### Failed Verification Attempt Handling\nThe default behaviour is to redirect to the previous view with an error message in case token verification fails. However, there most likely are instances where you would like to handle a failed token verification attempt differently. For instance, in the case of _MessageBird_ a token can only be verified once. Any attempt with the same token after a first failed attempt will always throw a `TokenAlreadyProcessedException` and hence, it would make more sense to either redirect to the _/login_ route again to start the entire authentication process from scratch or to redirect to a view where a new token can be requested.\n\nIn order to change the default behaviour it is possible to specify either a `$redirectToAfterFailure` property or a protected `redirectToAfterFailure` method on your `TwoFactorAuthController`. If one of these is present (the method taking precedence over the property), the default behaviour is bypassed and the user will be redirected to the specified route. To give a simple example, suppose you simply want to redirect to the _/login_ route after a failed verification attempt you would structure your `TwoFactorAuthController` like:\n```php\n\u003c?php\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Http\\Controllers\\Controller;\nuse App\\Providers\\RouteServiceProvider;\nuse MichaelDzjap\\TwoFactorAuth\\Http\\Controllers\\TwoFactorAuthenticatesUsers;\n\nclass TwoFactorAuthController extends Controller\n{\n    use TwoFactorAuthenticatesUsers;\n\n    /**\n     * Where to redirect users after two-factor authentication passes.\n     *\n     * @var string\n     */\n    protected $redirectTo = RouteServiceProvider::HOME;\n\n    /**\n     * Where to redirect users after two-factor authentication fails.\n     *\n     * @var string\n     */\n    protected $redirectToAfterFailure = '/login';\n}\n```\nRedirecting a user to a route for generating a fresh authentication token would require a bit more work, but certainly is possible this way.\n\n## Using a Custom Provider\nSince the v2.1.0 release it is possible to user your own custom provider. To do so your provider needs to implement `MichaelDzjap\\TwoFactorAuth\\Contracts\\TwoFactorProvider` (and possibly `MichaelDzjap\\TwoFactorAuth\\Contracts\\SMSToken` if you want to send the authentication token via SMS).\n\n1. Assuming the name of your custom provider is 'dummy', you should register it with `TwoFactorAuthManager` from a service provider (could be `\\App\\Providers\\AppServiceProvider`):\n ```php\n resolve(\\MichaelDzjap\\TwoFactorAuth\\TwoFactorAuthManager)-\u003eextend('dummy', function ($app) {\n     return new DummyProvider;\n });\n ```\n2. Add an entry for you custom provider in the 'provider' array in *app/config/twofactor-auth.php*:\n ```php\n ...\n 'dummy' =\u003e [\n\n     'driver' =\u003e 'dummy',\n\n ],\n ...\n ```\n3. Lastly, don't forget to change the name of the provider in your *.env*:\n ```\n TWO_FACTOR_AUTH_DRIVER=dummy\n ```\n\n## Errors and Exceptions\nUnfortunately the *MessageBird* php api throws rather generic exceptions when the verification of a token fails. The only way to distinguish an expired token from an invalid token is by comparing their error messages, which obviously is not a very robust mechanism. The reason this is rather unfortunate is because in the case of an invalid token we want to give the user at least a few (3) changes to re-enter the token before throttling kicks in, whereas in the case of an expired token we just want to redirect to the login screen right away.\n\n## Testing\nAn example project including unit and browser tests can be found [here](https://github.com/michaeldzjap/laravel-two-factor-authentication-example).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaeldzjap%2Flaravel-two-factor-authentication","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichaeldzjap%2Flaravel-two-factor-authentication","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaeldzjap%2Flaravel-two-factor-authentication/lists"}