Update WebAuthn registration and authentication to use new and safer impl (#5)
* Update WebAuthn registration and authentication to use new and safer flow * Implement usernameless WebAuthn registration + Authentication The username is solely stored in the client side for the end-user to conveniently refer to. * Use updated SDK interface * Implement executeJs in webauthn demo * Show executeJs sig * Fix username bug * encodeURIComponent for username * Use latest working SDK version * Use serrano
This commit is contained in:
parent
efb3eab092
commit
2e19c39625
11
package.json
11
package.json
@ -5,11 +5,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.10.5",
|
"@emotion/react": "^11.10.5",
|
||||||
"@emotion/styled": "^11.10.5",
|
"@emotion/styled": "^11.10.5",
|
||||||
"@lit-protocol/access-control-conditions": "2.1.17",
|
"@lit-protocol/access-control-conditions": "2.1.111",
|
||||||
"@lit-protocol/bls-sdk": "2.1.17",
|
"@lit-protocol/bls-sdk": "2.1.111",
|
||||||
"@lit-protocol/constants": "2.1.17",
|
"@lit-protocol/constants": "2.1.111",
|
||||||
"@lit-protocol/crypto": "2.1.17",
|
"@lit-protocol/crypto": "2.1.111",
|
||||||
"@lit-protocol/lit-node-client": "2.1.17",
|
"@lit-protocol/lit-node-client": "2.1.111",
|
||||||
|
"@lit-protocol/lit-node-client-nodejs": "2.1.111",
|
||||||
"@mui/material": "^5.10.16",
|
"@mui/material": "^5.10.16",
|
||||||
"@react-oauth/google": "^0.4.0",
|
"@react-oauth/google": "^0.4.0",
|
||||||
"@simplewebauthn/browser": "^6.2.2",
|
"@simplewebauthn/browser": "^6.2.2",
|
||||||
|
449
src/App.tsx
449
src/App.tsx
@ -1,20 +1,18 @@
|
|||||||
import * as LitJsSdk_accessControlConditions from "@lit-protocol/access-control-conditions";
|
import * as LitJsSdk_accessControlConditions from "@lit-protocol/access-control-conditions";
|
||||||
import * as LitJsSdk_blsSdk from "@lit-protocol/bls-sdk";
|
import * as LitJsSdk_blsSdk from "@lit-protocol/bls-sdk";
|
||||||
import { AccsDefaultParams } from "@lit-protocol/constants";
|
|
||||||
import * as LitJsSdk from "@lit-protocol/lit-node-client";
|
import * as LitJsSdk from "@lit-protocol/lit-node-client";
|
||||||
import { Button, ButtonGroup } from "@mui/material";
|
import { AccsDefaultParams, JsonAuthSig } from "@lit-protocol/types";
|
||||||
|
import { Button, ButtonGroup, TextField } from "@mui/material";
|
||||||
import { GoogleLogin } from "@react-oauth/google";
|
import { GoogleLogin } from "@react-oauth/google";
|
||||||
import {
|
import {
|
||||||
startAuthentication,
|
startAuthentication,
|
||||||
startRegistration,
|
startRegistration,
|
||||||
} from "@simplewebauthn/browser";
|
} from "@simplewebauthn/browser";
|
||||||
import base64url from "base64url";
|
import base64url from "base64url";
|
||||||
import { utils } from "ethers";
|
import { ethers, utils } from "ethers";
|
||||||
import { hexlify } from "ethers/lib/utils";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { decodeAttestationObject } from "./utils/decodeAttestationObject";
|
import { getDomainFromOrigin } from "./utils/string";
|
||||||
import { parseAuthenticatorData } from "./utils/parseAuthenticatorData";
|
|
||||||
|
|
||||||
type CredentialResponse = any;
|
type CredentialResponse = any;
|
||||||
|
|
||||||
@ -28,21 +26,25 @@ const RELAY_API_URL =
|
|||||||
process.env.REACT_APP_RELAY_API_URL || "http://localhost:3001";
|
process.env.REACT_APP_RELAY_API_URL || "http://localhost:3001";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [pkpEthAddress, setPkpEthAddress] = useState<string>("");
|
const [registeredPkpEthAddress, setRegisteredPkpEthAddress] = useState<
|
||||||
|
string
|
||||||
|
>("");
|
||||||
const [
|
const [
|
||||||
googleCredentialResponse,
|
googleCredentialResponse,
|
||||||
setGoogleCredentialResponse,
|
setGoogleCredentialResponse,
|
||||||
] = useState<CredentialResponse | null>(null);
|
] = useState<CredentialResponse | null>(null);
|
||||||
const [pkpPublicKey, setPkpPublicKey] = useState<string>("");
|
const [registeredPkpPublicKey, setRegisteredPkpPublicKey] = useState<
|
||||||
|
string
|
||||||
|
>("");
|
||||||
|
const [authenticatedPkpPublicKey, setAuthenticatedPkpPublicKey] = useState<
|
||||||
|
string
|
||||||
|
>("");
|
||||||
const [status, setStatus] = useState("");
|
const [status, setStatus] = useState("");
|
||||||
const [selectedAuthMethod, setSelectedAuthMethod] = useState(6);
|
const [selectedAuthMethod, setSelectedAuthMethod] = useState(6);
|
||||||
const [
|
const [webAuthnUsername, setWebAuthnUsername] = useState<string>("");
|
||||||
webAuthnCredentialPublicKey,
|
const [authSig, setAuthSig] = useState<JsonAuthSig | null>(null);
|
||||||
setWebAuthnCredentialPublicKey,
|
const [executeJsSignature, setExecuteJsSignature] = useState<string | null>(
|
||||||
] = useState<string>("");
|
null
|
||||||
const [webAuthnSignature, setWebAuthnSignature] = useState<string>("");
|
|
||||||
const [webAuthnSignatureBase, setWebAuthnSignatureBase] = useState<string>(
|
|
||||||
""
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleLoggedInToGoogle = async (
|
const handleLoggedInToGoogle = async (
|
||||||
@ -61,8 +63,8 @@ function App() {
|
|||||||
requestId,
|
requestId,
|
||||||
setStatus,
|
setStatus,
|
||||||
({ pkpEthAddress, pkpPublicKey }) => {
|
({ pkpEthAddress, pkpPublicKey }) => {
|
||||||
setPkpEthAddress(pkpEthAddress);
|
setRegisteredPkpEthAddress(pkpEthAddress);
|
||||||
setPkpPublicKey(pkpPublicKey);
|
setRegisteredPkpPublicKey(pkpPublicKey);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -97,7 +99,7 @@ function App() {
|
|||||||
{selectedAuthMethod === 6 && (
|
{selectedAuthMethod === 6 && (
|
||||||
<>
|
<>
|
||||||
<h3>
|
<h3>
|
||||||
Step 1: log in with Google. Upon OAuth success, we will
|
Step 1: Log in with Google. Upon OAuth success, we will
|
||||||
mint a PKP on your behalf.
|
mint a PKP on your behalf.
|
||||||
</h3>
|
</h3>
|
||||||
<GoogleLogin
|
<GoogleLogin
|
||||||
@ -107,8 +109,11 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
useOneTap
|
useOneTap
|
||||||
/>
|
/>
|
||||||
{pkpEthAddress && (
|
{registeredPkpEthAddress && (
|
||||||
<div>PKP Eth Address: {pkpEthAddress}</div>
|
<div>
|
||||||
|
Registered PKP Eth Address:{" "}
|
||||||
|
{registeredPkpEthAddress}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<h3>
|
<h3>
|
||||||
<s>
|
<s>
|
||||||
@ -128,8 +133,8 @@ function App() {
|
|||||||
signatureBase: "dummy",
|
signatureBase: "dummy",
|
||||||
credentialPublicKey: "dummy",
|
credentialPublicKey: "dummy",
|
||||||
},
|
},
|
||||||
pkpEthAddress,
|
registeredPkpEthAddress,
|
||||||
pkpPublicKey
|
registeredPkpPublicKey
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -139,93 +144,88 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
{selectedAuthMethod === 3 && (
|
{selectedAuthMethod === 3 && (
|
||||||
<>
|
<>
|
||||||
<h3>Step 1: Register using WebAuthn.</h3>
|
<h3>Step 1: Register to mint PKP. (optional username)</h3>
|
||||||
|
<TextField
|
||||||
|
label="Username"
|
||||||
|
variant="outlined"
|
||||||
|
onChange={e => setWebAuthnUsername(e.target.value)}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleWebAuthnRegister(
|
await handleWebAuthnRegister(
|
||||||
|
webAuthnUsername,
|
||||||
setStatus,
|
setStatus,
|
||||||
({ attResp }) => {
|
({ pkpEthAddress, pkpPublicKey }) => {
|
||||||
const attestationObject = base64url.toBuffer(
|
setRegisteredPkpEthAddress(pkpEthAddress);
|
||||||
attResp.response.attestationObject
|
setRegisteredPkpPublicKey(pkpPublicKey);
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
authData,
|
|
||||||
} = decodeAttestationObject(
|
|
||||||
window.cbor,
|
|
||||||
attestationObject
|
|
||||||
);
|
|
||||||
|
|
||||||
const parsedAuthData = parseAuthenticatorData(
|
|
||||||
window.cbor,
|
|
||||||
authData
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"storing credential public key in browser",
|
|
||||||
{
|
|
||||||
credentialPublicKey:
|
|
||||||
parsedAuthData.credentialPublicKey,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// set in local state
|
|
||||||
setWebAuthnCredentialPublicKey(
|
|
||||||
hexlify(
|
|
||||||
parsedAuthData.credentialPublicKey!
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Register
|
Register
|
||||||
</Button>
|
</Button>
|
||||||
<h3>Step 2: Authenticate using WebAuthn to mint PKP.</h3>
|
{registeredPkpEthAddress && (
|
||||||
|
<div>
|
||||||
|
<b>Registered PKP Eth Address: </b>
|
||||||
|
{registeredPkpEthAddress}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<h3>
|
||||||
|
Step 2: Authenticate against Lit Nodes to generate auth
|
||||||
|
sigs.
|
||||||
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handleWebAuthnAuthenticate(
|
const {
|
||||||
setStatus,
|
authSig,
|
||||||
webAuthnCredentialPublicKey,
|
pkpPublicKey,
|
||||||
({ pkpEthAddress, pkpPublicKey }) => {
|
} = await handleWebAuthnAuthenticate(setStatus);
|
||||||
setPkpEthAddress(pkpEthAddress);
|
setAuthSig(authSig);
|
||||||
setPkpPublicKey(pkpPublicKey);
|
|
||||||
},
|
// After authenticating, we can store the pkpPublicKey for executing a
|
||||||
setWebAuthnSignature,
|
// Lit Action later.
|
||||||
setWebAuthnSignatureBase
|
setAuthenticatedPkpPublicKey(pkpPublicKey);
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Authenticate
|
Authenticate
|
||||||
</Button>
|
</Button>
|
||||||
|
{authenticatedPkpPublicKey && authSig && (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<b>Authenticated PKP Public Key: </b>
|
||||||
|
{authenticatedPkpPublicKey}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b>Auth Sig: </b>
|
||||||
|
{JSON.stringify(authSig)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<h3>
|
<h3>
|
||||||
<s>
|
Step 3: Generate session signatures and use them to
|
||||||
Step 3: Use Lit Network to obtain a session sig and
|
execute a Lit Action.
|
||||||
then store an encryption condition.
|
|
||||||
</s>
|
|
||||||
(Session Sigs do not work currently.)
|
|
||||||
</h3>
|
</h3>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() =>
|
onClick={async () => {
|
||||||
handleStoreEncryptionCondition(
|
const signature = await handleExecuteJs(
|
||||||
setStatus,
|
setStatus,
|
||||||
selectedAuthMethod,
|
authSig!,
|
||||||
googleCredentialResponse,
|
authenticatedPkpPublicKey
|
||||||
{
|
);
|
||||||
signature: webAuthnSignature,
|
setExecuteJsSignature(signature);
|
||||||
signatureBase: webAuthnSignatureBase,
|
}}
|
||||||
credentialPublicKey: webAuthnCredentialPublicKey,
|
|
||||||
},
|
|
||||||
pkpEthAddress,
|
|
||||||
pkpPublicKey
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Encrypt With Lit
|
Execute Lit Action
|
||||||
</Button>
|
</Button>
|
||||||
|
{executeJsSignature && (
|
||||||
|
<div>
|
||||||
|
<b>Executed Lit Action Signature: </b>
|
||||||
|
{executeJsSignature}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -234,6 +234,44 @@ function App() {
|
|||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
|
async function handleExecuteJs(
|
||||||
|
setStatusFn: (status: string) => void,
|
||||||
|
authSig: JsonAuthSig,
|
||||||
|
pkpPublicKey: string
|
||||||
|
): Promise<string> {
|
||||||
|
setStatusFn("Executing JS...");
|
||||||
|
const litActionCode = `
|
||||||
|
const go = async () => {
|
||||||
|
// this requests a signature share from the Lit Node
|
||||||
|
// the signature share will be automatically returned in the response from the node
|
||||||
|
// and combined into a full signature by the LitJsSdk for you to use on the client
|
||||||
|
// all the params (toSign, publicKey, sigName) are passed in from the LitJsSdk.executeJs() function
|
||||||
|
const sigShare = await LitActions.signEcdsa({ toSign, publicKey, sigName });
|
||||||
|
};
|
||||||
|
|
||||||
|
go();
|
||||||
|
`;
|
||||||
|
const litNodeClient = new LitJsSdk.LitNodeClient({
|
||||||
|
litNetwork: "serrano",
|
||||||
|
});
|
||||||
|
await litNodeClient.connect();
|
||||||
|
|
||||||
|
const results = await litNodeClient.executeJs({
|
||||||
|
code: litActionCode,
|
||||||
|
authSig,
|
||||||
|
// all jsParams can be used anywhere in your litActionCode
|
||||||
|
jsParams: {
|
||||||
|
// this is the string "Hello World" for testing
|
||||||
|
toSign: [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100],
|
||||||
|
publicKey: `0x${pkpPublicKey}`,
|
||||||
|
sigName: "sig1",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log("results: ", results);
|
||||||
|
|
||||||
|
return results.signatures["sig1"].signature;
|
||||||
|
}
|
||||||
|
|
||||||
async function mintPkpUsingRelayerGoogleAuthVerificationEndpoint(
|
async function mintPkpUsingRelayerGoogleAuthVerificationEndpoint(
|
||||||
credentialResponse: any,
|
credentialResponse: any,
|
||||||
setStatusFn: (status: string) => void
|
setStatusFn: (status: string) => void
|
||||||
@ -244,6 +282,7 @@ async function mintPkpUsingRelayerGoogleAuthVerificationEndpoint(
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
"api-key": "1234567890",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
idToken: credentialResponse.credential,
|
idToken: credentialResponse.credential,
|
||||||
@ -262,38 +301,6 @@ async function mintPkpUsingRelayerGoogleAuthVerificationEndpoint(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mintPkpUsingRelayerWebAuthnVerificationEndpoint(
|
|
||||||
signature: string,
|
|
||||||
signatureBase: string,
|
|
||||||
credentialPublicKey: string,
|
|
||||||
setStatusFn: (status: string) => void
|
|
||||||
) {
|
|
||||||
setStatusFn("Minting PKP with relayer...");
|
|
||||||
|
|
||||||
const mintRes = await fetch(`${RELAY_API_URL}/auth/webauthn`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
signature,
|
|
||||||
signatureBase,
|
|
||||||
credentialPublicKey,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (mintRes.status < 200 || mintRes.status >= 400) {
|
|
||||||
console.warn("Something wrong with the API call", await mintRes.json());
|
|
||||||
setStatusFn("Uh oh, something's not quite right.");
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
const resBody = await mintRes.json();
|
|
||||||
console.log("Response OK", { body: resBody });
|
|
||||||
setStatusFn("Successfully initiated minting PKP with relayer.");
|
|
||||||
return resBody.requestId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pollRequestUntilTerminalState(
|
async function pollRequestUntilTerminalState(
|
||||||
requestId: string,
|
requestId: string,
|
||||||
setStatusFn: (status: string) => void,
|
setStatusFn: (status: string) => void,
|
||||||
@ -313,7 +320,12 @@ async function pollRequestUntilTerminalState(
|
|||||||
for (let i = 0; i < maxPollCount; i++) {
|
for (let i = 0; i < maxPollCount; i++) {
|
||||||
setStatusFn(`Waiting for auth completion (poll #${i + 1})`);
|
setStatusFn(`Waiting for auth completion (poll #${i + 1})`);
|
||||||
const getAuthStatusRes = await fetch(
|
const getAuthStatusRes = await fetch(
|
||||||
`${RELAY_API_URL}/auth/status/${requestId}`
|
`${RELAY_API_URL}/auth/status/${requestId}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"api-key": "1234567890",
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (getAuthStatusRes.status < 200 || getAuthStatusRes.status >= 400) {
|
if (getAuthStatusRes.status < 200 || getAuthStatusRes.status >= 400) {
|
||||||
@ -482,6 +494,7 @@ async function handleStoreEncryptionCondition(
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
"api-key": "1234567890",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
key: hashedEncryptedSymmetricKeyStr,
|
key: hashedEncryptedSymmetricKeyStr,
|
||||||
@ -611,10 +624,24 @@ async function hashBytes({ bytes }: { bytes: Uint8Array }): Promise<string> {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
async function handleWebAuthnRegister(
|
async function handleWebAuthnRegister(
|
||||||
|
username: string,
|
||||||
setStatusFn: (status: string) => void,
|
setStatusFn: (status: string) => void,
|
||||||
onSuccess: ({ attResp }: { attResp: any }) => void
|
onSuccess: ({
|
||||||
|
pkpEthAddress,
|
||||||
|
pkpPublicKey,
|
||||||
|
}: {
|
||||||
|
pkpEthAddress: string;
|
||||||
|
pkpPublicKey: string;
|
||||||
|
}) => void
|
||||||
) {
|
) {
|
||||||
const resp = await fetch(`${RELAY_API_URL}/generate-registration-options`);
|
let url = `${RELAY_API_URL}/auth/webauthn/generate-registration-options`;
|
||||||
|
|
||||||
|
// Handle optional username
|
||||||
|
if (username !== "") {
|
||||||
|
url += `?username=${encodeURIComponent(username)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await fetch(url, { headers: { "api-key": "1234567890" } });
|
||||||
|
|
||||||
let attResp;
|
let attResp;
|
||||||
try {
|
try {
|
||||||
@ -635,110 +662,110 @@ async function handleWebAuthnRegister(
|
|||||||
|
|
||||||
console.log("attResp", { attResp });
|
console.log("attResp", { attResp });
|
||||||
|
|
||||||
const verificationResp = await fetch(
|
// Verify and mint PKP.
|
||||||
`${RELAY_API_URL}/verify-registration`,
|
setStatusFn("Verifying WebAuthn registration...");
|
||||||
|
const verificationAndMintResp = await fetch(
|
||||||
|
`${RELAY_API_URL}/auth/webauthn/verify-registration`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
"api-key": "1234567890",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(attResp),
|
body: JSON.stringify({ credential: attResp }),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const verificationJSON = await verificationResp.json();
|
if (
|
||||||
|
verificationAndMintResp.status < 200 ||
|
||||||
if (verificationJSON && verificationJSON.verified) {
|
verificationAndMintResp.status >= 400
|
||||||
setStatusFn("Successfully registered using WebAuthn!");
|
) {
|
||||||
onSuccess({ attResp });
|
console.warn(
|
||||||
} else {
|
"Something went wrong with the API call",
|
||||||
setStatusFn(
|
await verificationAndMintResp.json()
|
||||||
"Oh no, something went wrong during WebAuthn registration."
|
|
||||||
);
|
);
|
||||||
console.error("Error during WebAuthn registration", {
|
setStatusFn("Uh oh, something's not quite right.");
|
||||||
err: JSON.stringify(verificationJSON),
|
return null;
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleWebAuthnAuthenticate(
|
|
||||||
setStatusFn: (status: string) => void,
|
|
||||||
webAuthnCredentialPublicKey: string,
|
|
||||||
onSuccess: (resp: any) => void,
|
|
||||||
setWebAuthnSignatureFn: (signature: string) => void,
|
|
||||||
setWebAuthnSignatureBaseFn: (signatureBase: string) => void
|
|
||||||
) {
|
|
||||||
const resp = await fetch(
|
|
||||||
`${RELAY_API_URL}/generate-authentication-options`
|
|
||||||
);
|
|
||||||
|
|
||||||
let asseResp;
|
|
||||||
try {
|
|
||||||
const opts = await resp.json();
|
|
||||||
|
|
||||||
asseResp = await startAuthentication(opts);
|
|
||||||
} catch (error) {
|
|
||||||
// TODO: handle error
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const verificationResp = await fetch(
|
const resBody = await verificationAndMintResp.json();
|
||||||
`${RELAY_API_URL}/verify-authentication`,
|
console.log("Response OK", { body: resBody });
|
||||||
{
|
setStatusFn(
|
||||||
method: "POST",
|
"Successfully registered using WebAuthn! PKP minting initiated..."
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify(asseResp),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const verificationJSON = await verificationResp.json();
|
|
||||||
|
|
||||||
if (verificationJSON && verificationJSON.verified) {
|
|
||||||
setStatusFn("Successfully authenticated using WebAuthn!");
|
|
||||||
} else {
|
|
||||||
setStatusFn(
|
|
||||||
"Oh no, something went wrong during WebAuthn authentication."
|
|
||||||
);
|
|
||||||
console.error("Error during WebAuthn authentication", {
|
|
||||||
err: JSON.stringify(verificationJSON),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientDataHash = await crypto.subtle.digest(
|
|
||||||
"SHA-256",
|
|
||||||
base64url.toBuffer(asseResp.response.clientDataJSON)
|
|
||||||
);
|
|
||||||
|
|
||||||
const authDataBuffer = base64url.toBuffer(
|
|
||||||
asseResp.response.authenticatorData
|
|
||||||
);
|
|
||||||
|
|
||||||
const signatureBase = Buffer.concat([
|
|
||||||
authDataBuffer,
|
|
||||||
Buffer.from(clientDataHash),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const signature = base64url.toBuffer(asseResp.response.signature);
|
|
||||||
|
|
||||||
// mint PKP using Relayer
|
|
||||||
console.log("Minting PKP using Relayer...", {
|
|
||||||
signature: hexlify(signature),
|
|
||||||
signatureBase: hexlify(signatureBase),
|
|
||||||
webAuthnCredentialPublicKey,
|
|
||||||
});
|
|
||||||
const requestId = await mintPkpUsingRelayerWebAuthnVerificationEndpoint(
|
|
||||||
hexlify(signature),
|
|
||||||
hexlify(signatureBase),
|
|
||||||
webAuthnCredentialPublicKey,
|
|
||||||
setStatusFn
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Poll until success
|
// Poll until success
|
||||||
await pollRequestUntilTerminalState(requestId, setStatusFn, onSuccess);
|
const mintRequestId = resBody.requestId;
|
||||||
|
await pollRequestUntilTerminalState(mintRequestId, setStatusFn, onSuccess);
|
||||||
// Update state
|
}
|
||||||
setWebAuthnSignatureFn(hexlify(signature));
|
|
||||||
setWebAuthnSignatureBaseFn(hexlify(signatureBase));
|
const rpcUrl = process.env.REACT_APP_RPC_URL || "http://localhost:8545";
|
||||||
|
|
||||||
|
async function handleWebAuthnAuthenticate(
|
||||||
|
setStatusFn: (status: string) => void
|
||||||
|
): Promise<{
|
||||||
|
authSig: JsonAuthSig;
|
||||||
|
pkpPublicKey: string;
|
||||||
|
}> {
|
||||||
|
// Fetch latest blockHash
|
||||||
|
setStatusFn("Fetching latest block hash...");
|
||||||
|
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
|
||||||
|
|
||||||
|
const block = await provider.getBlock("latest");
|
||||||
|
const blockHash = block.hash;
|
||||||
|
|
||||||
|
// Turn into byte array.
|
||||||
|
const blockHashBytes = ethers.utils.arrayify(blockHash);
|
||||||
|
console.log(
|
||||||
|
"blockHash",
|
||||||
|
blockHash,
|
||||||
|
blockHashBytes,
|
||||||
|
base64url(Buffer.from(blockHashBytes))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Construct authentication options.
|
||||||
|
const rpId = getDomainFromOrigin(window.location.origin);
|
||||||
|
console.log("Using rpId: ", { rpId });
|
||||||
|
const authenticationOptions = {
|
||||||
|
challenge: base64url(Buffer.from(blockHashBytes)),
|
||||||
|
timeout: 60000,
|
||||||
|
userVerification: "required",
|
||||||
|
rpId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Authenticate with WebAuthn.
|
||||||
|
setStatusFn("Authenticating with WebAuthn...");
|
||||||
|
const authenticationResponse = await startAuthentication(
|
||||||
|
authenticationOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
// BUG: We need to make sure userHandle is base64url encoded.
|
||||||
|
// Deep copy the authentication response.
|
||||||
|
const actualAuthenticationResponse = JSON.parse(
|
||||||
|
JSON.stringify(authenticationResponse)
|
||||||
|
);
|
||||||
|
actualAuthenticationResponse.response.userHandle = base64url.encode(
|
||||||
|
authenticationResponse.response.userHandle
|
||||||
|
);
|
||||||
|
|
||||||
|
// Call all nodes POST /web/auth/webauthn to generate authSig.
|
||||||
|
setStatusFn("Verifying WebAuthn authentication against Lit Network...");
|
||||||
|
const litNodeClient = new LitJsSdk.LitNodeClient({
|
||||||
|
litNetwork: "serrano",
|
||||||
|
});
|
||||||
|
await litNodeClient.connect();
|
||||||
|
|
||||||
|
// Generate authMethod.
|
||||||
|
const authMethod = litNodeClient.generateAuthMethodForWebAuthn(
|
||||||
|
actualAuthenticationResponse
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get authSig.
|
||||||
|
const { authSig, pkpPublicKey } = await litNodeClient.signSessionKey({
|
||||||
|
authMethods: [authMethod],
|
||||||
|
expiration: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
|
||||||
|
resources: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { authSig, pkpPublicKey };
|
||||||
}
|
}
|
||||||
|
6
src/utils/string.ts
Normal file
6
src/utils/string.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function getDomainFromOrigin(origin: string): string {
|
||||||
|
// remove protocol with regex
|
||||||
|
let newOrigin = origin.replace(/(^\w+:|^)\/\//, "");
|
||||||
|
// remove port with regex
|
||||||
|
return newOrigin.replace(/:\d+$/, "");
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user