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

  1. 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.
  2. Include excludeCredentials containing the user's existing credential IDs in the PublicKeyCredentialCreationOptions so the browser will not offer an already-registered authenticator.
  3. 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

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


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)