Skip to main content

Tenant configuration

The Tenant API exposes a single per-tenant configuration object covering password policy, login behavior, passwordless authentication, captcha, and rate limits. It is read via the configuration query and written via the configure mutation.

Every successful configure call is recorded in the audit log as a tenant_config_change event, with captcha.secret redacted to ***.

Reading configuration

query {
configuration {
password {
minLength
requireUppercase
requireLowercase
requireDigit
requireSpecial
pattern
checkBlacklist
checkHibp
}
login {
revealUserExists
revealLoginMethod
baseBackoff
maxBackoff
attemptWindow
defaultTokenExpiration
maxTokenExpiration
}
passwordless {
enabled
url
expiration
}
captcha {
provider
threshold
}
rateLimits {
signUpPerIp { limit window }
loginPerIp { limit window }
passwordResetPerIp { limit window }
passwordlessInitPerIp { limit window }
}
}
}

The captcha.secret is intentionally not exposed by the schema — it is a credential, write-only.

Writing configuration

mutation {
configure(config: {
password: { minLength: 12, checkHibp: true, checkBlacklist: true },
login: { revealUserExists: false, defaultTokenExpiration: "PT30M" },
captcha: { provider: turnstile, secret: "0x4AAA…" },
rateLimits: {
signUpPerIp: { limit: 5, window: "PT1H" },
loginPerIp: { limit: 20, window: "PT1H" },
passwordResetPerIp: { limit: 5, window: "PT1H" },
passwordlessInitPerIp: { limit: 5, window: "PT1H" }
}
}) {
ok
error { code developerMessage }
}
}

All ConfigInput fields are optional and partial — unset fields preserve the current value.

Requires the CONFIGURE tenant permission. By default this is held by SUPER_ADMIN.

Sections

password

See password policy for individual fields and the WeakPasswordReason enum returned with TOO_WEAK errors.

FieldDefaultNotes
minLength8
requireUppercase, requireLowercase, requireDigit, requireSpecial0Minimum count of each character class
patternnullOptional regex; pattern violations yield INVALID_PATTERN
checkBlacklisttrue10k-most-common list with leetspeak normalization
checkHibpfalseHIBP k-anonymity check; opt-in. See anti-abuse. Available since 2.2.

login

FieldDefaultNotes
revealUserExiststrueWhen false, unknown-email failures on sign-in / reset / passwordless-init are masked. See anti-abuse — enumeration protection.
revealLoginMethodtrue(since 2.2) When false, signIn collapses NO_PASSWORD_SET / INVALID_PASSWORD into a generic INVALID_CREDENTIALS and signUp omits the recommendedAction hint on EMAIL_ALREADY_EXISTS. Orthogonal to revealUserExistsUNKNOWN_EMAIL is still controlled by that flag.
baseBackoffPT1SStarting backoff between per-email login attempts. Also drives the per-email mail-init throttle for password reset and passwordless init.
maxBackoffPT1MUpper bound for the exponential backoff.
attemptWindowPT5MHow long failed attempts are remembered.
defaultTokenExpirationPT30MSession token lifetime when the client omits expiration.
maxTokenExpiration6 monthsHard cap on client-requested expiration.

Intervals follow ISO 8601 duration syntax (PT5M, PT1H, P1D, …).

passwordless

See the passwordless page.

captcha (since 2.2)

See anti-abuse — captcha.

FieldNotes
providerturnstile, hcaptcha, or recaptchaV3. null disables captcha.
secretWrite-only. The provider's server-side secret. Encrypted at rest with the tenant's Providers keychain. Passing null leaves the stored value unchanged; passing "" clears it.
thresholdreCAPTCHA v3 score threshold (0.0–1.0). Ignored for hCaptcha / Turnstile.

rateLimits (since 2.2)

Sliding-window per-IP limits. limit: 0 (the default) disables that scope. See anti-abuse — rate limits.

ScopeApplied to
signUpPerIpsignUp
loginPerIpsignIn, signInIDP, signInPasswordless
passwordResetPerIpcreateResetPasswordRequest
passwordlessInitPerIpinitSignInPasswordless
mutation {
configure(config: {
password: { minLength: 12, checkBlacklist: true, checkHibp: true },
login: { revealUserExists: false, revealLoginMethod: false },
captcha: { provider: turnstile, secret: "…" },
rateLimits: {
signUpPerIp: { limit: 5, window: "PT1H" },
loginPerIp: { limit: 20, window: "PT1H" },
passwordResetPerIp: { limit: 5, window: "PT1H" },
passwordlessInitPerIp: { limit: 5, window: "PT1H" }
}
}) { ok error { code developerMessage } }
}

For most tenants this is enough to make brute-force, password-spray, and enumeration attacks impractical without affecting legitimate users.