Wocha Docs

Organisation security policies

Per-organisation MFA, password, and session policies with tenant inheritance and nested org cascades

Wocha lets you define security policies at the tenant level and override them per organisation. Organisations inherit tenant defaults by default and can override individual fields to be stricter — never less strict than the tenant baseline (except when both tenant and org are optional for MFA enforcement).

This guide covers the policy model, inheritance behaviour, Console configuration, and programmatic management via the Customer API.


Overview

The org security policy system covers three policy domains:

DomainWhat it controls
MFAEnforcement level (optional / recommended / required), challenge policy (never / adaptive / always), allowed factors, preferred factor, grace period, unenrolled user action
PasswordMinimum length, complexity rules, HIBP breach check, maximum age, reuse prevention
SessionAbsolute session timeout and idle timeout

Policies are stored in org_security_policies (per-org overrides) and tenant_configs (tenant MFA and password defaults). Legacy org metadata in org_metadata (mfa_enforcement, session_timeout_minutes) is merged during resolution.

At consent time, Wocha resolves the effective policy for the active organisation and enforces MFA, session limits, and password age before issuing tokens.


Policy inheritance model

Resolution follows a strictness merge: stricter wins.

flowchart LR
    Global["Global defaults"] --> Tenant["Tenant policy"]
    Tenant --> Parent["Parent org overrides"]
    Parent --> Org["This org overrides"]
    Org --> Effective["Effective policy"]
  1. Start with tenant MFA and password defaults.
  2. Apply parent organisation overrides (root-first, nearest parent last) when the org is nested.
  3. Apply this organisation's overrides.
  4. Return the merged effective policy.
Field typeMerge rule
MFA enforcementrequired > recommended > optional
MFA policyalways > adaptive > never
Allowed factorsIntersection with tenant-allowed factors
Grace periodMinimum of tenant and org values
Unenrolled user actionforce_enrollment > email_challenge > allow
Password fieldsOrg non-null values override tenant; null inherits
Session timeoutOrg value when set; otherwise tenant/org metadata default

An organisation cannot set a weaker MFA enforcement or policy than its tenant. The Customer API rejects invalid updates with a validation_error.


MFA policy at org level

Enforcement levels

LevelBehaviour
optionalMFA only when tenant policy, adaptive risk, or org mfaPolicy override requires it. Org can still restrict allowed factors.
recommendedUnenrolled users see an interstitial (/mfa-recommended) with "Set up now" and "Skip for now". Dismissal suppresses the prompt for 7 days. Enrolled users at AAL1 are stepped up.
requiredAll members must complete MFA before consent — no grace period.

MFA policy override

Separate from enforcement, mfaPolicy controls when to challenge:

PolicyBehaviour
neverRisk engine and tenant policy do not force MFA (org required enforcement still applies).
adaptiveMFA when risk verdict is step_up.
alwaysEvery first-party consent requires MFA for enrolled users.

Example: tenant adaptive + org always + org required → all org members always face MFA.

Per-org factor configuration

Orgs can override allowedFactors and preferredFactor. Allowed factors must be a subset of the tenant's allowed factors. When adaptive MFA triggers step-up for an org member, the org's factor configuration is used.

See Adaptive MFA for the full risk engine and assessor reference.


Password policy per org

Password policies are enforced at:

  • Kratos proxy — registration and settings flows (password change)
  • Platform API — API-created users via validatePassword()
  • Consent processor — password age expiry redirects to /settings
FieldDescription
passwordMinLength8–128 characters
passwordRequireUppercaseAt least one uppercase letter
passwordRequireLowercaseAt least one lowercase letter
passwordRequireNumberAt least one digit
passwordRequireSymbolAt least one symbol
passwordHibpCheckReject passwords found in Have I Been Pwned (k-anonymity API)
passwordMaxAgeDaysForce password rotation after N days (0 = no expiry)
passwordDisallowReuseReject passwords matching the last N stored hashes (0 = disabled)

Org password rules can only be stricter than the tenant default (e.g. longer minimum length, shorter max age).


Session timeout per org

FieldDescription
sessionTimeoutMinutesMaximum session lifetime from authenticated_at (minimum 5 minutes)
sessionIdleTimeoutMinutesMaximum idle time from last consent activity (minimum 1 minute)

When either limit is exceeded at consent time, the user is redirected to re-authenticate. OAuth access token TTL is aligned to min(default_ttl, org_session_timeout) so tokens do not outlive the session policy.


Configuration via Console

Navigate to Organisations → [Organisation] → Security to manage the full security policy.

Each field supports an inherit / override pattern:

  • Inherited — uses the tenant (or parent org) value; shown as "Inherited from tenant: [value]"
  • Customise — toggle to set an org-specific override
  • Badge — "Inherited", "Overridden", or "Stricter than tenant"

The Security tab includes:

  • MFA Enforcement — optional / recommended / required
  • MFA Policy Override — inherit / never / adaptive / always
  • Allowed Factors — checkboxes with inherit toggle
  • Preferred Factor — dropdown with inherit toggle
  • Unenrolled User Action — inherit or override
  • Password Policy — min length, complexity, HIBP, max age, reuse prevention
  • Session Policy — absolute and idle timeouts

For nested organisations, the UI shows the policy hierarchy: global default → tenant → parent organisation → this organisation.

Tenant-wide password defaults are configured under Settings → Security → Password Policy.


Configuration via Customer API

Organisation security policy

# Get effective policy (merged tenant + org, with _inherited flags)
curl -X GET "https://TENANT.api.wocha.ai/v1/customer/organisations/org_abc123/security-policy" \
  -H "Authorization: Bearer wocha_mgmt_..."
 
# Set org overrides (only include fields to override)
curl -X PUT "https://TENANT.api.wocha.ai/v1/customer/organisations/org_abc123/security-policy" \
  -H "Authorization: Bearer wocha_mgmt_..." \
  -H "Content-Type: application/json" \
  -d '{
    "mfaEnforcement": "required",
    "mfaPolicy": "always",
    "passwordMinLength": 16,
    "sessionTimeoutMinutes": 60
  }'
 
# Reset all org overrides to tenant defaults
curl -X DELETE "https://TENANT.api.wocha.ai/v1/customer/organisations/org_abc123/security-policy" \
  -H "Authorization: Bearer wocha_mgmt_..."
const response = await fetch(
  `https://${tenant}.api.wocha.ai/v1/customer/organisations/${orgId}/security-policy`,
  {
    method: "PUT",
    headers: {
      Authorization: `Bearer ${managementApiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      mfaEnforcement: "recommended",
      allowedFactors: ["totp", "webauthn"],
      passwordMaxAgeDays: 90,
    }),
  },
);
 
const policy = await response.json();
console.log(policy._inherited.mfaEnforcement); // false — overridden

Tenant password policy

curl -X GET "https://TENANT.api.wocha.ai/v1/customer/settings/password-policy" \
  -H "Authorization: Bearer wocha_mgmt_..."
 
curl -X PUT "https://TENANT.api.wocha.ai/v1/customer/settings/password-policy" \
  -H "Authorization: Bearer wocha_mgmt_..." \
  -H "Content-Type: application/json" \
  -d '{
    "min_length": 14,
    "require_uppercase": true,
    "hibp_check": true,
    "max_age_days": 180
  }'

Full request and response schemas are documented in the Organisation security policy API reference.


Nested org inheritance

Wocha supports nested organisation hierarchies (parent/child orgs via SpiceDB). Child organisations inherit policies from their parent chain in addition to the tenant baseline.

Tenant (adaptive MFA, min password 12)
  └── Parent org (MFA required, session timeout 30 min)
        └── Child org (password min 16, idle timeout 15 min)

Effective policy for child org member:
  MFA: required (from parent)
  MFA policy: adaptive (from tenant)
  Password min length: 16 (from child)
  Session timeout: 30 min (from parent)
  Idle timeout: 15 min (from child)

This is a unique advantage over platforms with flat org models (e.g. Kinde), where policies apply only at a single org level without parent-to-child cascade.

Resolved policies are cached in Valkey for 60 seconds to avoid expensive hierarchy walks on every consent.