Enterprise-grade SAML 2.0 authentication app for Frappe where Frappe acts as the Service Provider (SP) and an external IdP (Azure AD, Keycloak, bank IdP, etc.) handles identity.
This app is designed to be:
- Reusable across projects
- Secure by default
- Safe for bench updates (no Frappe core changes)
- Docker-friendly
- SAML endpoints:
/saml/login(SP-initiated login)/saml/acs(Assertion Consumer Service)/saml/metadata(dynamic SP metadata)/saml/logout(local logout)
- Strict OneLogin python-saml validation
- HTTPS enforcement for ACS
- Signed assertion/message requirements
- Replay attack prevention (assertion ID cache)
- Optional SAML-only mode to block password login
- Automatic user provisioning and role assignment
- Safe audit logging (no raw assertions in logs)
frappe_saml/
├── frappe_saml/
│ ├── __init__.py
│ ├── hooks.py
│ ├── saml/
│ │ ├── settings.py
│ │ ├── auth.py
│ │ ├── validation.py
│ │ ├── user_provision.py
│ │ ├── roles.py
│ │ └── audit.py
│ ├── doctype/
│ │ └── saml_settings/
│ │ ├── saml_settings.json
│ │ ├── saml_settings.py
│ │ └── saml_settings.js
├── www/
│ └── saml/
│ ├── login.py
│ ├── acs.py
│ ├── metadata.py
│ └── logout.py
- Frappe/ERPNext bench environment
- Python 3.10+
- HTTPS-enabled reverse proxy (Nginx/Traefik/Ingress)
- IdP metadata XML (or XML string source)
If your bench apps folder is mounted, clone/copy this app in the bench apps directory.
pip install python3-samlIf your deployment pins requirements per app, add
python3-samlto the app requirements file used by your CI/CD image.
bench --site <your-site> install-app frappe_samlbench restartConfiguration sources (merged):
site_config.json(primary)- Optional
SAML SettingsSingle DocType
{
"saml": {
"enabled": true,
"entity_id": "https://frappe.example.com",
"acs_url": "https://frappe.example.com/saml/acs",
"idp_metadata_xml": "<EntityDescriptor ...>...</EntityDescriptor>",
"nameid_format": "emailAddress",
"saml_only": true,
"default_roles": ["Employee"]
}
}enabled: Enable/disable SAML integrationentity_id: SP Entity ID used by the IdP trust configacs_url: Must be HTTPS; IdP posts SAML responses hereidp_metadata_xml: Full IdP metadata XMLnameid_format: NameID format (e.g.,emailAddress)saml_only: Disable password login in Frappe when truedefault_roles: Roles assigned on login if missing
- Create Enterprise Application.
- Configure SAML SSO.
- Set:
- Identifier (Entity ID) = your
entity_id - Reply URL (ACS) = your
acs_url
- Identifier (Entity ID) = your
- Add claim mappings:
emailfirst_namelast_name
- Download federation metadata XML and place in
idp_metadata_xml.
- Create realm/client for SAML.
- Set client protocol
saml. - Configure:
- Client ID = your
entity_id - Valid Redirect URI = your
acs_url
- Client ID = your
- Ensure response/assertions are signed.
- Export metadata XML and apply to
idp_metadata_xml.
| SAML Attribute | Frappe Field |
|---|---|
email |
User.email |
first_name |
User.first_name |
last_name |
User.last_name |
Behavior:
- Email is unique key
- Missing users are auto-created as
System User - Default roles are assigned idempotently
- Builds AuthnRequest and redirects to IdP
- Preserves
nextpath via RelayState
- Validates SAML response
- Enforces signature/issuer/audience/time checks
- Enforces replay prevention
- Provisions/maps user and creates Frappe session
- Redirects to RelayState or
/app
- Generates and returns SP metadata XML
- Useful for IdP onboarding and trust setup
- Logs out local Frappe session
- Redirects to SAML login
Implemented controls:
- HTTPS required for ACS
- Strict SAML validation mode
- Signed assertions/messages enforced
- Assertion expiry and skew checks
- Replay attack prevention via cached assertion IDs
- Optional password-login blocking (
saml_only=true) - Sanitized audit logging only (no full assertion payloads)
Operational recommendations:
- Restrict access to
site_config.json - Rotate IdP signing certs with metadata refresh process
- Keep server time synchronized (NTP)
- Enforce TLS 1.2+ at proxy/load balancer
- Ensure container image includes
python3-saml. - Mount or inject
site_config.jsonper site securely. - Set
X-Forwarded-Proto httpscorrectly so app sees HTTPS. - Run behind a trusted reverse proxy with TLS termination.
Example (conceptual):
- app container + worker + scheduler
- reverse proxy with HTTPS
- Redis for cache/session (used by replay detection cache)
After deployment, validate in staging:
- Open
/saml/loginand confirm redirect to IdP. - Authenticate with IdP and confirm redirect to Desk.
- Verify new users are auto-created with mapped names.
- Verify default roles are assigned.
- Verify
/saml/metadatareturns valid XML. - Replay same SAML response and confirm rejection.
- Set
saml_only=trueand verify/loginpassword login is blocked. - Inspect logs and confirm no raw SAML assertion content is logged.
- Ensure
saml.enabledistrueinsite_config.json.
- Use HTTPS ACS URL and configure proxy headers correctly.
- Verify IdP metadata/certificate is current.
- Confirm Entity ID and ACS URL exactly match IdP config.
- Confirm response/assertion signing is enabled at IdP.
- Ensure SAML includes
email(or NameID resolves to email). - Confirm user is not disabled.
# install app
bench --site <site> install-app frappe_saml
# run migrations (if needed)
bench --site <site> migrate
# clear cache
bench --site <site> clear-cache
# restart services
bench restartThis app is designed to support enterprise SSO compliance requirements, but final compliance posture depends on your deployment controls, IdP policy, key management, logging governance, and operational procedures.