Add real two-factor authentication (TOTP) to Microsoft Exchange Server on-premises without Azure AD / Entra ID. Browser clients (OWA/ECP) and rich clients (Outlook desktop, macOS/iOS Mail) authenticate through AD FS, which delegates the login to an external identity provider - Keycloak - that enforces TOTP. Everything sits behind an HAProxy reverse proxy (SSL bridging) with an optional Coraza WAF.
This is a field-tested reference build. It complements Microsoft's Enable modern authentication in Exchange Server on-premises with the dozen non-obvious gotchas that the official guide leaves out (see docs/09-troubleshooting.md).
Author: Igor Batin (@igrbtn - batin.uz). Licensed under MIT - attribution required (keep the copyright notice).
Microsoft's cloud story for MFA is Entra ID. On-premises Exchange has no native MFA: OWA forms auth and Basic auth for rich clients are single-factor. The supported path to add MFA on-prem is claims/OAuth via AD FS - but AD FS itself has no built-in TOTP. This project chains a standards-based IdP (Keycloak) behind AD FS as a Claims Provider Trust, so:
- OWA / ECP redirect to AD FS -> Keycloak -> password + TOTP.
- Outlook desktop, EWS, MAPI, EAS, OAB use OAuth 2.0 -> AD FS -> Keycloak -> password + TOTP.
- No cloud dependency, no per-user Entra licensing, self-hosted TOTP.
Client --443/TLS--> HAProxy (SSL bridging, WAF) --re-encrypt--> backends:
mail / owa / ecp / autodiscover.corp.example -> Exchange
sts.corp.example -> AD FS
kc.corp.example -> Keycloak (TOTP)
AD FS --Claims Provider Trust (SAML)--> Keycloak --LDAP--> Active Directory
The diagram shows the default mode (HAProxy + Keycloak). Both the proxy and the second-factor engine are swappable - see Modes and paths.
Three independent enforcement layers:
| Layer | Clients | Mechanism |
|---|---|---|
| 1. Browser | OWA, ECP | WS-Federation claims -> AD FS -> Keycloak -> TOTP |
| 2. WAF | OWA web surface | Coraza + OWASP CRS v4 (DetectionOnly -> On) |
| 3. Rich client | Outlook, EWS, MAPI, EAS, OAB | OAuth 2.0 -> AD FS -> Keycloak -> TOTP |
| Protocol | Modern auth | Client | Modern auth | |
|---|---|---|---|---|
| MAPI/HTTP | Yes | Outlook Classic (Win) | Yes | |
| EWS | Yes | macOS Mail | Yes | |
| EAS | Yes | iOS Mail | Yes | |
| OAB | Yes | Outlook New (Win) | Falls back to Basic | |
| RPC/HTTP | No | Outlook iOS/Android | Falls back to Basic | |
| IMAP / POP | No | Gmail app | Falls back to Basic |
Rich-client modern auth on Windows requires Windows 11 22H2+ (OS-level WAM broker) and Outlook M365 Apps / 2021 Retail 2304+. See docs/01-prerequisites.md.
Two independent choices - the second-factor engine and the reverse proxy - combine freely. All are field-tested on the same lab.
| Mode | How | External components | Guide |
|---|---|---|---|
| Keycloak (external IdP) | AD FS delegates auth to Keycloak (SAML CPT); Keycloak enforces TOTP | a Keycloak server | 02 |
| Native AD FS TOTP | AD FS does the AD password + TOTP itself via an MFA adapter (adfsmfa); no external IdP, no SAML | none (AD attribute storage) | 11 |
| Path | Notes | Guide |
|---|---|---|
| HAProxy (default) | Linux, SSL bridging, Coraza WAF, security headers, rate limiting; handles the OWA WebSocket | 04 |
| AD FS WAP | Microsoft's native AD FS proxy on Windows; no WAF; browser OWA hangs on the notification WebSocket - front OWA with HAProxy | 10 |
| Hybrid | HAProxy in front of WAP - keeps the WAF + WebSocket handling and the supported AD FS proxy | 10 |
- Fastest working baseline: HAProxy + Keycloak - follow 02 -> 08.
- No external IdP: HAProxy + Native AD FS TOTP - do the Exchange/proxy setup (03-05) then 11 instead of the Keycloak steps.
- Microsoft-native proxy: WAP + either engine - 10. Because browser OWA hangs on WAP's WebSocket, use the hybrid (HAProxy in front) for the OWA path; rich clients (Outlook/EAS) work on WAP directly.
docs/ step-by-step guides (00..11)
scripts/ PowerShell (AD FS, Exchange, client, WAP, adfs-mfa) + Keycloak helpers
config/ HAProxy, Coraza WAF, Keycloak login theme (templates)
This is the default path (HAProxy + Keycloak). For the other modes, see Modes and paths above.
- Read docs/00-overview.md and docs/01-prerequisites.md.
- Second-factor engine - pick one: Keycloak docs/02-adfs-keycloak.md, or native AD FS TOTP docs/11-native-adfs-totp.md (no external IdP).
- Enable modern auth on Exchange: docs/03-exchange-modern-auth.md.
- Put a reverse proxy in front: docs/04-haproxy-ssl-bridging.md (HAProxy) or docs/10-wap-variant.md (AD FS WAP / hybrid).
- Wire browser 2FA (OWA/ECP): docs/05-browser-owa-ecp.md.
- Wire rich-client 2FA (Outlook): docs/06-rich-client-outlook.md.
- Mobile EAS + Keycloak theming: docs/07-eas-mobile.md, docs/08-keycloak-login-theme.md.
- When something breaks: docs/09-troubleshooting.md (+ the per-variant troubleshooting in 10/11).
The guides use placeholder names - substitute your own:
| Placeholder | Meaning | Example |
|---|---|---|
corp.example |
AD DNS domain | your AD forest |
CORP |
NetBIOS domain | your NetBIOS name |
sts.corp.example |
AD FS service FQDN | |
kc.corp.example |
Keycloak FQDN | |
mail.corp.example |
Exchange namespace | |
10.0.0.x |
example IPs | your subnet |
Passwords, thumbprints, and client secrets are always placeholders like
<STRONG_PASSWORD> / <THUMBPRINT>. Never commit real secrets.
Validated end-to-end: OWA/ECP browser TOTP, Coraza WAF on OWA, Outlook Classic modern auth (MAPI + EWS OAuth), macOS/iOS Mail EAS. Contributions and issues welcome.
MIT (c) 2026 Igor Batin. Attribution required - keep the copyright notice. See LICENSE.