Skip to content

Commit

Permalink
Prevent overwriting passkeys when using registering with the same Use…
Browse files Browse the repository at this point in the history
…r ID (#1)

* check to prevent creating new credential when if one already exists on authenticator

* add user-friendly error message

* add check to make sure credential IDs in excludeCredentials are binary

* store credentials as array to prevent overwriting credentials

* refactor
  • Loading branch information
tonydangblog committed Mar 13, 2024
1 parent af84b23 commit a306697
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 5 deletions.
14 changes: 14 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ export default publicAPI;
async function register(regOptions = regDefaults()) {
try {
if (supportsWebAuthn) {
// ensure credential IDs are binary (not base64 string)
if (Array.isArray(regOptions[regOptions[credentialTypeKey]].excludeCredentials)) {
regOptions[regOptions[credentialTypeKey]].excludeCredentials = (
regOptions[regOptions[credentialTypeKey]].excludeCredentials.map(entry => ({
...entry,
id: (
typeof entry.id == "string" ?
sodium.from_base64(entry.id,sodium.base64_variants.ORIGINAL) :
entry.id
),
}))
);
}

let regResult = await navigator.credentials.create(regOptions);

let regClientDataRaw = new Uint8Array(regResult.response.clientDataJSON);
Expand Down
39 changes: 34 additions & 5 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ async function registerNewCredential(name,userIDStr) {
displayName: name,
id: userID,
},
excludeCredentials: credentialsByID[userIDStr]?.map(({ credentialID }) => ({
type: "public-key",
id: credentialID,
})) ?? [],
});
try {
let regResult = await register(regOptions);
Expand All @@ -166,16 +170,38 @@ async function registerNewCredential(name,userIDStr) {

// keep registered credential info in memory only
// (no persistence)
credentialsByID[userIDStr] = {
credentialID: regResult.response.credentialID,
publicKey: regResult.response.publicKey,
};
if (userIDStr in credentialsByID) {
credentialsByID[userIDStr] = [
...credentialsByID[userIDStr],
{
credentialID: regResult.response.credentialID,
publicKey: regResult.response.publicKey,
}
];
} else {
credentialsByID[userIDStr] = [{
credentialID: regResult.response.credentialID,
publicKey: regResult.response.publicKey,
}]
}

console.log("regResult:",regResult);
}
}
catch (err) {
logError(err);

if (err.cause instanceof Error) {
var errorString = err.cause.toString();
if (errorString.includes("credentials already registered with the relying party")) {
showError(`
A credential already exists for this User ID.
Please try a different User ID or pick a different authenticator.
`);
return
}
}

showError("Registering credential failed. Please try again.");
}
}
Expand Down Expand Up @@ -278,7 +304,10 @@ async function promptProvideAuth() {

cancelToken = new AbortController();
var authOptions = authDefaults({
allowCredentials: [ { type: "public-key", id: credentialsByID[userID].credentialID, }, ],
allowCredentials: credentialsByID[userID].map(({ credentialID }) => ({
type: "public-key",
id: credentialID,
})),
signal: cancelToken.signal,
});
try {
Expand Down

0 comments on commit a306697

Please sign in to comment.