Skip to content

Add PhoneAppOTP (TOTP) support for MFA authentication#445

Open
tuaris wants to merge 1 commit intomguessan:masterfrom
tuaris:feature/phoneapp-otp-totp-support
Open

Add PhoneAppOTP (TOTP) support for MFA authentication#445
tuaris wants to merge 1 commit intomguessan:masterfrom
tuaris:feature/phoneapp-otp-totp-support

Conversation

@tuaris
Copy link

@tuaris tuaris commented Mar 5, 2026

Support PhoneAppOTP (SoftwareTokenBasedTOTP) as an MFA method in O365Authenticator, with optional automatic TOTP code generation.

EDIT: I just noticed there was an existing PR for this feature: #405. The difference with mine is the support for multiple users/accounts.

Changes

  • Recognize PhoneAppOTP in the MFA method selection loop (highest priority)
  • Handle PhoneAppOTP in retrieveSmsCode() with interactive prompt
  • Handle PhoneAppOtpAuthFailedDuplicateCodeEntered in EndAuth polling loop
  • Add optional davmail.oauth.totpSecret property for automatic TOTP code generation using RFC 6238 (HMAC-SHA1) with standard Java crypto (no external dependencies)

When davmail.oauth.totpSecret is set with a Base32-encoded TOTP secret, DavMail generates codes automatically, enabling fully headless daemon operation without stdin interaction. When not set, falls back to interactive prompt (stdin in server/headless mode, GUI dialog otherwise).

This fixes authentication for accounts where PhoneAppOTP is the default MFA method. Previously DavMail silently skipped it and fell back to OneWaySMS, which can be rate-limited by Microsoft (BadReputation error).

New configuration properties

# Optional: Base32-encoded TOTP secret for automatic MFA code generation.
# When set, DavMail generates TOTP codes automatically — no manual input needed.
# When not set, DavMail prompts for the code on stdin (server/headless) or via dialog (GUI).

# Global secret (used as fallback for all users)
davmail.oauth.totpSecret=

# Per-user secrets (checked first, before the global fallback)
# [email protected]=AAABBBCCC111222
# [email protected]=DDDEEEFFF333444

Lookup order: davmail.oauth.totpSecret.<username>davmail.oauth.totpSecret → interactive prompt.

Usage

Single-user setup

Add the TOTP secret to davmail.properties:

davmail.oauth.totpSecret=YOUR_BASE32_TOTP_SECRET

Start DavMail normally. When MFA is required, DavMail generates the TOTP code automatically.

Multi-user setup

Configure per-user secrets:

davmail.oauth.totpSecret.alice@contoso.com=AAABBBCCC111222
davmail.oauth.totpSecret.bob@contoso.com=DDDEEEFFF333444

Each user's TOTP code is generated from their own secret. Users without a configured secret will be prompted interactively.

TOTP secret encryption

TOTP secrets follow the same encrypt-on-first-use pattern as OAuth refresh tokens, using
the existing StringEncryptor class (PBE with AES-128, keyed by the user's O365 password).

On first authentication, if the TOTP secret is stored in plaintext, DavMail automatically
encrypts it and saves it back to the config file with an {AES} prefix:

# Before first use (plaintext):
davmail.oauth.totpSecret=XXXXXXXXXXXXXXX

# After first use (encrypted automatically):
davmail.oauth.totpSecret={AES}base64encrypteddata...

On subsequent authentications, the {AES} prefix is detected and the secret is decrypted
using the user's password before generating the TOTP code.

Note: The DavMail process must have write access to davmail.properties for
encrypt-on-first-use to work. Ensure the config file is owned by the user running DavMail.

Interactive mode (no secret configured)

If no totpSecret is configured for a user, DavMail falls back to:

  • Server/headless mode: prompts Enter TOTP code: on stdin
  • GUI mode: shows a password dialog

This allows the first interactive login to cache a refresh token for subsequent daemon runs.

Important: OAuth client ID

For headless/server deployments, you will likely need to use the Microsoft Office client
ID (d3590ed6-52b3-4102-aeff-aad2292ab01c) instead of DavMail's default client ID.
DavMail's built-in client ID (facd6cff-a294-4415-b59f-c5b01937d7bd) triggers an
Azure AD consent prompt (arrScopes check) that cannot be completed without a browser.
The Microsoft Office client ID is pre-consented in all Azure AD tenants and requires
the redirect URI urn:ietf:wg:oauth:2.0:oob:

davmail.oauth.clientId=d3590ed6-52b3-4102-aeff-aad2292ab01c
davmail.oauth.redirectUri=urn:ietf:wg:oauth:2.0:oob

Support PhoneAppOTP (SoftwareTokenBasedTOTP) as an MFA method in
O365Authenticator, with optional automatic TOTP code generation.

Changes:
- Recognize PhoneAppOTP in the MFA method selection loop (highest priority)
- Handle PhoneAppOTP in retrieveSmsCode() with interactive prompt
- Handle PhoneAppOtpAuthFailedDuplicateCodeEntered in EndAuth polling loop
- Add optional davmail.oauth.totpSecret property for automatic TOTP code
  generation using RFC 6238 (HMAC-SHA1) with standard Java crypto (no
  external dependencies)

When davmail.oauth.totpSecret is set with a Base32-encoded TOTP secret,
DavMail generates codes automatically, enabling fully headless daemon
operation without stdin interaction. When not set, falls back to
interactive prompt (stdin in server/headless mode, GUI dialog otherwise).

This fixes authentication for accounts where PhoneAppOTP is the default
MFA method. Previously DavMail silently skipped it and fell back to
OneWaySMS, which can be rate-limited by Microsoft (BadReputation error).
@tuaris tuaris force-pushed the feature/phoneapp-otp-totp-support branch from 289736c to 933c19e Compare March 5, 2026 03:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant