Skip to content

Commit

Permalink
Merge pull request #83 from Paul-Taiwo/feat-exclude-credentials
Browse files Browse the repository at this point in the history
Add excludeCredentials to Passkey Creation Process
  • Loading branch information
dagnelies authored Jan 27, 2025
2 parents b9fa783 + 6529e36 commit be10451
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 76 deletions.
3 changes: 1 addition & 2 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ The following options are available.
| `domain` | `window.location.hostname` | By default, the current domain name is used. Also known as "relying party id". You may want to customize it for ...
| `allowedCredentials` | The list of credentials and the transports it supports. Used to skip passkey selection. Either a list of credential ids (discouraged) or list of credential objects with `id` and supported `transports` (recommended).
| `autocomplete` | `false` | See concepts


| `customProperties` | `{}` | An object of additional properties that will be merged into the WebAuthn authenticate options. This can be used to explicitly set fields such as `extensions`.


3️⃣ Send the payload to the server
Expand Down
2 changes: 1 addition & 1 deletion docs/registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Besides the required `user` and `challenge`, it has following options.
| `timeout` | - | How long the native authentication popup stays open before aborting the authentication process.
| `attestation` | `true` | Whether or not to provide "attestation" in the result. The attestation can be used to prove the authenticator device model's authenticity. Note that not all authenticators provide this (looking at you apple), it might be anonymized, and its verification is complex.
| `domain` | `window.location.hostname` | This can be set to a parent domain, to have the passkey valid for all subdomains.

| `customProperties` | `{}` | An object of additional properties that will be merged into the WebAuthn create options. This can be used to explicitly set fields such as `excludeCredentials`.



Expand Down
7 changes: 5 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 32 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ let ongoingAuth: AbortController | null = null;
* @param {'discouraged'|'preferred'|'required'} [discoverable] A "discoverable" credential can be selected using `authenticate(...)` without providing credential IDs.
* Instead, a native pop-up will appear for user selection.
* This may have an impact on the "passkeys" user experience and syncing behavior of the key.
* @param {Record<string, any>} [options.customProperties] - **Advanced usage**: An object of additional
* properties that will be merged into the WebAuthn create options. This can be used to
* explicitly set fields such as `excludeCredentials`.
*
* @example
* const registration = await register({
* user: { id: 'user-id', name: 'john', displayName: 'John' },
* challenge: 'base64url-encoded-challenge',
* customProperties: {
* excludeCredentials: [
* { id: 'base64url-credential-id', type: 'public-key' },
* ],
* },
* });
*/
export async function register(options: RegisterOptions): Promise<RegistrationJSON> {

Expand Down Expand Up @@ -92,7 +106,8 @@ export async function register(options: RegisterOptions): Promise<RegistrationJS
residentKey: options.discoverable ?? 'preferred', // see https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions#residentkey
requireResidentKey: (options.discoverable === 'required') // mainly for backwards compatibility, see https://www.w3.org/TR/webauthn/#dictionary-authenticatorSelection
},
attestation: "direct"
attestation: "direct",
...options.customProperties,
}

console.debug(creationOptions)
Expand Down Expand Up @@ -150,6 +165,21 @@ export async function isAutocompleteAvailable() {
* @param {number} [timeout=60000] Number of milliseconds the user has to respond to the biometric/PIN check.
* @param {'required'|'preferred'|'discouraged'} [userVerification='required'] Whether to prompt for biometric/PIN check or not.
* @param {boolean} [conditional] Does not return directly, but only when the user has selected a credential in the input field with `autocomplete="username webauthn"`
* @param {Record<string, any>} [options.customProperties] - **Advanced usage**: An object of additional
* properties that will be merged into the WebAuthn authenticate options. This can be used to
* explicitly set fields such as `extensions`.
*
* @example
* const authentication = await authenticate({
* challenge: 'base64url-encoded-challenge',
* allowCredentials: [],
* customProperties: {
* extensions: {
* uvm: true, // User verification methods extension
* appid: "https://legacy-app-id.example.com", // App ID extension for backward compatibility
* },
* },
* });
*/
export async function authenticate(options: AuthenticateOptions): Promise<AuthenticationJSON> {
if (!utils.isBase64url(options.challenge))
Expand All @@ -165,6 +195,7 @@ export async function authenticate(options: AuthenticateOptions): Promise<Authen
hints: options.hints,
userVerification: options.userVerification,
timeout: options.timeout,
...options.customProperties,
}

console.debug(authOptions)
Expand Down
131 changes: 61 additions & 70 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,66 @@
export type NamedAlgo = 'RS256' | 'EdDSA' | 'ES256'
export type NamedAlgo = "RS256" | "EdDSA" | "ES256";
export type Base64URLString = string;


/**
* The available "hints" for WebAuthn, not yet available in the official DOM types
*/
export type PublicKeyCredentialHints = "client-device" | "hybrid" | "security-key"
export type PublicKeyCredentialHints = "client-device" | "hybrid" | "security-key";

/**
* Extends the native DOM type since the "hints" are not yet included in the official version.
*/
export interface WebAuthnCreateOptions extends PublicKeyCredentialCreationOptions {
hints?: PublicKeyCredentialHints[]
hints?: PublicKeyCredentialHints[];
}

/**
* Extends the native DOM type since the "hints" are not yet included in the official version.
*/
export interface WebAuthnGetOptions extends PublicKeyCredentialRequestOptions {
hints?: PublicKeyCredentialHints[]
hints?: PublicKeyCredentialHints[];
}


/*********************** OPTIONS *************************/

export interface CommonOptions {
challenge: string
domain?: string // used for parent/subdomain auth and other exotic use cases
hints?: PublicKeyCredentialHints[]
timeout?: number
userVerification?: UserVerificationRequirement
challenge: string;
domain?: string; // used for parent/subdomain auth and other exotic use cases
hints?: PublicKeyCredentialHints[];
timeout?: number;
userVerification?: UserVerificationRequirement;
}

export interface RegisterOptions extends CommonOptions {
attestation?: boolean
discoverable?: ResidentKeyRequirement
user: string | User
attestation?: boolean;
discoverable?: ResidentKeyRequirement;
user: string | User;
customProperties?: Record<string, any>;
}


export interface User {
id?: string
name: string
displayName?: string
id?: string;
name: string;
displayName?: string;
}

/**
* @see PublicKeyCredentialDescriptor
*/
export interface CredentialDescriptor {
id: Base64URLString,
transports: ExtendedAuthenticatorTransport[]
id: Base64URLString;
transports: ExtendedAuthenticatorTransport[];
}

export interface AuthenticateOptions extends CommonOptions {
allowCredentials?: (CredentialDescriptor | string)[]
autocomplete?: boolean
allowCredentials?: (CredentialDescriptor | string)[];
autocomplete?: boolean;
customProperties?: Record<string, any>;
}



/********************************** JSON PAYLOADS **********************/

export interface RegistrationJSON extends RegistrationResponseJSON {
user: User // Added by this library, not by the WebAuthn protocol
user: User; // Added by this library, not by the WebAuthn protocol
}

export type AuthenticationJSON = AuthenticationResponseJSON;
Expand All @@ -82,7 +79,6 @@ export interface RegistrationResponseJSON {
type: PublicKeyCredentialType;
}


/**
* A slightly-modified AuthenticatorAttestationResponse to simplify working with ArrayBuffers that
* are Base64URL-encoded in the browser so that they can be sent as JSON to the server.
Expand Down Expand Up @@ -113,7 +109,6 @@ export interface AuthenticationResponseJSON {
type: PublicKeyCredentialType;
}


/**
* A slightly-modified AuthenticatorAssertionResponse to simplify working with ArrayBuffers that
* are Base64URL-encoded in the browser so that they can be sent as JSON to the server.
Expand All @@ -130,38 +125,37 @@ export interface AuthenticatorAssertionResponseJSON {
/**
* WebAuthn added transports that are not yet defined in the DOM definitions.
* However, it's partly obsoleted by the `hints` in the registration/authentication request.
*
*
* https://w3c.github.io/webauthn/#enumdef-authenticatortransport
*/
export type ExtendedAuthenticatorTransport = AuthenticatorTransport | 'smart-card'; // missing in the current DOM types

export type ExtendedAuthenticatorTransport = AuthenticatorTransport | "smart-card"; // missing in the current DOM types

/************************** PARSED **************************/

/**
* https://w3c.github.io/webauthn/#dictionary-client-data
*/
export interface CollectedClientData {
type: string
challenge: Base64URLString
origin: string
topOrigin?: string
type: string;
challenge: Base64URLString;
origin: string;
topOrigin?: string;
crossOrigin?: boolean;
}

export interface AuthenticatorParsed {
rpIdHash:Base64URLString,
rpIdHash: Base64URLString;
flags: {
userPresent: boolean,
userVerified: boolean,
backupEligibility: boolean,
backupState: boolean,
attestedData: boolean,
extensionsIncluded: boolean
},
signCount: number,
aaguid: string,
attestation?: Base64URLString
userPresent: boolean;
userVerified: boolean;
backupEligibility: boolean;
backupState: boolean;
attestedData: boolean;
extensionsIncluded: boolean;
};
signCount: number;
aaguid: string;
attestation?: Base64URLString;
}

/**
Expand All @@ -171,41 +165,38 @@ export interface AuthenticatorParsed {
/************************** RESULTS *************************/

export interface RegistrationInfo {
user: UserInfo
credential: CredentialInfo
authenticator: AuthenticatorInfo
synced: boolean
userVerified: boolean
user: UserInfo;
credential: CredentialInfo;
authenticator: AuthenticatorInfo;
synced: boolean;
userVerified: boolean;
}


export interface AuthenticationInfo {
credentialId: Base64URLString
userId?: Base64URLString
userVerified: boolean
counter: number
authenticatorAttachment?: AuthenticatorAttachment
credentialId: Base64URLString;
userId?: Base64URLString;
userVerified: boolean;
counter: number;
authenticatorAttachment?: AuthenticatorAttachment;
}


export interface UserInfo {
id: string
name: string
displayName: string
id: string;
name: string;
displayName: string;
}


export interface CredentialInfo {
id: string
publicKey: string
algorithm: NamedAlgo
transports: ExtendedAuthenticatorTransport[]
id: string;
publicKey: string;
algorithm: NamedAlgo;
transports: ExtendedAuthenticatorTransport[];
}

export interface AuthenticatorInfo {
aaguid: string
name: string
icon_light: string
icon_dark: string
counter: number
aaguid: string;
name: string;
icon_light: string;
icon_dark: string;
counter: number;
}

0 comments on commit be10451

Please sign in to comment.