Skip to content
This repository was archived by the owner on Oct 10, 2025. It is now read-only.

Conversation

@Bewinxed
Copy link
Collaborator

@Bewinxed Bewinxed commented Sep 9, 2025

What kind of change does this PR introduce?

This is a preparatory PR for adding webauthn support, which will have different params/responses across the client.mfa.{enroll/challenge/verify}() methods.

At the moment, some parameters in the calls are present in the type even when they are not valid for that factor/method type, and the response signature that the function returns does not match what's actually being returned.

Also, in some instances of the types returned, we can assert some values in the return types as being one property of the valid union types in that property, for example:

in the call to client.mfa.listFactors() an object returns which contains:`

all: [],
totp: [],
phone: [],

If the developer tries to iterate through this list, the factor_type will be a union of all the possible values in .all, and status will be verified | unverified which is correct.
but in .phone, we know that it's of type phone and that its status is verified, thus, we can assert this in the type, the Factor signature has been updated to accept 2 generics (which are optional), which enable us to set specific shapes in different parts of the library, some other examples:

  • In .enroll(), we know for a fact that the .type() of the factor will match the factorType that was passed in the function call,
  • Similarly, in the AuthMFAChallengeResponse, we know for a fact that the .type property will always be totp or phone based on what parameters were passed.

I believe these type revamps greately enhance the Developer Experience & enable the developer to not make any mistakes while accessing those fields.

It will also enable us to add specific documentation/links that will appear in intellisense depending on which parameters were passed to each function.

What is the current behavior?

Most calls to the backend result in either:

type ExampleCalltoT = {data: T, error: null} | {data: null, error: AuthError}

or

type ExampleCalltoT = {data: T, error: null} | {data: ...keys of T set to null, error: AuthError}

Also, there was no distinction in calls to .enroll() and .challenge()

What is the new behavior?

  • Consolidate response patterns into RequestResult<T> and RequestResultSafeDestructure<T>
  • Provide better type inference and IDE support with Prettify<T> utility
  • Add specific overloads to each function so that all the possible parameters show up in the IDE, then narrow down as soon as you lock into a specific parameter.
  • Update AMR Methods to include everything that's supported by the GoTrue backend.

Key improvements

  1. Generic response utilities:

    • RequestResult<T> - Standard result type with data/error pattern
    • RequestResultSafeDestructure<T> - Allows safe destructuring with null safety
    • Prettify<T> - Improves IDE hover experience by resolving mapped types
  2. Enhanced type safety:

    • Stronger typing for MFA factor types using discriminated unions
    • Better type narrowing for factor verification status
    • Consistent error type handling across all responses
  3. Internally breaking change:

    • _callRefreshToken now returns { data: Session, error: null } instead of { session: Session, error: null }, for consistency, this will not affect users as it's an internal utility (tests have been adjusted to accomodate this)

Additional context

This refactoring improves developer experience by:

  • Making response types more predictable and easier to work with
  • Providing better IntelliSense/autocomplete in IDEs
  • Reducing the likelihood of type-related bugs
  • Making the codebase more maintainable

All existing tests pass with minimal adjustments to accommodate the internal API change, and to prepare for the next PR here that adds webauthn types, which require that some function calls have distinct parameters/responses to not confuse users and guide them to use the library correctly using the TYPES as a source of truth, so the developer can use the api naturally just through the types.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 9, 2025

🚀 Preview Release Status

false


Last updated: 2025-09-24T13:57:14Z

@Bewinxed Bewinxed changed the title Streamline types & Enhance type safety, DX Improvements. refactor: streamline types and enhance type safety for MFA methods Sep 9, 2025
@Bewinxed Bewinxed force-pushed the bewinxed/streamline-types branch from 5ac4139 to 59414f8 Compare September 9, 2025 08:24
@hf hf changed the title refactor: streamline types and enhance type safety for MFA methods fix: streamline types and enhance type safety for MFA methods Sep 24, 2025
@hf hf force-pushed the bewinxed/streamline-types branch from e6dfe20 to 9ab1fd7 Compare September 24, 2025 13:57
@hf hf changed the title fix: streamline types and enhance type safety for MFA methods fix: prettify types and improved typesafety for MFA methods Sep 24, 2025
@hf hf merged commit 209003c into master Sep 24, 2025
9 checks passed
@hf hf deleted the bewinxed/streamline-types branch September 24, 2025 14:00
hf added a commit that referenced this pull request Sep 24, 2025
## 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
#1116

and this PR for `auth` supabase/auth#2163

---------

Co-authored-by: Stojan Dimitrovski <[email protected]>
mandarini pushed a commit to supabase/supabase-js that referenced this pull request Oct 2, 2025
## 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]>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants