-
Notifications
You must be signed in to change notification settings - Fork 43
Description
Summary
The user_credentials table is never created by Hibernate's ddl-auto: update on MariaDB because the generated CREATE TABLE statement exceeds InnoDB's 65,535-byte row size limit. Hibernate silently swallows the error, so the app starts but /user/auth-methods and /user/webauthn/credentials return 500.
Root Cause
WebAuthnCredential.java has two byte[] fields with @Column(length = 65535):
attestationObjectattestationClientDataJson
With Hibernate 7.x and MariaDB dialect, @Column(length = 65535) on byte[] maps to VARBINARY(65535) — not BLOB. Two VARBINARY(65535) columns alone account for 131,074 bytes, which exceeds MariaDB's InnoDB row size limit of 65,535 bytes.
MariaDB rejects the CREATE TABLE with:
ERROR 1118 (42000): Row size too large (> 65535)
Hibernate's ddl-auto: update catches this exception silently and moves on.
Why user_entities Works But user_credentials Doesn't
WebAuthnUserEntity has only small String columns and an FK, so its row size is well under the limit.
Historical Context
Commit cd4b0a1 ("fix: Use length 65535 for WebAuthn blob columns to avoid MySQL MEDIUMBLOB promotion") changed length from 65536 to 65535. Ironically, at length = 65536 Hibernate generates MEDIUMBLOB (stored off-page, no row size issue). At length = 65535 it generates VARBINARY(65535) (stored inline, breaks the row size limit).
Suggested Fix
Add @Lob to the byte[] fields in WebAuthnCredential.java:
import jakarta.persistence.Lob;
// On attestationObject field:
@Lob
@Column(name = "attestation_object")
private byte[] attestationObject;
// On attestationClientDataJson field:
@Lob
@Column(name = "attestation_client_data_json")
private byte[] attestationClientDataJson;With @Lob, Hibernate maps byte[] to BLOB/LONGBLOB regardless of the length setting. BLOB data is stored off-page in InnoDB, avoiding the row size limit. The length attribute can be removed since @Lob takes precedence.
Optionally add @Lob to publicKey as well (currently 2048 bytes).
Impact
- Existing deployments where table was never created: Table will be created on next startup with
ddl-auto: update - Existing deployments where table exists:
ddl-auto: updateshould detect columns are already BLOB and not alter them
Reproduction
- Use MariaDB with
spring.jpa.hibernate.ddl-auto: update - Start the app —
user_entitiestable is created butuser_credentialsis not - Hit
/user/auth-methodsor/user/webauthn/credentialswhile authenticated → 500 withTable 'springuser.user_credentials' doesn't exist
Discovered while investigating Playwright test failures in the demo app (Issue #67).