Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add isAvailableOnDevice util #53

Merged
merged 2 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add isAvailableOnDevice util
  • Loading branch information
hwhmeikle committed Mar 1, 2024
commit b0c642b07f36d9f54b879b6509cbb291cbb2a053
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 (e) {
return false;
}
}

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

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