Skip to content

Commit 80c25d9

Browse files
authored
feat: add a confirm key route (#503)
1 parent ab1ebd1 commit 80c25d9

File tree

14 files changed

+302
-7
lines changed

14 files changed

+302
-7
lines changed

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,7 @@ class AppServiceProvider extends ServiceProvider
102102
*/
103103
public function boot(): void
104104
{
105-
Event::listen(
106-
\Illuminate\Auth\Events\Login::class,
107-
\LaravelWebauthn\Listeners\LoginViaRemember::class
108-
);
105+
Event::subscribe(LoginViaRemember::class);
109106
}
110107
}
111108
```
@@ -278,6 +275,7 @@ These routes are defined:
278275
| POST `/webauthn/keys` | `webauthn.store` | Post data after a WebAuthn register check. |
279276
| DELETE `/webauthn/keys/{id}` | `webauthn.destroy` | Delete an existing key. |
280277
| PUT `/webauthn/keys/{id}` | `webauthn.update` | Update key properties (name, ...). |
278+
| POST `/webauthn/confirm-key` | `webauthn.key.confirm` | Post data to test a WebAuthn authentication without login. |
281279

282280

283281
You can customize the first part of the url by setting `prefix` value in the config file.
@@ -445,6 +443,7 @@ However, your browser will refuse to negotiate a relay to your security device w
445443
- connected HTTPS on port 443 (ports other than 443 will be rejected)
446444

447445
### Homestead
446+
448447
If you are a Laravel Homestead user, the default is to forward ports. You can switch from NAT/port forwarding to a private network with similar `Homestead.yaml` options:
449448

450449
```yaml

config/webauthn.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
| When using navigation, redirects to these url on success:
104104
| - login: after a successful login.
105105
| - register: after a successful Webauthn key creation.
106+
| - key-confirmation: after a successful Webauthn key confirmation.
106107
|
107108
| Redirects are not used in case of application/json requests.
108109
|
@@ -111,6 +112,7 @@
111112
'redirects' => [
112113
'login' => null,
113114
'register' => null,
115+
'key-confirmation' => null,
114116
],
115117

116118
/*

routes/routes.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use Illuminate\Support\Facades\Route;
44
use LaravelWebauthn\Http\Controllers\AuthenticateController;
5+
use LaravelWebauthn\Http\Controllers\ConfirmableKeyController;
56
use LaravelWebauthn\Http\Controllers\WebauthnKeyController;
67

78
$limiterMiddleware = ($limiter = config('webauthn.limiters.login')) !== null
@@ -38,4 +39,6 @@
3839
Route::post('keys', [WebauthnKeyController::class, 'store'])->name('webauthn.store');
3940
Route::delete('keys/{id}', [WebauthnKeyController::class, 'destroy'])->name('webauthn.destroy');
4041
Route::put('keys/{id}', [WebauthnKeyController::class, 'update'])->name('webauthn.update');
42+
43+
Route::post('confirm-key', [ConfirmableKeyController::class, 'store'])->name('webauthn.key.confirm');
4144
});

src/Actions/ConfirmKey.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace LaravelWebauthn\Actions;
4+
5+
use Illuminate\Contracts\Auth\StatefulGuard;
6+
use Illuminate\Http\Request;
7+
use LaravelWebauthn\Services\Webauthn;
8+
9+
class ConfirmKey
10+
{
11+
/**
12+
* Confirm that the given challenge is valid for the given user.
13+
*/
14+
public function __invoke(StatefulGuard $guard, Request $request): bool
15+
{
16+
return is_null(Webauthn::$confirmKeyUsingCallback)
17+
? $guard->attempt($this->getParams($request))
18+
: $this->confirmKeyUsingCustomCallback($request);
19+
}
20+
21+
/**
22+
* Confirm the give challenge using a custom callback.
23+
*/
24+
protected function confirmKeyUsingCustomCallback(Request $request): bool
25+
{
26+
return call_user_func(
27+
Webauthn::$confirmKeyUsingCallback,
28+
$this->getParams($request)
29+
);
30+
}
31+
32+
/**
33+
* Get the parameters from the request.
34+
*/
35+
private function getParams(Request $request): array
36+
{
37+
return $request->only(['id', 'rawId', 'response', 'type']);
38+
}
39+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace LaravelWebauthn\Contracts;
4+
5+
use Illuminate\Contracts\Support\Responsable;
6+
7+
interface FailedKeyConfirmedResponse extends Responsable
8+
{
9+
//
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace LaravelWebauthn\Contracts;
4+
5+
use Illuminate\Contracts\Support\Responsable;
6+
7+
interface KeyConfirmedResponse extends Responsable
8+
{
9+
//
10+
}

src/Facades/Webauthn.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* @method static bool enabled(\Illuminate\Contracts\Auth\Authenticatable $user)
1818
* @method static bool canRegister(\Illuminate\Contracts\Auth\Authenticatable $user)
1919
* @method static bool hasKey(\Illuminate\Contracts\Auth\Authenticatable $user)
20-
* @method static string redirects(string $redirect, $default = null)
20+
* @method static ?string redirects(string $redirect, $default = null)
2121
* @method static string model()
2222
* @method static bool userless()
2323
*
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
namespace LaravelWebauthn\Http\Controllers;
4+
5+
use Illuminate\Contracts\Auth\StatefulGuard;
6+
use Illuminate\Contracts\Support\Responsable;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Routing\Controller;
9+
use Illuminate\Support\Facades\Date;
10+
use LaravelWebauthn\Actions\ConfirmKey;
11+
use LaravelWebauthn\Contracts\FailedKeyConfirmedResponse;
12+
use LaravelWebauthn\Contracts\KeyConfirmedResponse;
13+
14+
class ConfirmableKeyController extends Controller
15+
{
16+
/**
17+
* The guard implementation.
18+
*
19+
* @var \Illuminate\Contracts\Auth\StatefulGuard
20+
*/
21+
protected $guard;
22+
23+
/**
24+
* Create a new controller instance.
25+
*/
26+
public function __construct(StatefulGuard $guard)
27+
{
28+
$this->guard = $guard;
29+
}
30+
31+
/**
32+
* Confirm the user's key.
33+
*/
34+
public function store(Request $request): Responsable
35+
{
36+
$confirmed = app(ConfirmKey::class)(
37+
$this->guard, $request
38+
);
39+
40+
if ($confirmed) {
41+
$request->session()->put('auth.password_confirmed_at', Date::now()->unix());
42+
}
43+
44+
return $confirmed
45+
? app(KeyConfirmedResponse::class)
46+
: app(FailedKeyConfirmedResponse::class);
47+
}
48+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace LaravelWebauthn\Http\Responses;
4+
5+
use Illuminate\Validation\ValidationException;
6+
use LaravelWebauthn\Contracts\FailedKeyConfirmedResponse as FailedKeyConfirmedResponseContract;
7+
8+
class FailedKeyConfirmedResponse implements FailedKeyConfirmedResponseContract
9+
{
10+
/**
11+
* Create an HTTP response that represents the object.
12+
*
13+
* @param \Illuminate\Http\Request $request
14+
* @return \Symfony\Component\HttpFoundation\Response
15+
*/
16+
#[\Override]
17+
public function toResponse($request)
18+
{
19+
$message = __('Invalid key.');
20+
21+
if ($request->wantsJson()) {
22+
throw ValidationException::withMessages([
23+
'key' => [$message],
24+
]);
25+
}
26+
27+
return back()->withErrors(['key' => $message]);
28+
}
29+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace LaravelWebauthn\Http\Responses;
4+
5+
use Illuminate\Http\JsonResponse;
6+
use LaravelWebauthn\Contracts\KeyConfirmedResponse as KeyConfirmedResponseContract;
7+
use LaravelWebauthn\Services\Webauthn;
8+
9+
class KeyConfirmedResponse implements KeyConfirmedResponseContract
10+
{
11+
/**
12+
* Create an HTTP response that represents the object.
13+
*
14+
* @param \Illuminate\Http\Request $request
15+
* @return \Symfony\Component\HttpFoundation\Response
16+
*/
17+
#[\Override]
18+
public function toResponse($request)
19+
{
20+
return $request->wantsJson()
21+
? new JsonResponse('', 201)
22+
: redirect()->intended(Webauthn::redirects('key-confirmation'));
23+
}
24+
}

0 commit comments

Comments
 (0)