EDIT 2
Just to ellaborate on what @Tim has already explained, I made these changes to the credential.create Authenticator Selection Options: -
authenticatorSelection: {
//This defaults to false but I specified it anyway
requireResidentKey: false,
//This defaults to "preferred" and gives a warning in Chrome if not specified.
//Not sure if it has any functional impact
userVerification: "discouraged",
//This was required to get it to work
authenticatorAttachment: "cross-platform"
},
Apart from the first time it still prompting for USB dongle, it works like a dream!
It even turns on BlueTooth if you've forgotten. Wow! (How does it know my phone in this case? 'Cos I'm logged on with the same Chrome account PC & Phone? Some registry?)
Anyway this functionality is just the mutt's nuts! I have tried to drag sites in Perth Western Australia kicking and screaming to FIDO2 but this has to be the clincher.
Well done to all involved!
EDIT 2 END
Edit Start
If you are just trying to test the new FIDO cross device authentication flow using your phone,
Yes that is exactly what I'm trying to achieve. I have now rolled back to just: -
But all the bellow code gives me is the option to enter my PIN no "Add a new Android phone"
"You are mixing a few different things here. The security key, local platform authenticator (Windows Hello) and your phone will all have their own credential."
I'm sure you're correct. I was just pulling all the leavers I knew trying to make it work:- Windows Phone Link, Acccount.Live.Com account options, etc
do not require a resident credential (not yet supported) and do not set an attachment preference.
Not sure what that means. If you mean the USB key then fine I don't want to use it, but I was getting prompted for it. (See below)
When prompted in Chrome, add your phone to link it, scan the QR on your phone and then perform the UV gesture.
Ok, what QR code reader are you using?
My problem is Chrome is not prompting me to add a phone :-( There's a configuration or API argument I'm missing?
Please help.
Edit End
I understand the "available for developers later this year" caveat, but as a FIDO devotee I have been very excited about the previewed functionality from Passkeys 9:00+. My appetite has been further whetted by the latent support in the current version of Chrome/Samsung/Windows!
TL;DR
So while I willing acknowledge/concede this is "emerging" technology: -
Please see source code below.
const utf8Decoder = new TextDecoder('utf-8');
async function verifyCredential() {
var keyResult = await getKey();
var serverChallenge = JSON.parse(keyResult);
var credentialId = localStorage.getItem("credentialId");
if (!credentialId) {
throw new Error("You must create a Credential first");
}
var allowCredentials = [{
type: "public-key",
id: Uint8Array.from(atob(credentialId), x => x.charCodeAt(0)).buffer
}]
var getAssertionOptions = {
timeout: 30000,
challenge: Uint8Array.from(serverChallenge.Token, c => c.charCodeAt(0)).buffer,
allowCredentials: allowCredentials,
userVerification: "required"
};
return navigator.credentials.get({
publicKey: getAssertionOptions
}).then(rawAssertion => {
var assertion = {
id: base64encode(rawAssertion.rawId),
clientDataJSON: utf8Decoder.decode(rawAssertion.response.clientDataJSON),
userHandle: base64encode(rawAssertion.response.userHandle),
signature: base64encode(rawAssertion.response.signature),
authenticatorData: base64encode(rawAssertion.response.authenticatorData)
};
// Check id = allowcredentials.id
console.log("=== Assertion response ===");
console.log(assertion);
verifyAssertion(assertion).then(
result => {
var res = JSON.parse(result);
console.log(res.success);
if (res.success) {
}
});
return;
}).catch(
(err) => {
if (err.name == "NotAllowedError") {
console.log("here " + err.name);
} else {
console.log("other " + err.name);
}
return Promise.resolve(false);
});
}
async function createCredential() {
var keyResult = await getKey();
var serverChallenge = JSON.parse(keyResult);
var createCredentialOptions = {
rp: {
name: "WebAuthn Sample App",
icon: ""
},
user: {
id: Uint8Array.from("some.user.guid", c => c.charCodeAt(0)),
name: "maherrj@gmail.com",
displayName: "Richard Maher",
icon: ""
},
pubKeyCredParams: [
{
//External authenticators support the ES256 algorithm
type: "public-key",
alg: -7
},
{
//Windows Hello supports the RS256 algorithm
type: "public-key",
alg: -257
}
],
authenticatorSelection: {
//Select authenticators that support username-less flows
//requireResidentKey: true,
//Select authenticators that have a second factor (e.g. PIN, Bio) "preferred" "discouraged"
userVerification: "required",
//Selects between bound or detachable authenticators
authenticatorAttachment: "platform" // Optional
},
//Since Edge shows UI, it is better to select larger timeout values
timeout: 30000,
//an opaque challenge that the authenticator signs over
challenge: Uint8Array.from(serverChallenge.Token, c => c.charCodeAt(0)).buffer,
//prevent re-registration by specifying existing credentials here
excludeCredentials: [],
//specifies whether you need an attestation statement
attestation: "none"
};
const authAbort = new AbortController();
const abortSignal = authAbort.signal;
abortSignal.addEventListener("abort", (e) => { console.log("It has been aborted"); });
return navigator.credentials.create({
publicKey: createCredentialOptions,
signal: abortSignal
}).then(rawAttestation => {
var attestation = {
id: base64encode(rawAttestation.rawId),
clientDataJSON: utf8Decoder.decode(rawAttestation.response.clientDataJSON),
attestationObject: base64encode(rawAttestation.response.attestationObject)
};
console.log("=== Attestation response ===");
console.log(attestation);
verifyCredentials(attestation).then(
result => {
var res = JSON.parse(result);
console.log(res.success);
if (res.success) {
localStorage.setItem("credentialId", res.id);
}
});
return;
}).catch(
(err) => {
if (err.name == "NotAllowedError") {
console.log("here " + err.name);
} else {
console.log("other " + err.name);
}
return Promise.resolve(false);
});
}
async function verifyCredentials(attestation) {
let params = JSON.stringify(attestation);
let resp = await fetch("api/fido/verifycredentials", {
method: "POST",
headers: { "Content-type": "application/json", "Accept": "application/json" },
body: params
});
var myStat;
if (resp.ok) {
myStat = await resp.json();
console.log("Stat vc = " + myStat)
} else {
console.log("boom");
}
console.log("done ");
return myStat;
}
async function verifyAssertion(assertion) {
let params = JSON.stringify(assertion);
let resp = await fetch("api/fido/verifyassertion", {
method: "POST",
headers: { "Content-type": "application/json", "Accept": "application/json" },
body: params
});
var myStat;
if (resp.ok) {
myStat = await resp.json();
console.log("Stat va = " + myStat)
} else {
console.log("boom");
}
console.log("done ");
return myStat;
}
async function getKey() {
let resp = await fetch("api/fido/getkey", {
method: "GET",
headers: { "Content-type": "application/json", "Accept": "application/json" }
});
var mykey;
if (resp.ok) {
mykey = await resp.json();
console.log("key = " + mykey)
} else {
throw new Error("boom");
}
console.log("done key");
return mykey;
}
function base64encode(arrayBuffer) {
if (!arrayBuffer || arrayBuffer.length == 0)
return undefined;
return btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}
You are mixing a few different things here. The security key, local platform authenticator (Windows Hello) and your phone will all have their own credential.
If you are just trying to test the new FIDO cross device authentication flow using your phone, do not require a resident credential (not yet supported) and do not set an attachment preference.
When prompted in Chrome, add your phone to link it, scan the QR on your phone and then perform the UV gesture.