-
-
Save getify/b7395cdf0d2c5d3c0dd6685b5fae406d to your computer and use it in GitHub Desktop.
slide code for "Past Time For Passkeys" talk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let { | |
name: USER_NAME, | |
displayName: USER_DISPLAY_NAME, | |
id: USER_ID, | |
regChallenge: USER_REG_CHALLENGE | |
} = await ( | |
fetch("/api/register-passkey",{ | |
// .. | |
}) | |
.then(resp => resp.json()) | |
); | |
USER_ID = new Uint8Array(USER_ID); | |
USER_REG_CHALLENGE = new Uint8Array(USER_REG_CHALLENGE); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let authResult = await navigator.credentials.get(/* .. */); | |
let authClientData = JSON.parse( | |
(new TextDecoder("utf-8",{ fatal: true })) | |
.decode( | |
authResult.response.clientDataJSON | |
) | |
); | |
if (authClientData.type != "webauthn.get") { | |
throw new Error("Invalid auth response"); | |
} | |
let credID = authResult.id; | |
let signature = new Uint8Array(authResult.response.signature); | |
let authenticatorData = new Uint8Array( | |
authResult.response.authenticatorData | |
); | |
let clientData = new Uint8Array( | |
authResult.response.clientDataJSON | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let { success } = await ( | |
fetch("/api/verify-passkey",{ | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json" | |
}, | |
body: JSON.stringify({ | |
credentialID: credID, | |
// JSON serialize typed arrays as simple arrays | |
signature: [ ...signature ], | |
authenticatorData: [ ...authenticatorData ], | |
clientData: [ ...clientData ] | |
}) | |
}) | |
.then(resp => resp.json()) | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async function handleRequest(requ,resp) { | |
// if (requ.url == "/api/register-passkey") { .. } | |
// else if (requ.url == "/api/save-passkey") { .. } | |
// else if (requ.url == "/api/auth-passkey") { .. } | |
else if (requ.url == "/api/verify-passkey") { | |
let { userID } = await lookupCurrentUser(requ); | |
let { | |
credentialID, | |
signature, | |
authenticatorData, | |
clientData | |
} = JSON.parse(await getStream(requ)); | |
let isVerified = await verifyUserPasskey( | |
userID, | |
credentialID, | |
new Uint8Array(signature), | |
new Uint8Array(authenticatorData), | |
new Uint8Array(clientData) | |
); | |
resp.writeHead(200,{ "Content-Type": "application/json" }); | |
resp.end(JSON.stringify({ success: isVerified }); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let crypto = require("node:crypto").webcrypto; | |
async function verifyUserPasskey( | |
userID, | |
credentialID, | |
signature, | |
authenticatorData, | |
clientData | |
) { | |
let { algo, pubkey } = await lookupUserPasskey(userID,credentialID); | |
let clientDataHash = new Uint8Array( | |
await crypto.subtle.digest("SHA-256",clientData) | |
); | |
var verificationData = new Uint8Array( | |
authenticatorData.byteLength + | |
clientDataHash.byteLength | |
); | |
verificationData.set(authenticatorData,0); | |
verificationData.set(clientDataHash,authenticatorData.byteLength); | |
if (algo == -7) { | |
// // ES256 / ECDSA (P-256)? signature comes back | |
// ASN.1 encoded, so decode it | |
let der = ASN1.parseVerbose(signature); | |
signature = new Uint8Array([ | |
...der.children[0].value, | |
...der.children[1].value, | |
]); | |
} | |
let cipherOpts = ( | |
algo == -257 ? | |
{ | |
name: "RSASSA-PKCS1-v1_5", | |
hash: { name: "SHA-256", }, | |
} : | |
algo == -7 ? | |
{ | |
name: "ECDSA", | |
namedCurve: "P-256", | |
hash: { name: "SHA-256", }, | |
} : | |
/* .. */ | |
); | |
let pubKeySubtle = await crypto.subtle.importKey( | |
"spki", // Simple Public Key Infrastructure rfc2692 | |
pubkey, | |
cipherOpts, | |
false, // extractable | |
[ "verify", ] | |
); | |
return crypto.subtle.verify( | |
cipherOpts, | |
pubKeySubtle, | |
signature, | |
verificationData | |
); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
regDefaults, authDefaults, | |
register, auth | |
} from "@lo-fi/webauthn-local-client"; | |
var regResult = await register( | |
regDefaults({ | |
relyingPartyName: "My Cool Web Site", | |
user: { | |
name: USER_NAME, | |
displayName: USER_DISPLAY_NAME, | |
id: USER_ID | |
}, | |
challenge: | |
}) | |
); | |
var authResult = await auth( | |
authDefaults({ | |
challenge: USER_AUTH_CHALLENGE | |
}) | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let crypto = require("node:crypto").webcrypto; | |
async function handleRequest(requ,resp) { | |
if (requ.url == "/api/register-passkey") { | |
// get current user info | |
let { | |
userID, | |
name, | |
displayName | |
} = await lookupCurrentUser(requ); | |
// generate registration challenge (random bytes) | |
let regChallenge = new Uint8Array(16); | |
crypto.getRandomValues(regChallenge); | |
resp.writeHead(200,{ "Content-Type": "application/json" }); | |
resp.end(JSON.stringify({ | |
name, | |
displayName, | |
// JSON serialize typed arrays as simple arrays | |
userID: [ ...userID ], | |
regChallenge: [ ...regChallenge ] | |
})); | |
} | |
// else if .. | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let regResult = await navigator.credentials.create({ | |
publicKey: { | |
attestation: "none", // unless you're security paranoid | |
rp: { // rp = relying party | |
id: document.location.hostname, | |
name: "My Cool Web Site" | |
}, | |
user: { | |
name: USER_NAME, | |
displayName: USER_DISPLAY_NAME, | |
id: USER_ID | |
}, | |
challenge: USER_REG_CHALLENGE, | |
pubKeyCredParams: [ | |
{ | |
type: "public-key", | |
alg: -257 // RSASSA-PKCS1-v1_5 | |
}, | |
{ | |
type: "public-key", | |
alg: -7 // ES256 / ECDSA (P-256) | |
} | |
] | |
} | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let regResult = await navigator.credentials.create(/* .. */); | |
let regClientData = JSON.parse( | |
(new TextDecoder("utf-8",{ fatal: true })) | |
.decode( | |
regResult.response.clientDataJSON | |
) | |
); | |
if (regClientData.type != "webauthn.create") { | |
throw new Error("Invalid registration response"); | |
} | |
let credID = regResult.id; | |
let publicKeyAlgoCOSE = regResult.response.getPublicKeyAlgorithm(); | |
let publicKeySPKI = new Uint8Array(regResult.response.getPublicKey()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let { success } = await ( | |
fetch("/api/save-passkey",{ | |
method: "POST", | |
headers: { | |
"Content-Type": "application/json" | |
}, | |
body: JSON.stringify({ | |
credentialID: credID, | |
algo: publicKeyAlgoCOSE, | |
// JSON serialize typed arrays as simple arrays | |
pubkey: [ ...publicKeySPKI ] | |
}) | |
}) | |
.then(resp => resp.json()) | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let getStream = require("get-stream"); | |
async function handleRequest(requ,resp) { | |
// if (requ.url == "/api/register-passkey") { .. } | |
else if (requ.url == "/api/save-passkey") { | |
let { userID } = await lookupCurrentUser(requ); | |
let { | |
credentialID, | |
algo, | |
pubkey | |
} = JSON.parse(await getStream(requ)); | |
await saveUserPasskey( | |
userID, | |
credentialID, | |
algo, | |
new Uint8Array(pubkey) | |
); | |
resp.writeHead(200,{ "Content-Type": "application/json" }); | |
resp.end(JSON.stringify({ success: true }); | |
} | |
// else if .. | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let { | |
authChallenge: USER_AUTH_CHALLENGE | |
} = await ( | |
fetch("/api/auth-passkey",{ | |
// .. | |
}) | |
.then(resp => resp.json()) | |
); | |
USER_AUTH_CHALLENGE = new Uint8Array(USER_AUTH_CHALLENGE); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
async function handleRequest(requ,resp) { | |
// if (requ.url == "/api/register-passkey") { .. } | |
// else if (requ.url == "/api/save-passkey") { .. } | |
else if (requ.url == "/api/auth-passkey") { | |
let { userID } = await lookupCurrentUser(requ); | |
// generate authentication challenge (random bytes) | |
let authChallenge = new Uint8Array(16); | |
crypto.getRandomValues(authChallenge); | |
await savePasskeyAuthChallenge(userID,authChallenge); | |
resp.writeHead(200,{ "Content-Type": "application/json" }); | |
resp.end(JSON.stringify({ | |
// JSON serialize typed arrays as simple arrays | |
authChallenge: [ ...authChallenge ] | |
})); | |
} | |
// else if .. | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let authResult = await navigator.credentials.get({ | |
publicKey: { | |
rpId: document.location.hostname, | |
challenge: USER_AUTH_CHALLENGE | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment