{"record":{"id":"7e56ed386114df41","repo":"spring-projects/spring-security","slug":"credential-with-id-credentialid-already-exists","errorCode":null,"errorMessage":"Credential with id <credentialId> already exists","messagePattern":"Credential with id <credentialId> already exists","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java","lineNumber":246,"sourceCode":"\t\t\treturn foundUserEntity;\n\t\t}\n\n\t\tPublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder()\n\t\t\t.displayName(username)\n\t\t\t.id(Bytes.random())\n\t\t\t.name(username)\n\t\t\t.build();\n\t\tthis.userEntities.save(userEntity);\n\t\treturn userEntity;\n\t}\n\n\t@Override\n\tpublic CredentialRecord registerCredential(RelyingPartyRegistrationRequest rpRegistrationRequest) {\n\t\tAssert.notNull(rpRegistrationRequest, \"rpRegistrationRequest cannot be null\");\n\t\tBytes credentialId = rpRegistrationRequest.getPublicKey().getCredential().getRawId();\n\t\tCredentialRecord existingCredential = this.userCredentials.findByCredentialId(credentialId);\n\t\tif (existingCredential != null) {\n\t\t\tthrow new IllegalArgumentException(\"Credential with id \" + credentialId + \" already exists\");\n\t\t}\n\t\tPublicKeyCredentialCreationOptions creationOptions = rpRegistrationRequest.getCreationOptions();\n\t\tString rpId = creationOptions.getRp().getId();\n\t\tRelyingPartyPublicKey publicKey = rpRegistrationRequest.getPublicKey();\n\t\tPublicKeyCredential<AuthenticatorAttestationResponse> credential = publicKey.getCredential();\n\t\tAuthenticatorAttestationResponse response = credential.getResponse();\n\t\t// Server properties\n\t\tSet<Origin> origins = toOrigins();\n\t\tbyte[] base64Challenge = creationOptions.getChallenge().getBytes();\n\t\tbyte[] attestationObject = response.getAttestationObject().getBytes();\n\t\tbyte[] clientDataJSON = response.getClientDataJSON().getBytes();\n\t\tChallenge challenge = new DefaultChallenge(base64Challenge);\n\t\tServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);\n\t\tboolean userVerificationRequired = UserVerificationRequirement.REQUIRED\n\t\t\t.equals(creationOptions.getAuthenticatorSelection().getUserVerification());\n\t\t// requireUserPresence The constant Boolean value true\n\t\t// https://www.w3.org/TR/webauthn-3/#sctn-op-make-cred\n\t\tboolean userPresenceRequired = true;","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java#L228-L264","documentation":"Webauthn4JRelyingPartyOperations.registerCredential() checks whether a credential record with the submitted credential ID already exists and refuses duplicate registration by throwing this IllegalArgumentException. Each WebAuthn credential ID must be unique in the credential store.","triggerScenarios":"Calling registerCredential(rpRegistrationRequest) when userCredentials.findByCredentialId(credentialId) returns an existing record — i.e. the authenticator re-registers a credential previously attested with the same ID, or a malicious/replayed attestation reuses an ID.","commonSituations":"User re-runs the registration ceremony with an already-registered passkey instead of authenticating with it; browser reuse of a credential not excluded via excludeCredentials in PublicKeyCredentialCreationOptions; replayed registration payload.","solutions":["Decide the update path: if the same credential should be re-attested, delete/replace the existing record (implement a saveOrUpdate flow) or update user verification settings on the existing record instead of registering again.","Include excludeCredentials containing the user's existing credential IDs in the PublicKeyCredentialCreationOptions so the browser will not offer an already-registered authenticator.","Guide users to the authentication (assertion) flow when they select a credential that is already registered; surface a friendly message on catching this exception."],"exampleFix":"// before\nCredentialRecord existing = ops.userCredentials.findByCredentialId(credentialId);\nif (existing != null) {\n    throw new IllegalArgumentException(\"Credential with id \" + credentialId + \" already exists\");\n}\n// after — replace instead of fail\nCredentialRecord existing = ops.userCredentials.findByCredentialId(credentialId);\nif (existing != null) {\n    ops.userCredentials.delete(credentialId);\n}","handlingStrategy":"validation","validationCode":"Bytes credentialId = rpRegistrationRequest.getPublicKey().getCredential().getRawId();\nif (relyingPartyOperations.userCredentials.findByCredentialId(credentialId) != null) {\n    throw new ResponseStatusException(HttpStatus.CONFLICT,\n        \"This passkey is already registered — sign in with it instead\");\n}\n","typeGuard":null,"tryCatchPattern":"try {\n    record = relyingPartyOperations.registerCredential(rpRegistrationRequest);\n} catch (IllegalArgumentException e) {\n    if (!e.getMessage().contains(\"already exists\")) throw e;\n    throw new ResponseStatusException(HttpStatus.CONFLICT, \"Credential already registered\");\n}\n","preventionTips":["Populate excludeCredentials in PublicKeyCredentialCreationOptions with the user's existing credential IDs","Route users who already hold a registered passkey to the assertion (login) flow","Treat \"already exists\" IllegalArgumentException as a 409 Conflict, not a 500","Implement delete-then-register (or update) only if re-attestation is a deliberate product decision"],"tags":["webauthn","registration","duplicate","java"],"backgroundTag":"file-already-exists","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}