Documentation Index Fetch the complete documentation index at: https://mintlify.com/cgwire/zou/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Zou uses JWT (JSON Web Tokens) for stateless authentication. The system supports multiple authentication strategies and optional two-factor authentication for enhanced security.
Authentication Strategies
Zou supports three authentication strategies configurable via the AUTH_STRATEGY environment variable:
Local Authentication (Default)
AUTH_STRATEGY = "auth_local_classic"
Traditional username/password authentication with BCrypt password hashing.
zou/app/services/auth_service.py
def local_auth_strategy ( person , password , app = None ):
"""
Local strategy checks that email and password hash match.
Password comparison is based on BCrypt.
"""
password_hash = person[ "password" ] or ""
if password_hash and flask_bcrypt.check_password_hash(password_hash, password):
return person
else :
raise WrongPasswordException()
BCrypt automatically includes salt and uses configurable work factor (default: 12 rounds).
LDAP/Active Directory
AUTH_STRATEGY = "auth_remote_ldap"
Authenticates users against an LDAP or Active Directory server.
# LDAP Server
LDAP_HOST = ldap.company.com
LDAP_PORT = 389
LDAP_SSL = false
# LDAP Settings
LDAP_BASE_DN = cn = Users, dc = company, dc = com
LDAP_DOMAIN = company.com
LDAP_IS_AD = true # Active Directory mode
LDAP_IS_AD_SIMPLE = false # Use simple bind instead of NTLM
# Fallback to local auth for non-LDAP users
LDAP_FALLBACK = true
def ldap_auth_strategy ( person , password , app ):
if person[ "is_generated_from_ldap" ]:
# Connect to LDAP server
server = Server( f " { app.config[ 'LDAP_HOST' ] } : { app.config[ 'LDAP_PORT' ] } " ,
get_info = ALL , use_ssl = app.config[ 'LDAP_SSL' ])
if app.config[ 'LDAP_IS_AD' ]:
user = f " { app.config[ 'LDAP_DOMAIN' ] } \\ { person[ 'desktop_login' ] } "
authentication = NTLM
else :
user = f "uid= { person[ 'desktop_login' ] } , { app.config[ 'LDAP_BASE_DN' ] } "
authentication = SIMPLE
conn = Connection(server, user = user, password = password,
authentication = authentication)
conn.bind()
return person
elif app.config[ 'LDAP_FALLBACK' ]:
# Fall back to local authentication
return local_auth_strategy(person, password, app)
No Password (Development)
AUTH_STRATEGY = "auth_local_no_password"
Only use this in development environments. It bypasses password verification entirely.
JWT Token System
Token Types
Zou uses two types of JWT tokens:
Token Type Purpose Expires Storage Access Token API authentication 7 days Cookie + Header Refresh Token Renew access token 15 days Cookie only
Token Configuration
JWT_ACCESS_TOKEN_EXPIRES = timedelta( days = 7 )
JWT_REFRESH_TOKEN_EXPIRES = timedelta( days = 15 )
JWT_TOKEN_LOCATION = [ "cookies" , "headers" ]
JWT_COOKIE_CSRF_PROTECT = False
JWT_COOKIE_SAMESITE = "Lax"
JWT_IDENTITY_CLAIM = "sub" # User ID in 'sub' claim
Token Payload
JWT tokens contain:
{
"sub" : "user-uuid" , // User ID
"identity_type" : "person" , // person, bot, or person_api
"jti" : "token-unique-id" , // Token ID for blacklist
"exp" : 1234567890 , // Expiration timestamp
"iat" : 1234567890 , // Issued at timestamp
"requires_2fa_setup" : false // 2FA enforcement flag
}
Login Flow
Standard Login
Login Request
curl -X POST http://localhost:5000/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "artist@studio.com",
"password": "securepassword"
}'
Login Response
{
"user" : {
"id" : "uuid" ,
"email" : "artist@studio.com" ,
"first_name" : "John" ,
"last_name" : "Doe" ,
"role" : "user" ,
"active" : true
},
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Tokens are also set as HTTP-only cookies for browser clients.
Token Usage
curl -X GET http://localhost:5000/api/data/projects \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Cookie (Browser)
When using cookies, tokens are sent automatically:
fetch ( 'http://localhost:5000/api/data/projects' , {
credentials: 'include' // Include cookies
})
Token Refresh
Refresh Flow
Refresh Request
curl -X POST http://localhost:5000/auth/refresh-token \
--cookie "refresh_token_cookie=eyJ..."
Refresh tokens can only be used at the /auth/refresh-token endpoint.
Token Revocation (Logout)
Logout Flow
zou/app/services/auth_service.py
def logout ( jti ):
"""Remove access and refresh tokens from auth token store."""
try :
auth_tokens_store.add(jti, "true" , current_app.config[ "JWT_ACCESS_TOKEN_EXPIRES" ])
except Exception :
pass
Tokens are added to a Redis-based blacklist with TTL matching token expiration.
Token Blacklist Check
@jwt.token_in_blocklist_loader
def check_if_token_is_revoked ( _ , payload ):
identity_type = payload.get( "identity_type" )
if identity_type == "person" :
return auth_tokens_store.is_revoked(payload[ "jti" ])
elif identity_type in [ "bot" , "person_api" ]:
return persons_service.is_jti_revoked(payload[ "jti" ])
else :
return True
Two-Factor Authentication
Zou supports three 2FA methods:
TOTP (Time-based One-Time Password)
Authenticator app-based 2FA (Google Authenticator, Authy, etc.).
Enable TOTP
Pre-enable : Generate secret and QR code
def pre_enable_totp ( person_id ):
person = Person.get(person_id)
person.totp_secret = pyotp.random_base32()
totp = pyotp.TOTP(person.totp_secret)
organisation = persons_service.get_organisation()
totp_uri = totp.provisioning_uri(
name = person.email,
issuer_name = f "Kitsu { organisation[ 'name' ] } "
)
person.save()
return totp_uri, person.totp_secret
Enable : Verify code and activate
def enable_totp ( person_id , totp ):
person = Person.get(person_id)
if pyotp.TOTP(person.totp_secret).verify(totp):
person.totp_enabled = True
# Generate recovery codes
otp_recovery_codes = generate_recovery_codes() # 16 codes
person.otp_recovery_codes = hash_recovery_codes(otp_recovery_codes)
person.save()
return otp_recovery_codes
Login with TOTP
curl -X POST http://localhost:5000/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@studio.com",
"password": "password",
"totp": "123456"
}'
Email OTP
One-time code sent via email (HOTP-based).
def send_email_otp ( person ):
# Generate random counter
count = random.randint( 0 , 999999999999 )
otp = pyotp.HOTP(person[ "email_otp_secret" ]).at(count)
# Store counter in Redis (5 min TTL)
auth_tokens_store.add(
f "email-otp-count- { person[ 'email' ] } " ,
count,
ttl = 60 * 5
)
# Send email
emails.send_email(
subject = "Your login code" ,
body = f "Your code is: { otp } " ,
recipient = person[ "email" ]
)
FIDO2/WebAuthn
Hardware key or biometric authentication.
def pre_register_fido ( person_id ):
person = Person.get(person_id)
options, state = current_app.extensions[ "fido_server" ].register_begin(
PublicKeyCredentialUserEntity(
id = str (person.id).encode(),
name = person.email,
display_name = person.full_name
),
credentials = get_existing_credentials(person),
user_verification = "preferred" ,
authenticator_attachment = "cross-platform"
)
session[ f "fido-state- { person.id } " ] = state
return dict (options.public_key)
FIDO2 supports:
USB security keys (YubiKey, etc.)
Platform authenticators (Touch ID, Windows Hello)
Bluetooth/NFC devices
Mandatory 2FA Enforcement
Configuration
ENFORCE_2FA = true
2FA_EXEMPT_USERS = admin@studio.com,service@studio.com
When enabled, users without 2FA are restricted to setup endpoints until configured.
Enforcement Logic
@jwt.user_lookup_loader
def user_lookup_callback ( _ , payload ):
identity = persons_service.get_person_raw_cached(payload[ "sub" ])
if payload.get( "requires_2fa_setup" ):
allowed_paths = {
"/auth/totp" ,
"/auth/email-otp" ,
"/auth/fido" ,
"/auth/logout" ,
}
if request.path not in allowed_paths:
raise TwoFactorAuthenticationRequiredException()
return identity
Recovery Codes
When 2FA is enabled, users receive 16 single-use recovery codes:
def generate_recovery_codes ():
return [
'' .join(random.choice(string.ascii_uppercase + string.digits)
for _ in range ( 10 ))
for _ in range ( 16 )
]
def hash_recovery_codes ( recovery_codes ):
return [flask_bcrypt.generate_password_hash(code)
for code in recovery_codes]
Recovery codes are BCrypt-hashed and stored as an array.
Security Features
Password Requirements
MIN_PASSWORD_LENGTH = 8 # Configurable
Rate Limiting
Login attempts are rate-limited:
def check_login_failed_attemps ( person ):
login_failed_attemps = person[ "login_failed_attemps" ] or 0
if (
login_failed_attemps >= 5
and person[ "last_login_failed" ] + timedelta( minutes = 1 )
> datetime.now()
):
raise TooMuchLoginFailedAttemps()
return login_failed_attemps
After 5 failed attempts, accounts are locked for 1 minute.
Secret Key
JWT tokens are signed with a secret key:
SECRET_KEY = your-secret-key-here # Change in production!
Use a strong, randomly generated secret key in production. If the key is compromised, all tokens can be forged.
Identity Types
Zou supports three identity types:
Type Description Use Case personRegular user Human users logging in botService account Automated systems, integrations person_apiAPI key user Personal API tokens
Bot Accounts
Bots use long-lived tokens stored in the jti field:
class Person :
is_bot = db.Column(db.Boolean(), default = False )
jti = db.Column(db.String( 60 ), unique = True ) # Bot's permanent token ID
SAML SSO
Enterprise single sign-on support:
SAML_ENABLED = true
SAML_IDP_NAME = "Company SSO"
SAML_METADATA_URL = https://idp.company.com/metadata
SAML flow:
User clicks “Login with SSO”
Redirect to IdP (Identity Provider)
User authenticates at IdP
IdP redirects back with SAML assertion
Zou validates assertion and creates session
Best Practices
Store tokens in HTTP-only cookies to prevent XSS
Use SameSite=Lax cookie attribute
Never store tokens in localStorage (XSS vulnerable)
For mobile apps, use secure keychain/keystore
Keep access token lifetime short (hours/days)
Use refresh tokens for long-term sessions
Implement automatic token refresh before expiration
Clear tokens on logout
Encourage all users to enable 2FA
Enforce 2FA for admin/manager accounts
Provide recovery codes and keep them secure
Support multiple 2FA methods for flexibility
Next Steps
Permissions Learn about role-based access control
User Management Manage users and their access
Architecture Understand the system architecture
API Security Secure API integration guide