Last active
September 29, 2022 15:03
-
-
Save rawilk/d9384836ea03413b3f8c572cfdf9844d to your computer and use it in GitHub Desktop.
WebAuthn Key Authentication - Laravel WebAuthn Package
This file contains 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
<div> | |
{{-- change "head" to a stack name you chose in your layout file. --}} | |
@push('head') | |
@webauthnScripts | |
@endpush | |
<div x-data="{ | |
publicKey: @entangle('publicKey').defer, | |
keyData: @entangle('keyData').defer, | |
webAuthnSupported: true, | |
errorMessages: {{ Js::from($this->errorMessages) }}, | |
errorMessage: null, | |
notifyCallback() { | |
return (errorName, defaultMessage) => { | |
this.errorMessage = this.errorMessages[errorName] || defaultMessage; | |
}; | |
}, | |
webAuthn: new WebAuthn, | |
init() { | |
this.webAuthnSupported = this.webAuthn.supported(); | |
if (! this.webAuthnSupported) { | |
this.errorMessage = this.errorMessages[this.webAuthn.notSupportedType()]; | |
} | |
this.webAuthn.registerNotifyCallback(this.notifyCallback()); | |
this.authenticate(); | |
}, | |
authenticate() { | |
if (! this.webAuthnSupported) { | |
return; | |
} | |
this.keyData = null; | |
this.errorMessage = null; | |
// This is the most important part. | |
this.webAuthn.sign(JSON.parse(this.publicKey), data => { | |
this.keyData = data; | |
@this.login(); | |
}); | |
}, | |
}"> | |
<div x-show="! errorMessage"> | |
<p>Interact with your authenticator...</p> | |
</div> | |
<div x-show="errorMessage"> | |
<p class="text-base text-red-600" x-html="errorMessage"></p> | |
<div class="mt-10" x-show="webAuthnSupported"> | |
<button type="button" | |
class="..." | |
x-on:click="errorMessage = null; authenticate();" | |
> | |
Retry | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> |
This file contains 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
<?php | |
namespace App\Http\Livewire; | |
use App\Http\Requests\TwoFactorLoginRequest; | |
use Illuminate\Support\Arr; | |
use Illuminate\Support\Facades\Lang; | |
use Livewire\Component; | |
use Rawilk\Webauthn\Actions\PrepareAssertionData; | |
use Rawilk\Webauthn\Facades\Webauthn; | |
class TwoFactorChallenge extends Component | |
{ | |
/* | |
* Public WebAuthn assertion key for when | |
* a user has at least one key registered. | |
*/ | |
public string $publicKey = ''; | |
public $keyData; | |
public function getErrorMessagesProperty(): array | |
{ | |
return [ | |
...Lang::get('webauthn::alerts.auth') ?? [], | |
'InvalidStateError' => __('webauthn::alerts.login_not_allowed_error'), | |
'notSupported' => __('webauthn::alerts.browser_not_supported'), | |
'notSecured' => __('webauthn::alerts.browser_not_secure'), | |
]; | |
} | |
// See code below for request example | |
public function login(TwoFactorLoginRequest $request) | |
{ | |
$this->resetErrorBag(); | |
$user = $request->challengedUser(); | |
// WebAuthn package will update the last_used_at timestamp of the key. | |
$valid = Webauthn::validateAssertion($user, Arr::only((array) $this->keyData, [ | |
'id', | |
'rawId', | |
'response', | |
'type', | |
])); | |
if (! $valid) { | |
// Notify user of failed authentication attempt. | |
return; | |
} | |
auth()->login($user); | |
// Handle successful login | |
} | |
public function mount(TwoFactorLoginRequest $request): void | |
{ | |
if (! $request->hasChallengedUser()) { | |
redirect('/login'); | |
} | |
$this->publicKey = json_encode(app(PrepareAssertionData::class)($request->challengedUser())); | |
} | |
public function render() | |
{ | |
return view('livewire.two-factor-challenge'); | |
} | |
} |
This file contains 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
<?php | |
namespace App\Http\Requests; | |
use Illuminate\Foundation\Http\FormRequest; | |
class TwoFactorLoginRequest extends FormRequest | |
{ | |
/** The user attempting the two factor challenge */ | |
protected $challengedUser; | |
public function authorize(): bool | |
{ | |
return true; | |
} | |
public function rules(): array | |
{ | |
return []; | |
} | |
/** | |
* Determine if there is a challenged user in the current session. | |
*/ | |
public function hasChallengedUser(): bool | |
{ | |
$model = app(config('auth.providers.users.model')); | |
return $this->session()->has('login.id') | |
&& $model::find($this->session()->get('login.id')); | |
} | |
/** | |
* Get the user that is attempting the two factor challenge. | |
*/ | |
public function challengedUser() | |
{ | |
if ($this->challengedUser) { | |
return $this->challengedUser; | |
} | |
$model = app(config('auth.providers.users.model')); | |
if ( | |
! $this->session()->has('login.id') | |
|| ! $user = $model::find($this->session()->get('login.id')) | |
) { | |
throw new \Exception('No user found.'); | |
} | |
return $this->challengedUser = $user; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment