Skip to content

Commit

Permalink
Add isAvailableOnDevice util (#53)
Browse files Browse the repository at this point in the history
* Add `isAvailableOnDevice` util

* wip
  • Loading branch information
hwhmeikle committed Mar 1, 2024
1 parent dec4c6c commit 8209710
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 8 deletions.
30 changes: 22 additions & 8 deletions src/api/passkey-api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AddAuthenticatorResponse,
AuthenticationOptsRequest,
AuthenticationOptsResponse,
PasskeyAuthenticatorResponse,
RegistrationOptsRequest,
RegistrationOptsResponse,
VerifyRequest,
Expand All @@ -24,43 +25,56 @@ export class PasskeyApiClient {
}

async registrationOptions({token, userName}: RegistrationOptsRequest): Promise<RegistrationOptsResponse> {
const request = fetch(`${this.baseUrl}/client/user-authenticators/passkey/registration-options`, {
const response = fetch(`${this.baseUrl}/client/user-authenticators/passkey/registration-options`, {
method: "POST",
headers: this.buildHeaders(token),
body: JSON.stringify({username: userName}),
});

return (await request).json();
return (await response).json();
}

async authenticationOptions({token}: AuthenticationOptsRequest): Promise<AuthenticationOptsResponse> {
const request = fetch(`${this.baseUrl}/client/user-authenticators/passkey/authentication-options`, {
const response = fetch(`${this.baseUrl}/client/user-authenticators/passkey/authentication-options`, {
method: "POST",
headers: this.buildHeaders(token),
body: JSON.stringify({}),
});

return (await request).json();
return (await response).json();
}

async addAuthenticator({token, ...rest}: AddAuthenticatorRequest): Promise<AddAuthenticatorResponse> {
const request = fetch(`${this.baseUrl}/client/user-authenticators/passkey`, {
const response = fetch(`${this.baseUrl}/client/user-authenticators/passkey`, {
method: "POST",
headers: this.buildHeaders(token),
body: JSON.stringify(rest),
});

return (await request).json();
return (await response).json();
}

async verify({token, ...rest}: VerifyRequest): Promise<VerifyResponse> {
const request = fetch(`${this.baseUrl}/client/verify/passkey`, {
const response = fetch(`${this.baseUrl}/client/verify/passkey`, {
method: "POST",
headers: this.buildHeaders(token),
body: JSON.stringify(rest),
});

return (await request).json();
return (await response).json();
}

async getPasskeyAuthenticator(credentialId: string): Promise<PasskeyAuthenticatorResponse> {
const response = await fetch(`${this.baseUrl}/client/user-authenticators/passkey?credentialId=${credentialId}`, {
method: "GET",
headers: this.buildHeaders(),
});

if (!response.ok) {
throw new Error(response.statusText);
}

return response.json();
}

private buildHeaders(token?: string) {
Expand Down
5 changes: 5 additions & 0 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ export type VerifyResponse = {
isVerified: boolean;
accessToken?: string;
};

export type PasskeyAuthenticatorResponse = {
credentialId: string;
verifiedAt: string;
};
37 changes: 37 additions & 0 deletions src/passkey.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {startAuthentication, startRegistration} from "@simplewebauthn/browser";

import {PasskeyApiClient} from "./api";
import {AuthenticationResponseJSON, RegistrationResponseJSON} from "@simplewebauthn/types";

type PasskeyOptions = {
baseUrl: string;
Expand All @@ -14,6 +15,7 @@ type SignUpParams = {

export class Passkey {
public api: PasskeyApiClient;
private passkeyLocalStorageKey = "as_passkey_credential_id";

constructor({baseUrl, tenantId}: PasskeyOptions) {
this.api = new PasskeyApiClient({baseUrl, tenantId});
Expand All @@ -30,6 +32,10 @@ export class Passkey {
token,
});

if (addAuthenticatorResponse?.isVerified) {
this.storeCredentialAgainstDevice(registrationResponse);
}

return addAuthenticatorResponse?.accessToken;
}

Expand All @@ -51,6 +57,37 @@ export class Passkey {
token: params?.token,
});

if (verifyResponse?.isVerified) {
this.storeCredentialAgainstDevice(authenticationResponse);
}

return verifyResponse?.accessToken;
}

async isAvailableOnDevice() {
const credentialId = localStorage.getItem(this.passkeyLocalStorageKey);

if (!credentialId) {
return false;
}

try {
await this.api.getPasskeyAuthenticator(credentialId);

return true;
} catch {
return false;
}
}

private storeCredentialAgainstDevice({
id,
authenticatorAttachment,
}: AuthenticationResponseJSON | RegistrationResponseJSON) {
if (authenticatorAttachment === "cross-platform") {
return;
}

localStorage.setItem(this.passkeyLocalStorageKey, id);
}
}

0 comments on commit 8209710

Please sign in to comment.