-
Notifications
You must be signed in to change notification settings - Fork 485
Commit 9dfff67
feat: mfa with webauthn support (#1118)
## What kind of change does this PR introduce?
**Feature** - This PR introduces YubiKey support for Multi-Factor
Authentication (MFA) via WebAuthn, enabling users to authenticate with
hardware security keys.
## What is the current behavior?
Currently, Supabase Auth JS supports two MFA methods:
- TOTP (Time-based One-Time Password) authenticators
- SMS-based verification
## What is the new behavior?
This PR adds full WebAuthn support to the authentication library, the
defaults enable yubikey support at the moment, but it allows the user to
override some parameters client-side to use other types of passkey
methods.
The PR adds the 'webauthn' factor type, to `listFactors`, `enroll()`,
`challenge()`, and `verify()`
(De)serialization of the webauthn reponse/credential object is done
behind the scenes via dedicated objects.
it also adds a new `experimental` namespace `.mfa.webauthn` which has a
`.register()` and `.authenticate()` methods, these methods allows
**single click** yubikey 2FA addition with a single function call.
additionally, we have `webauthn.{enroll|challenge|verify}()`, which
abstract away some of the logic surrounding enrollment, interaction with
the verifier, and have defaults for factortype etc.
### Two ways to use the new api:
#### Single Step
```typescript
const { data, error } = await client.mfa.webauthn.register({
friendlyName: `Security Key ${new Date().toLocaleDateString()}`,
rpId: window.location.hostname,
rpOrigins: [window.location.origin]
}, {
authenticatorSelection: {
authenticatorAttachment: 'platform',
residentKey: 'discouraged',
userVerification: 'discouraged',
requireResidentKey: false
}
});
if (error) throw error;
console.log(data); // <- session
```
#### Multi Step Composition
```typescript
const { enroll, challenge, verify } = new WebAuthnApi(client);
return enroll({
friendlyName: params.friendlyName
})
.then(async ({ data, error }) => {
if (!data) {
throw error;
}
console.log(`enrolled factor, id: ${data.id}`, 'success');
return await challenge({
factorId: data?.id,
webauthn: {
rpId: params.rpId,
rpOrigins: params.rpOrigins
},
signal: undefined
});
})
.then(async ({ data, error }) => {
if (!data) {
throw error;
}
console.log(`challenged factor, id: ${data.factorId}`, 'success');
return await verify({
factorId: data.factorId,
challengeId: data.challengeId,
webauthn: {
rpId: params.rpId,
rpOrigins: params.rpOrigins,
type: data.webauthn.type,
credential_response: data.webauthn.credential_response
}
});
})
.then(({ data, error }) => {
if (!data) {
throw error;
}
console.log(`verified factor, id: ${data.access_token}`, 'success');
return data;
});
```
## Additional context
While this PR focuses on YubiKey support, the architecture is designed
to accommodate additional authenticator types in future releases
(platform authenticators, passkeys, etc.) without requiring significant
refactoring.
I've added `webauthn.dom.ts` and `webauthn.errors.ts` which attempt to
augment the typescript interfaces for webauthn since they are out of
date and there are some new features that its not aware of yet but are
publicly available in all major browsers.
For all such types, and due to the complexity of the API, I've added
comprehensive jsdocs for each parameter with reference to the w3a spec
for reference on their usage.
in all webauthn related methods, I've added the ability to **override**
any of the parameters we pass by default to the
`credentials.{get|create}()` method for convenience.
This PR is dependent on my previous PR for streamlining types
supabase/auth-js#1116
and this PR for `auth` supabase/auth#2163
---------
Co-authored-by: Stojan Dimitrovski <[email protected]>1 parent f762def commit 9dfff67Copy full SHA for 9dfff67
File tree
Expand file treeCollapse file tree
7 files changed
+2291
-97
lines changedFilter options
- packages/core/auth-js/src
- lib
Expand file treeCollapse file tree
7 files changed
+2291
-97
lines changed
0 commit comments