Configuration
Engine configuration (cryden.Config)
This is the current, complete field list. Every field beyond the first four is independently optional — leaving it unset turns that one feature off and changes nothing else. See Features for what each field turns on and Code Examples for a runnable snippet of every one of them.
type Config struct {
// Required — no default exists for any of these. cryden.New()
// returns an error immediately if any are missing.
JWTSecret string
Users store.UserStore
Sessions store.SessionStore
Audit store.AuditStore
// Email verification / email change — both required together,
// or leave both unset.
Verifications store.VerificationStore
EmailSender notify.EmailSender
// Magic-link (passwordless) login. Requires Verifications too.
MagicLinkSender notify.MagicLinkSender
// OAuth login/signup with any provider.
OAuth store.OAuthStore
// TOTP (authenticator app) second factor. Requires EncryptionKey.
TOTP store.TOTPStore
EncryptionKey string
TOTPIssuerName string // default: "Cryden"
// Passkeys (WebAuthn) second factor. Requires EncryptionKey and
// all three WebAuthnRP* fields below.
WebAuthn store.WebAuthnCredentialStore
WebAuthnRPID string
WebAuthnRPDisplayName string
WebAuthnRPOrigins []string
// Recovery (backup) codes, offered alongside a real second factor.
RecoveryCodes store.RecoveryCodeStore
// API keys for machine-to-machine auth.
APIKeys store.APIKeyStore
APIKeyPrefix string // default: "ck"
// Webhooks — the engine notifies the host app of key events.
Webhooks notify.WebhookSender
WebhookEvents []store.AuditEventType // default: cryden.DefaultWebhookEvents()
// Password policy and breach checking. The zero-valued
// PasswordPolicy applies security.DefaultPasswordPolicy — unlike
// every other feature here, there is no "off" state for policy.
PasswordPolicy security.PasswordPolicy
BreachedPasswordChecker security.BreachedPasswordChecker
// Named-session geolocation. Device labels work with this unset.
Geolocator security.IPGeolocator
// Login anomaly detection and credential-stuffing detection.
// Both thresholds structs default when left entirely zero-valued.
Anomalies store.AnomalyStore
AnomalyThresholds security.AnomalyThresholds
CredentialStuffingThresholds security.CredentialStuffingThresholds
// Host-supplied data attached to every access token.
AccessTokenClaims token.ClaimsProvider
// Optional — sensible defaults applied if left unset.
AccessTokenTTL time.Duration // default: 15 minutes
BcryptCost int // default: 10
RefreshTokenByteLength int // default: 32
RateLimitAttempts int // default: 10
RateLimitWindow time.Duration // default: 1 minute
LockoutThreshold int // default: 5 failed attempts
LockoutDuration time.Duration // default: 15 minutes
Logger logger.Logger // default: ConsoleJSONLogger
// Replace the default in-memory implementations built from the
// tuning knobs above. Both are injected already-constructed, the
// same as every store.
RateLimiter security.RateLimiter // e.g. security.NewRedisRateLimiter(...)
Hasher security.Hasher // e.g. security.NewArgon2idHasher(...)
}Why some fields are required and others default
JWTSecret and the three core stores (Users, Sessions, Audit) are security-critical or structurally necessary — the engine cannot safely guess a value for them, so it refuses to start without them. Every other field is a tuning knob where a reasonable default exists and getting it slightly wrong is not a security failure, only a suboptimal default — see design-decisions.md for the reasoning behind this split.
Wiring Postgres
import (
"database/sql"
_ "github.com/lib/pq"
"github.com/crydensync/cryden/v2/store/postgres"
)
db, _ := sql.Open("postgres", os.Getenv("DATABASE_URL"))
engine, err := cryden.New(cryden.Config{
JWTSecret: os.Getenv("JWT_SECRET"),
Users: postgres.NewUserStore(db),
Sessions: postgres.NewSessionStore(db),
Audit: postgres.NewAuditStore(db),
})Wiring email verification
type myEmailSender struct{ /* your provider's client */ }
func (s *myEmailSender) SendVerification(ctx context.Context, to, rawToken string) error {
// Build your own URL, e.g. https://yourapp.com/verify?token=" + rawToken
// and send it via your provider (Resend, SES, SendGrid, etc.)
}
engine, err := cryden.New(cryden.Config{
// ...required fields...
Verifications: postgres.NewVerificationStore(db),
EmailSender: &myEmailSender{},
})Calling RequestEmailChange without both Verifications and EmailSender configured returns cryden.ErrEmailChangeNotConfigured rather than panicking.
api repository environment variables
| Variable | Required | Default | Notes |
|---|---|---|---|
DATABASE_URL | Yes | — | Postgres connection string |
JWT_SECRET | Yes | — | Signs access tokens |
CORS_ORIGINS | Yes | — | Comma-separated allowed origins. No wildcard default — an API handling auth tokens should never allow every origin. |
PORT | No | 8080 | |
ACCESS_TOKEN_TTL_MINUTES | No | 15 | |
EDGE_RATE_LIMIT | No | 100 | Requests per minute, per IP, across the whole API surface — separate from the engine's own per-user login/signup rate limiting |
csax CLI configuration
Written by csax config init to a local .env file:
| Variable | Notes |
|---|---|
DATABASE_URL | Same database your api/engine-embedding app uses |
JWT_SECRET | Needed for any command that constructs a full engine instance (session revocation, etc.) — not needed for pure read commands |
MIGRATIONS_DIR | Default ./migrations — should point at a folder containing both the engine's migration and your own app's migrations |
Security notes on configuration
- Never commit
.envfiles. Every repository in the ecosystem ships a.env.examplewith variable names but no real values, and a.gitignoreexcluding.env. - Generate
JWT_SECRETwith a real source of randomness — e.g.openssl rand -base64 32— never a guessable string. - In a real deployment, prefer setting environment variables directly (most hosting platforms — Railway, Render, Fly.io, etc. — support this natively) over relying on a
.envfile being present at runtime.