fix: Prevent registration of duplicate passkeys on the same device (#11870)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Moor
2026-03-24 22:54:43 -04:00
committed by GitHub
parent 793804cd0d
commit c2ccdb6fd4
+22 -2
View File
@@ -95,12 +95,20 @@ router.post(
const { user } = ctx.state.auth;
authorize(user, "createUserPasskey", user.team);
// Fetch existing passkeys to exclude them from registration
const existingPasskeys = await UserPasskey.findAll({
where: { userId: user.id },
});
const options = await generateRegistrationOptions({
rpName,
rpID: getRpID(ctx),
userID: isoBase64URL.toBuffer(user.id),
userName: user.email || user.name,
// Don't exclude credentials, so we can detect if one is already registered (optional)
excludeCredentials: existingPasskeys.map((pk) => ({
id: pk.credentialId,
transports: pk.transports as AuthenticatorTransportFuture[],
})),
authenticatorSelection: {
residentKey: "preferred",
userVerification: "preferred",
@@ -154,6 +162,7 @@ router.post(
}
const { verified, registrationInfo } = verification;
const ZERO_AAGUID = "00000000-0000-0000-0000-000000000000";
if (verified && registrationInfo) {
const { credential, aaguid } = registrationInfo;
@@ -166,7 +175,7 @@ router.post(
const userAgent = ctx.request.get("user-agent");
const transports = body.response.transports || [];
// Check if already exists
// Check if already exists by credential ID
const existing = await UserPasskey.findOne({
where: { credentialId: credentialIdBase64 },
});
@@ -183,6 +192,17 @@ router.post(
aaguid,
});
} else {
// Check if user already has a passkey from the same authenticator
if (aaguid && aaguid !== ZERO_AAGUID) {
const duplicateDevice = await UserPasskey.findOne({
where: { userId: user.id, aaguid },
});
if (duplicateDevice) {
throw ValidationError("You already have a passkey on this device");
}
}
await UserPasskey.createWithCtx(ctx, {
userId: user.id,
credentialId: credentialIdBase64,