-
-
Notifications
You must be signed in to change notification settings - Fork 366
Expand file tree
/
Copy pathWebAuthnLoginController.php
More file actions
134 lines (115 loc) · 3.82 KB
/
WebAuthnLoginController.php
File metadata and controls
134 lines (115 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php
/**
* SPDX-License-Identifier: MIT
* Copyright (c) 2017-2018 Tobias Reich
* Copyright (c) 2018-2025 LycheeOrg.
*/
namespace App\Http\Controllers\WebAuthn;
use App\Exceptions\UnauthenticatedException;
use App\Exceptions\WebAuthnDisabledExecption;
use App\Models\User;
use App\Providers\AuthServiceProvider;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Support\Responsable;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Laragear\WebAuthn\Assertion\Validator\AssertionValidation;
use Laragear\WebAuthn\Assertion\Validator\AssertionValidator;
use Laragear\WebAuthn\Http\Requests\AssertedRequest;
use Laragear\WebAuthn\Http\Requests\AssertionRequest;
use Laragear\WebAuthn\JsonTransport;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class WebAuthnLoginController extends Controller
{
/**
* Returns the challenge to assertion.
*
* @throws BindingResolutionException
*/
public function options(AssertionRequest $request): Responsable
{
$this->checkEnabled();
/** @phpstan-ignore staticMethod.dynamicCall */
$fields = $request->validate([
'user_id' => 'sometimes|int',
'username' => 'sometimes|string',
]);
$username = $fields['username'] ?? null;
$authenticatable = $fields['user_id'] ?? ($username !== null ? ['username' => $username] : null);
return $request->toVerify($authenticatable);
}
/**
* Log the user in.
*
* 1. We retrieve the credentials candidate
* 2. Double check the challenge is signed.
* 3. Retrieve the User from the credential ID, we will use it to validate later (otherwise keys like yubikey4 are not working).
* 4. Validate the credentials
* 5. Log in on success
*/
public function login(AssertedRequest $request, AssertionValidator $validator): void
{
$this->checkEnabled();
$credentials = $request->validated();
if (!$this->isSignedChallenge($credentials)) {
// @codeCoverageIgnoreStart
throw new HttpException(Response::HTTP_UNPROCESSABLE_ENTITY, 'Response is not signed.');
// @codeCoverageIgnoreEnd
}
$associated_user = $this->retrieveByCredentials($credentials);
if ($associated_user === null) {
// @codeCoverageIgnoreStart
throw new HttpException(Response::HTTP_UNPROCESSABLE_ENTITY, 'Associated user does not exists.');
// @codeCoverageIgnoreEnd
}
$json_transport = new JsonTransport($request->only(AssertionValidation::REQUEST_KEYS));
$credential = $validator
->send(new AssertionValidation($json_transport, $associated_user))
->thenReturn()
->credential;
if ($credential === null) {
// @codeCoverageIgnoreStart
throw new UnauthenticatedException('Invalid credentials');
// @codeCoverageIgnoreEnd
}
$authenticatable = $credential->authenticatable;
Auth::login($authenticatable);
}
/**
* Check if the credentials are for a public key signed challenge.
*
* @param array<string,string> $credentials
*/
private function isSignedChallenge(array $credentials): bool
{
return isset($credentials['id'], $credentials['rawId'], $credentials['response'], $credentials['type']);
}
/**
* Retrieve a user by the given credentials.
*
* @param array<string,string> $credentials
*/
public function retrieveByCredentials(array $credentials): User|null
{
/** @var User|null $user */
$user = User::whereHas('webAuthnCredentials',
fn ($query) => $query->where('id', '=', $credentials['id'])->whereNull('disabled_at')
)->first();
return $user;
}
/**
* Validate whether the WebAuthn is enabled in the configuration.
* If not throw an exception with status code 403 (Forbidden).
*
* @return void
*
* @throws WebAuthnDisabledExecption
*/
private function checkEnabled(): void
{
if (AuthServiceProvider::isWebAuthnEnabled() === false) {
throw new WebAuthnDisabledExecption();
}
}
}