@authaction/passkey-plus-sdk
Version:
A lightweight frontend SDK for passkey-based passwordless authentication with AuthAction
131 lines (109 loc) • 3.47 kB
text/typescript
import {
IPasskeyAuthenticateOptions,
IPasskeyCreationOptions,
PasskeyPlusOptions,
} from "./types";
import {
startRegistration,
startAuthentication,
} from "@simplewebauthn/browser";
export class PasskeyPlus {
private baseUrl: string;
constructor(options: PasskeyPlusOptions) {
const { tenantDomain, appId } = options;
this.baseUrl = `https://${tenantDomain}/api/v1/passkey-plus-public/${appId}`;
}
async register(
transactionID: string,
opts?: IPasskeyCreationOptions
): Promise<string> {
const publicKey = await this.getRegistrationOptions(transactionID);
if (opts?.authenticatorAttachment) {
publicKey.authenticatorSelection = {
...publicKey.authenticatorSelection,
authenticatorAttachment: opts.authenticatorAttachment,
};
}
const attestationResponse = await startRegistration({
optionsJSON: publicKey,
});
const res = await fetch(
`${this.baseUrl}/transaction/${transactionID}/register`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(attestationResponse),
}
);
const responseJson = await res.json();
const { nonce } = responseJson.data;
return nonce;
}
async authenticate(
transactionId: string,
opts?: IPasskeyAuthenticateOptions
): Promise<string> {
const publicKey = await this.getAuthenticationOptions(transactionId);
const assertionResponse = await startAuthentication({
optionsJSON: publicKey,
});
const res = await fetch(
`${this.baseUrl}/transaction/${transactionId}/authenticate`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(assertionResponse),
}
);
if (!res.ok) {
const error = await res.json();
throw new Error(
`Authentication request failed: ${error.message || res.statusText}`
);
}
const responseJson = await res.json();
const { nonce } = responseJson.data;
return nonce;
}
async canAuthenticateWithPasskey(): Promise<boolean> {
return !!window.PublicKeyCredential;
}
async canRegisterPasskey(): Promise<boolean> {
return (
!!window.PublicKeyCredential &&
typeof navigator.credentials.create === "function"
);
}
async canUseConditionalMediation(): Promise<boolean> {
return typeof window?.PublicKeyCredential
?.isConditionalMediationAvailable === "function"
? await window.PublicKeyCredential.isConditionalMediationAvailable()
: false;
}
private async getRegistrationOptions(transactionID: string) {
const res = await fetch(
`${this.baseUrl}/transaction/${transactionID}/registration-options`
);
if (!res.ok) {
const error = await res.json();
throw new Error(
`Registration options request failed: ${error.message || res.statusText}`
);
}
const responseJson = await res.json();
return responseJson.data;
}
private async getAuthenticationOptions(transactionID: string) {
const res = await fetch(
`${this.baseUrl}/transaction/${transactionID}/authentication-options`
);
if (!res.ok) {
const error = await res.json();
throw new Error(
`Authentication options request failed: ${error.message || res.statusText}`
);
}
const responseJson = await res.json();
return responseJson.data;
}
}