Skip to content

Commit 20c81ef

Browse files
committed
feat: support event dispatch from webauthn-lib
1 parent af7cdb1 commit 20c81ef

File tree

7 files changed

+114
-44
lines changed

7 files changed

+114
-44
lines changed

config/webauthn.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@
266266
| See https://www.w3.org/TR/webauthn/#enum-userVerificationRequirement
267267
|
268268
| Supported: "required", "preferred", "discouraged".
269-
| Forced to "required" when userless is true.
269+
| This should be set to "required" when userless is true.
270270
|
271271
*/
272272

@@ -283,7 +283,7 @@
283283
| See https://www.w3.org/TR/webauthn/#enum-residentKeyRequirement
284284
|
285285
| Supported: "null", "required", "preferred", "discouraged".
286-
| Forced to "required" when userless is true.
286+
| This should be set to "required" when userless is true.
287287
|
288288
*/
289289

src/Events/EventDispatcher.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace LaravelWebauthn\Events;
4+
5+
use Illuminate\Contracts\Events\Dispatcher;
6+
use Psr\EventDispatcher\EventDispatcherInterface;
7+
8+
final class EventDispatcher implements EventDispatcherInterface
9+
{
10+
/**
11+
* Create a new event dispatcher instance.
12+
*/
13+
public function __construct(
14+
private readonly Dispatcher $dispatcher,
15+
) {}
16+
17+
/**
18+
* Dispatch the given event.
19+
*
20+
* @return object
21+
*/
22+
public function dispatch(object $event)
23+
{
24+
$this->dispatcher->dispatch($event);
25+
26+
return $event;
27+
}
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace LaravelWebauthn\Events;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Foundation\Events\Dispatchable;
7+
use Illuminate\Queue\SerializesModels;
8+
9+
/**
10+
* @psalm-suppress PossiblyUnusedProperty
11+
*/
12+
class WebauthnAuthenticate
13+
{
14+
use Dispatchable, SerializesModels;
15+
16+
/**
17+
* Create a new event instance.
18+
*
19+
* @param \Illuminate\Database\Eloquent\Model $webauthnKey The WebauthnKey used to authenticate.
20+
*/
21+
public function __construct(
22+
public Model $webauthnKey,
23+
) {}
24+
}

src/Models/WebauthnKey.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class WebauthnKey extends Model
3939
'aaguid',
4040
'credentialPublicKey',
4141
'counter',
42-
'timestamp',
4342
];
4443

4544
/**

src/Services/Webauthn/CredentialAssertionValidator.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Contracts\Auth\Authenticatable as User;
66
use Illuminate\Contracts\Cache\Repository as Cache;
77
use Illuminate\Http\Request;
8+
use LaravelWebauthn\Events\WebauthnAuthenticate;
89
use LaravelWebauthn\Exceptions\ResponseMismatchException;
910
use LaravelWebauthn\Services\Webauthn;
1011
use ParagonIE\ConstantTime\Base64UrlSafe;
@@ -36,15 +37,19 @@ public function __invoke(?User $user, array $data): bool
3637
$content = json_encode($data, flags: JSON_THROW_ON_ERROR);
3738
$publicKeyCredential = $this->loader->deserialize($content, PublicKeyCredential::class, 'json');
3839

40+
$webauthnKey = $this->getKey($user, $publicKeyCredential);
41+
3942
// Check the response against the request
4043
$this->validator->check(
41-
$this->getCredentialSource($user, $publicKeyCredential),
44+
$webauthnKey->publicKeyCredentialSource,
4245
$this->getResponse($publicKeyCredential),
4346
$this->pullPublicKey($user),
4447
$this->request->host(),
4548
optional($user)->getAuthIdentifier()
4649
);
4750

51+
WebauthnAuthenticate::dispatch($webauthnKey);
52+
4853
return true;
4954
}
5055

@@ -87,7 +92,7 @@ protected function getResponse(PublicKeyCredential $publicKeyCredential): Authen
8792
/**
8893
* Get credential source from user and public key.
8994
*/
90-
protected function getCredentialSource(?User $user, PublicKeyCredential $publicKeyCredential)
95+
protected function getKey(?User $user, PublicKeyCredential $publicKeyCredential)
9196
{
9297
$credentialId = $publicKeyCredential->rawId;
9398

@@ -97,7 +102,6 @@ protected function getCredentialSource(?User $user, PublicKeyCredential $publicK
97102
)->where(
98103
fn ($query) => $user !== null ? $query->where('user_id', $user->getAuthIdentifier()) : $query
99104
)
100-
->firstOrFail()
101-
->publicKeyCredentialSource;
105+
->firstOrFail();
102106
}
103107
}

src/WebauthnAuthenticatable.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace LaravelWebauthn;
44

55
use Illuminate\Database\Eloquent\Relations\HasMany;
6-
use LaravelWebauthn\Models\WebauthnKey;
6+
use LaravelWebauthn\Facades\Webauthn;
77

88
/**
99
* Trait to add Webauthn authenticatable to a user model.
@@ -17,6 +17,6 @@ trait WebauthnAuthenticatable
1717
*/
1818
public function webauthnKeys(): HasMany
1919
{
20-
return $this->hasMany(WebauthnKey::class);
20+
return $this->hasMany(Webauthn::model());
2121
}
2222
}

src/WebauthnServiceProvider.php

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use LaravelWebauthn\Contracts\RegisterSuccessResponse as RegisterSuccessResponseContract;
2525
use LaravelWebauthn\Contracts\RegisterViewResponse as RegisterViewResponseContract;
2626
use LaravelWebauthn\Contracts\UpdateResponse as UpdateResponseContract;
27+
use LaravelWebauthn\Events\EventDispatcher;
2728
use LaravelWebauthn\Facades\Webauthn as WebauthnFacade;
2829
use LaravelWebauthn\Http\Responses\DestroyResponse;
2930
use LaravelWebauthn\Http\Responses\FailedKeyConfirmedResponse;
@@ -58,6 +59,8 @@
5859
use Webauthn\CeremonyStep\CeremonyStepManagerFactory;
5960
use Webauthn\Counter\CounterChecker;
6061
use Webauthn\Counter\ThrowExceptionIfInvalid;
62+
use Webauthn\Event\CanDispatchEvents;
63+
use Webauthn\MetadataService\CanLogData;
6164
use Webauthn\PublicKeyCredentialRpEntity;
6265

6366
/**
@@ -82,6 +85,8 @@ public function boot(): void
8285
#[\Override]
8386
public function register(): void
8487
{
88+
$this->app->bind('webauthn.log', fn ($app) => $app['log']->channel(config('webauthn.log', config('logging.default'))));
89+
8590
$this->app->singleton(WebauthnFacade::class, Webauthn::class);
8691

8792
$this->registerResponseBindings();
@@ -93,7 +98,6 @@ public function register(): void
9398
);
9499

95100
$this->app->bind(StatefulGuard::class, fn () => Auth::guard(config('webauthn.guard', null)));
96-
$this->app->bind('webauthn.log', fn ($app) => $app['log']->channel(config('webauthn.log', config('logging.default'))));
97101
}
98102

99103
/**
@@ -132,6 +136,10 @@ public function registerResponseBindings(): void
132136
*/
133137
protected function bindWebAuthnPackage(): void
134138
{
139+
$this->app->singleton(EventDispatcher::class);
140+
$this->app->resolving(CanDispatchEvents::class, fn (CanDispatchEvents $object) => $object->setEventDispatcher($this->app[EventDispatcher::class]));
141+
$this->app->resolving(CanLogData::class, fn (CanLogData $object) => $object->setLogger($this->app['webauthn.log']));
142+
135143
$this->app->bind(
136144
PackedAttestationStatementSupport::class,
137145
fn ($app) => new PackedAttestationStatementSupport(
@@ -141,60 +149,69 @@ protected function bindWebAuthnPackage(): void
141149
$this->app->bind(
142150
AttestationStatementSupportManager::class,
143151
fn ($app) => tap(new AttestationStatementSupportManager, function ($manager) use ($app) {
144-
// https://www.w3.org/TR/webauthn/#sctn-none-attestation
145-
$manager->add($app[NoneAttestationStatementSupport::class]);
146-
147-
// https://www.w3.org/TR/webauthn/#sctn-fido-u2f-attestation
148-
$manager->add($app[FidoU2FAttestationStatementSupport::class]);
149-
150-
// https://www.w3.org/TR/webauthn/#sctn-android-key-attestation
151-
$manager->add($app[AndroidKeyAttestationStatementSupport::class]);
152-
153-
// https://www.w3.org/TR/webauthn/#sctn-tpm-attestation
154-
$manager->add($app[TPMAttestationStatementSupport::class]);
155-
156-
// https://www.w3.org/TR/webauthn/#sctn-packed-attestation
157-
$manager->add($app[PackedAttestationStatementSupport::class]);
158-
159-
// https://www.w3.org/TR/webauthn/#sctn-apple-anonymous-attestation
160-
$manager->add($app[AppleAttestationStatementSupport::class]);
152+
$supports = [
153+
// https://www.w3.org/TR/webauthn/#sctn-packed-attestation
154+
PackedAttestationStatementSupport::class,
155+
// https://www.w3.org/TR/webauthn/#sctn-tpm-attestation
156+
TPMAttestationStatementSupport::class,
157+
// https://www.w3.org/TR/webauthn/#sctn-android-key-attestation
158+
AndroidKeyAttestationStatementSupport::class,
159+
// https://www.w3.org/TR/webauthn/#sctn-fido-u2f-attestation
160+
FidoU2FAttestationStatementSupport::class,
161+
// https://www.w3.org/TR/webauthn/#sctn-none-attestation
162+
NoneAttestationStatementSupport::class,
163+
// https://www.w3.org/TR/webauthn/#sctn-apple-anonymous-attestation
164+
AppleAttestationStatementSupport::class,
165+
];
166+
foreach ($supports as $support) {
167+
$manager->add($app[$support]);
168+
}
161169
})
162170
);
163171
$this->app->bind(
164172
AttestationObjectLoader::class,
165-
fn ($app) => tap(new AttestationObjectLoader(
166-
$app[AttestationStatementSupportManager::class]
167-
), fn (AttestationObjectLoader $loader) => $loader->setLogger($app['webauthn.log'])
173+
fn ($app) => new AttestationObjectLoader(
174+
attestationStatementSupportManager: $app[AttestationStatementSupportManager::class]
168175
)
169176
);
170-
171177
$this->app->bind(
172-
CounterChecker::class,
173-
fn ($app) => new ThrowExceptionIfInvalid($app['webauthn.log'])
178+
SerializerInterface::class,
179+
fn ($app) => (new \Webauthn\Denormalizer\WebauthnSerializerFactory($app[AttestationStatementSupportManager::class]))->create()
174180
);
175181

176182
$this->app->bind(
177-
AuthenticatorAttestationResponseValidator::class,
178-
fn ($app) => tap(new AuthenticatorAttestationResponseValidator(
179-
ceremonyStepManager: ($app[CeremonyStepManagerFactory::class])->creationCeremony(),
180-
), fn (AuthenticatorAttestationResponseValidator $responseValidator) => $responseValidator->setLogger($app['webauthn.log'])
183+
CounterChecker::class,
184+
fn ($app) => new ThrowExceptionIfInvalid(
185+
logger: $app['webauthn.log']
181186
)
182187
);
188+
183189
$this->app->bind(
184190
CeremonyStepManagerFactory::class,
185191
fn ($app) => tap(new CeremonyStepManagerFactory, function (CeremonyStepManagerFactory $factory) use ($app) {
186192
$factory->setExtensionOutputCheckerHandler($app[ExtensionOutputCheckerHandler::class]);
187193
$factory->setAlgorithmManager($app[CoseAlgorithmManager::class]);
188194
$factory->setCounterChecker($app[CounterChecker::class]);
195+
$factory->setAttestationStatementSupportManager($app[AttestationStatementSupportManager::class]);
196+
// $factory->setAllowedOrigins(
197+
// allowedOrigins: $app['config']->get('webauthn.allowed_origins'),
198+
// allowSubdomains: $app['config']->get('webauthn.allow_subdomains')
199+
// );
189200
})
190201
);
202+
$this->app->bind(
203+
AuthenticatorAttestationResponseValidator::class,
204+
fn ($app) => new AuthenticatorAttestationResponseValidator(
205+
ceremonyStepManager: ($app[CeremonyStepManagerFactory::class])->creationCeremony(),
206+
)
207+
);
191208
$this->app->bind(
192209
AuthenticatorAssertionResponseValidator::class,
193-
fn ($app) => tap((new AuthenticatorAssertionResponseValidator(
210+
fn ($app) => (new AuthenticatorAssertionResponseValidator(
194211
ceremonyStepManager: ($app[CeremonyStepManagerFactory::class])->requestCeremony()
195-
)), fn (AuthenticatorAssertionResponseValidator $responseValidator) => $responseValidator->setLogger($app['webauthn.log'])
196-
)
212+
))
197213
);
214+
198215
$this->app->bind(
199216
AuthenticatorSelectionCriteria::class,
200217
fn ($app) => new AuthenticatorSelectionCriteria(
@@ -203,6 +220,7 @@ protected function bindWebAuthnPackage(): void
203220
residentKey: $app['config']->get('webauthn.resident_key', 'preferred')
204221
)
205222
);
223+
206224
$this->app->bind(
207225
PublicKeyCredentialRpEntity::class,
208226
fn ($app) => new PublicKeyCredentialRpEntity(
@@ -211,6 +229,7 @@ protected function bindWebAuthnPackage(): void
211229
icon: $app['config']->get('webauthn.icon')
212230
)
213231
);
232+
214233
$this->app->bind(
215234
CoseAlgorithmManager::class,
216235
fn ($app) => $app[CoseAlgorithmManagerFactory::class]
@@ -243,10 +262,6 @@ protected function bindWebAuthnPackage(): void
243262
}
244263
})
245264
);
246-
$this->app->bind(
247-
SerializerInterface::class,
248-
fn ($app) => (new \Webauthn\Denormalizer\WebauthnSerializerFactory($app[AttestationStatementSupportManager::class]))->create()
249-
);
250265
}
251266

252267
/**

0 commit comments

Comments
 (0)