-
Notifications
You must be signed in to change notification settings - Fork 185
Add Two Factor Auth For Livewire Starter Kit #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,230
−99
Merged
Changes from 38 commits
Commits
Show all changes
45 commits
Select commit
Hold shift + click to select a range
419a373
Add Fortify
pushpak1300 4cfad5b
wip
pushpak1300 de92b27
Merge branch 'main' into feat/two-factor-auth
pushpak1300 0357084
Support Fortify Options
pushpak1300 d965439
FIx Tests
pushpak1300 e72ce33
Add Tests
pushpak1300 6172b74
Test Fix
pushpak1300 f5c6063
Refactor Component
pushpak1300 3609fd8
Refactor Code
pushpak1300 2683ee4
formatting
pushpak1300 07c2414
Refactoring
pushpak1300 41e079a
Fix test
pushpak1300 da7d298
Formatting
pushpak1300 9741f78
formatting
pushpak1300 49acd68
formatting
pushpak1300 f33fa41
refactoring
pushpak1300 ac5fa58
Refactor input-otp-component it to use the $nextTick
pushpak1300 d919841
simplify input-otp component
pushpak1300 567f2c4
Change conditional signature for better clarity
pushpak1300 9355369
Merge branch 'main' into feat/two-factor-auth
pushpak1300 002c3df
Refactor input-otp component for improved clarity
pushpak1300 e393696
Refactor two-factor authentication views for improved clarity and fun…
pushpak1300 3252948
Add error handling in the components
pushpak1300 78b9971
Refactor confirm password functionality to use fortify confirm password
pushpak1300 c5d5f9c
Use regex instead of checking number
pushpak1300 6026b37
Remove unnecessary calculation of next index in input-otp component
pushpak1300 970ed4e
Formatting
pushpak1300 ecebcf9
use when instead of assigning variable in routes file
joetannenbaum 10944f6
formatting
joetannenbaum 2ff6a8e
formatting
joetannenbaum e847251
formatting
joetannenbaum ac02b1b
undo typo
joetannenbaum d41ddc1
formatting
joetannenbaum eea0aae
formatting
joetannenbaum ead85d1
extract x data out to script tag
joetannenbaum d151879
reset instead of manual values
joetannenbaum 832ef44
Revert "extract x data out to script tag"
joetannenbaum 0c3ef7b
formatting
joetannenbaum 4db6d9c
Update layout.blade.php
taylorotwell 51a3350
Update input-otp.blade.php
taylorotwell e0e321d
Update two-factor-challenge.blade.php
taylorotwell f5534c7
Update AuthenticationTest.php
taylorotwell e387cf8
formatting
taylorotwell 4dded7b
periods
taylorotwell 15be9cc
some spacing
taylorotwell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
namespace App\Providers; | ||
|
||
use Illuminate\Cache\RateLimiting\Limit; | ||
use Illuminate\Http\Request; | ||
use Illuminate\Support\Facades\RateLimiter; | ||
use Illuminate\Support\ServiceProvider; | ||
use Laravel\Fortify\Fortify; | ||
|
||
class FortifyServiceProvider extends ServiceProvider | ||
{ | ||
/** | ||
* Register any application services. | ||
*/ | ||
public function register(): void | ||
{ | ||
// | ||
} | ||
|
||
/** | ||
* Bootstrap any application services. | ||
*/ | ||
public function boot(): void | ||
{ | ||
Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge')); | ||
Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password')); | ||
|
||
RateLimiter::for('two-factor', function (Request $request) { | ||
return Limit::perMinute(5)->by($request->session()->get('login.id')); | ||
}); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
<?php | ||
|
||
use Laravel\Fortify\Features; | ||
|
||
return [ | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Fortify Guard | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Here you may specify which authentication guard Fortify will use while | ||
| authenticating users. This value should correspond with one of your | ||
| guards that is already present in your "auth" configuration file. | ||
| | ||
*/ | ||
|
||
'guard' => 'web', | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Fortify Password Broker | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Here you may specify which password broker Fortify can use when a user | ||
| is resetting their password. This configured value should match one | ||
| of your password brokers setup in your "auth" configuration file. | ||
| | ||
*/ | ||
|
||
'passwords' => 'users', | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Username / Email | ||
|-------------------------------------------------------------------------- | ||
| | ||
| This value defines which model attribute should be considered as your | ||
| application's "username" field. Typically, this might be the email | ||
| address of the users but you are free to change this value here. | ||
| | ||
| Out of the box, Fortify expects forgot password and reset password | ||
| requests to have a field named 'email'. If the application uses | ||
| another name for the field you may define it below as needed. | ||
| | ||
*/ | ||
|
||
'username' => 'email', | ||
|
||
'email' => 'email', | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Lowercase Usernames | ||
|-------------------------------------------------------------------------- | ||
| | ||
| This value defines whether usernames should be lowercased before saving | ||
| them in the database, as some database system string fields are case | ||
| sensitive. You may disable this for your application if necessary. | ||
| | ||
*/ | ||
|
||
'lowercase_usernames' => true, | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Home Path | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Here you may configure the path where users will get redirected during | ||
| authentication or password reset when the operations are successful | ||
| and the user is authenticated. You are free to change this value. | ||
| | ||
*/ | ||
|
||
'home' => '/dashboard', | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Fortify Routes Prefix / Subdomain | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Here you may specify which prefix Fortify will assign to all the routes | ||
| that it registers with the application. If necessary, you may change | ||
| subdomain under which all of the Fortify routes will be available. | ||
| | ||
*/ | ||
|
||
'prefix' => '', | ||
|
||
'domain' => null, | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Fortify Routes Middleware | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Here you may specify which middleware Fortify will assign to the routes | ||
| that it registers with the application. If necessary, you may change | ||
| these middleware but typically this provided default is preferred. | ||
| | ||
*/ | ||
|
||
'middleware' => ['web'], | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Rate Limiting | ||
|-------------------------------------------------------------------------- | ||
| | ||
| By default, Fortify will throttle logins to five requests per minute for | ||
| every email and IP address combination. However, if you would like to | ||
| specify a custom rate limiter to call then you may specify it here. | ||
| | ||
*/ | ||
|
||
'limiters' => [ | ||
'login' => 'login', | ||
'two-factor' => 'two-factor', | ||
], | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Register View Routes | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Here you may specify if the routes returning views should be disabled as | ||
| you may not need them when building your own application. This may be | ||
| especially true if you're writing a custom single-page application. | ||
| | ||
*/ | ||
|
||
'views' => true, | ||
|
||
/* | ||
|-------------------------------------------------------------------------- | ||
| Features | ||
|-------------------------------------------------------------------------- | ||
| | ||
| Some of the Fortify features are optional. You may disable the features | ||
| by removing them from this array. You're free to only remove some of | ||
| these features or you can even remove all of these if you need to. | ||
| | ||
*/ | ||
|
||
'features' => [ | ||
// Features::registration(), | ||
// Features::resetPasswords(), | ||
// Features::emailVerification(), | ||
// Features::updateProfileInformation(), | ||
// Features::updatePasswords(), | ||
Features::twoFactorAuthentication([ | ||
'confirm' => true, | ||
'confirmPassword' => true, | ||
// 'window' => 0, | ||
]), | ||
], | ||
|
||
]; |
34 changes: 34 additions & 0 deletions
34
database/migrations/2025_09_02_075243_add_two_factor_columns_to_users_table.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?php | ||
|
||
use Illuminate\Database\Migrations\Migration; | ||
use Illuminate\Database\Schema\Blueprint; | ||
use Illuminate\Support\Facades\Schema; | ||
|
||
return new class extends Migration | ||
{ | ||
/** | ||
* Run the migrations. | ||
*/ | ||
public function up(): void | ||
{ | ||
Schema::table('users', function (Blueprint $table) { | ||
$table->text('two_factor_secret')->after('password')->nullable(); | ||
$table->text('two_factor_recovery_codes')->after('two_factor_secret')->nullable(); | ||
$table->timestamp('two_factor_confirmed_at')->after('two_factor_recovery_codes')->nullable(); | ||
}); | ||
} | ||
|
||
/** | ||
* Reverse the migrations. | ||
*/ | ||
public function down(): void | ||
{ | ||
Schema::table('users', function (Blueprint $table) { | ||
$table->dropColumn([ | ||
'two_factor_secret', | ||
'two_factor_recovery_codes', | ||
'two_factor_confirmed_at', | ||
]); | ||
}); | ||
} | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
@props([ | ||
'digits' => 6, | ||
'name' => 'code', | ||
]) | ||
|
||
<div | ||
@focus-2fa-auth-code.window="$refs.input1?.focus()" | ||
@clear-2fa-auth-code.window="clearAll()" | ||
class="relative" | ||
x-data="{ | ||
totalDigits: @js($digits), | ||
digitIndices: @js(range(1, $digits)), | ||
init() { | ||
$nextTick(() => { | ||
this.$refs.input1?.focus(); | ||
}); | ||
}, | ||
getInput(index) { | ||
return this.$refs['input' + index]; | ||
}, | ||
setValue(index, value) { | ||
this.getInput(index).value = value; | ||
}, | ||
getCode() { | ||
return this.digitIndices | ||
.map(i => this.getInput(i).value) | ||
.join(''); | ||
}, | ||
updateHiddenField() { | ||
this.$refs.code.value = this.getCode(); | ||
this.$refs.code.dispatchEvent(new Event('input', { bubbles: true })); | ||
this.$refs.code.dispatchEvent(new Event('change', { bubbles: true })); | ||
}, | ||
handleNumberKey(index, key) { | ||
this.setValue(index, key); | ||
|
||
if (index < this.totalDigits) { | ||
this.getInput(index + 1).focus(); | ||
} | ||
|
||
$nextTick(() => { | ||
this.updateHiddenField(); | ||
}); | ||
}, | ||
handleBackspace(index) { | ||
const currentInput = this.getInput(index); | ||
|
||
if (currentInput.value !== '') { | ||
currentInput.value = ''; | ||
this.updateHiddenField(); | ||
return; | ||
} | ||
|
||
if (index <= 1) { | ||
return; | ||
} | ||
|
||
const previousInput = this.getInput(index - 1); | ||
previousInput.value = ''; | ||
previousInput.focus(); | ||
this.updateHiddenField(); | ||
}, | ||
handleKeyDown(index, event) { | ||
const key = event.key; | ||
|
||
if (/^[0-9]$/.test(key)) { | ||
event.preventDefault(); | ||
this.handleNumberKey(index, key); | ||
return; | ||
} | ||
|
||
if (key === 'Backspace') { | ||
event.preventDefault(); | ||
this.handleBackspace(index); | ||
return; | ||
} | ||
}, | ||
handlePaste(event) { | ||
event.preventDefault(); | ||
const pastedText = (event.clipboardData || window.clipboardData).getData('text'); | ||
const numericOnly = pastedText.replace(/[^0-9]/g, ''); | ||
const digitsToFill = Math.min(numericOnly.length, this.totalDigits); | ||
|
||
this.digitIndices | ||
.slice(0, digitsToFill) | ||
.forEach(index => { | ||
this.setValue(index, numericOnly[index - 1]); | ||
}); | ||
|
||
if (numericOnly.length >= this.totalDigits) { | ||
this.updateHiddenField(); | ||
} | ||
}, | ||
clearAll() { | ||
this.digitIndices.forEach(index => { | ||
this.setValue(index, ''); | ||
}); | ||
this.$refs.code.value = ''; | ||
this.$refs.input1?.focus(); | ||
} | ||
}" | ||
> | ||
<div class="flex items-center"> | ||
@for ($x = 1; $x <= $digits; $x++) | ||
<input | ||
x-ref="input{{ $x }}" | ||
type="text" | ||
inputmode="numeric" | ||
pattern="[0-9]" | ||
maxlength="1" | ||
autocomplete="off" | ||
@paste="handlePaste" | ||
@keydown="handleKeyDown({{ $x }}, $event)" | ||
@focus="$el.select()" | ||
@input="$el.value = $el.value.replace(/[^0-9]/g, '').slice(0, 1)" | ||
@class([ | ||
'flex size-10 items-center justify-center border border-zinc-300 bg-accent-foreground text-center text-sm font-medium text-accent-content transition-colors focus:border-accent focus:border-2 focus:outline-none focus:relative focus:z-10 dark:border-zinc-700 dark:focus:border-accent', | ||
'rounded-l-md' => $x === 1, | ||
'rounded-r-md' => $x === $digits, | ||
'-ml-px' => $x > 1, | ||
]) | ||
/> | ||
@endfor | ||
</div> | ||
|
||
<input | ||
{{ $attributes->except(['digits']) }} | ||
type="hidden" | ||
x-ref="code" | ||
name="{{ $name }}" | ||
minlength="{{ $digits }}" | ||
maxlength="{{ $digits }}" | ||
/> | ||
</div> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.