spring-projects/spring-security · error · IllegalArgumentException
Credential with id already exists
Error message
Credential with id <credentialId> already exists
What it means
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.
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.
Example fix
// before
CredentialRecord existing = ops.userCredentials.findByCredentialId(credentialId);
if (existing != null) {
throw new IllegalArgumentException("Credential with id " + credentialId + " already exists");
}
// after — replace instead of fail
CredentialRecord existing = ops.userCredentials.findByCredentialId(credentialId);
if (existing != null) {
ops.userCredentials.delete(credentialId);
} Defensive patterns
Strategy: validation
Validate before calling
Bytes credentialId = rpRegistrationRequest.getPublicKey().getCredential().getRawId();
if (relyingPartyOperations.userCredentials.findByCredentialId(credentialId) != null) {
throw new ResponseStatusException(HttpStatus.CONFLICT,
"This passkey is already registered — sign in with it instead");
}
Try / catch
try {
record = relyingPartyOperations.registerCredential(rpRegistrationRequest);
} catch (IllegalArgumentException e) {
if (!e.getMessage().contains("already exists")) throw e;
throw new ResponseStatusException(HttpStatus.CONFLICT, "Credential already registered");
}
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- <ex.getMessage()>
- Unable to authenticate the PublicKeyCredential
- Unable to authenticate the PublicKeyCredential. No…
- Unused placeholders in template
- Amount of performance parameters invalid
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/7e56ed386114df41.
Report an issue: GitHub.
Appendix: source
Thrown at webauthn/src/main/java/org/springframework/security/web/webauthn/management/Webauthn4JRelyingPartyOperations.java:246
return foundUserEntity;
}
PublicKeyCredentialUserEntity userEntity = ImmutablePublicKeyCredentialUserEntity.builder()
.displayName(username)
.id(Bytes.random())
.name(username)
.build();
this.userEntities.save(userEntity);
return userEntity;
}
@Override
public CredentialRecord registerCredential(RelyingPartyRegistrationRequest rpRegistrationRequest) {
Assert.notNull(rpRegistrationRequest, "rpRegistrationRequest cannot be null");
Bytes credentialId = rpRegistrationRequest.getPublicKey().getCredential().getRawId();
CredentialRecord existingCredential = this.userCredentials.findByCredentialId(credentialId);
if (existingCredential != null) {
throw new IllegalArgumentException("Credential with id " + credentialId + " already exists");
}
PublicKeyCredentialCreationOptions creationOptions = rpRegistrationRequest.getCreationOptions();
String rpId = creationOptions.getRp().getId();
RelyingPartyPublicKey publicKey = rpRegistrationRequest.getPublicKey();
PublicKeyCredential<AuthenticatorAttestationResponse> credential = publicKey.getCredential();
AuthenticatorAttestationResponse response = credential.getResponse();
// Server properties
Set<Origin> origins = toOrigins();
byte[] base64Challenge = creationOptions.getChallenge().getBytes();
byte[] attestationObject = response.getAttestationObject().getBytes();
byte[] clientDataJSON = response.getClientDataJSON().getBytes();
Challenge challenge = new DefaultChallenge(base64Challenge);
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
boolean userVerificationRequired = UserVerificationRequirement.REQUIRED
.equals(creationOptions.getAuthenticatorSelection().getUserVerification());
// requireUserPresence The constant Boolean value true
// https://www.w3.org/TR/webauthn-3/#sctn-op-make-cred
boolean userPresenceRequired = true;View on GitHub (pinned to 96852e8860)