MySQL stores (add-on)
CodeLogic.WebLogic.MySql is an optional package with production implementations of every WebLogic
store contract on CL.MySQL2. The core toolkit stays database-agnostic; reference this package only
when MySQL is your choice.
// CL.MySQL2 must be loaded and configured (config.mysql.json → databases → "Default").
builder.AddCodeLogicWebLogic(web => web
.UseMySqlStores("Default", o =>
{
o.Stores = WebMySqlStores.All & ~WebMySqlStores.ApiKeys; // keep your own API key store
o.AuditRetention = TimeSpan.FromDays(30);
}));
builder.Services.AddDataProtection()
.SetApplicationName("my-site")
.PersistKeysToMySql("Default")
.ProtectKeysWithSecret("KEYRING_SECRET"); // AES-256-GCM at rest (optional, recommended)
UseMySqlStores replaces the in-memory defaults for the selected stores, so the development-store
guard and the cluster readiness checks pass. Stores left out keep whatever the app registered.
Tables
| Flag | Contract | Table |
|---|---|---|
Users |
IWebUserStore (+ lockout, e-mail, external logins) |
wl_users, wl_user_logins |
Sessions |
IWebSessionStore (+ device listing) |
wl_sessions |
ApiKeys |
IWebApiKeyStore |
wl_api_keys |
RecoveryCodes |
IWebRecoveryCodeStore |
wl_recovery_codes |
OneTimeUse |
IWebOneTimeUseStore |
wl_one_time_use |
LoginThrottle |
IWebLoginThrottle |
wl_login_throttle |
RateLimits |
IWebRateLimitStore |
wl_rate_limits |
Audit |
IWebAuditSink |
wl_audit |
WidgetSettings |
IWebWidgetSettingsStore |
wl_widget_settings |
Dashboards |
IWebDashboardLayoutStore |
wl_dashboards |
Passkeys |
IWebPasskeyStore |
wl_passkeys |
| (DataProtection) | IXmlRepository |
wl_dataprotection_keys |
Every store goes through CL.MySQL2's typed API (repositories, Query<T>() with typed updates and
deletes, upserts, UpdateAndGetAsync, transactions); the add-on contains no SQL text, so every value
is sent as a parameter.
All tables use utf8mb4_bin; case-insensitive lookups go through normalized columns. With
SyncSchemaOnStart (default) the tables are created or updated before any other hosted service
starts. Turn it off when migrations are run separately; SyncSchemaAsync is still available on CL.MySQL2.
Guarantees across nodes
- Single use: recovery codes are consumed with one
DELETE(exactly one node succeeds); spent TOTP steps and single-use links useINSERT IGNORE. - Lockout: failed sign-ins are counted and the lockout set in one
UPDATE, so parallel attempts on several nodes cannot skip the limit. - Rate limits: each request refills and charges its bucket in one atomic update that returns the result (the row is locked meanwhile), so the capacity holds exactly under concurrency on any number of nodes.
- Sign-in throttle: each failure is one upsert that creates, increments or restarts the counter.
- Recovery code sets are replaced in one transaction: never empty, never old and new codes mixed.
- Dashboards: saves carry the expected version; a stale save fails instead of overwriting.
- Passkeys: a credential id has one owner (
INSERT IGNORE), and each sign-in updates the counter only if it still holds the value that was read, so two nodes can never both accept the same assertion. - Key ring: keys are inserted, never updated; every node sees every key.
Housekeeping
WebMySqlSweepService deletes expired sessions, one-time keys, throttle and rate-limit rows, and audit
rows older than AuditRetention, every SweepInterval (10 minutes). Each node may run it; the deletes
are idempotent.
Testing
The package's own tests run against a real server when WEBLOGIC_MYSQL_TEST_HOST (and _PORT, _DB,
_USER, _PASS) are set, and skip otherwise. CI provides a mysql:8.4 service on Linux.