Skip to main content

OAuth2 authentication for Microsoft 365 services (IMAP/SMTP/Calendar/etc)

Project description

M365-Auth: OAuth2 Authentication for Microsoft 365

A Python-based OAuth2 authentication tool for Microsoft 365 services including IMAP, SMTP, and Calendar access. Works with any mail client or application that supports OAuth2 authentication.

Based on the original M365-IMAP by Gerrit Oomens, which itself was forked from ag91/M365-IMAP.

Features

  • Universal OAuth2 support: Works with any M365 service (mail, calendar, etc.)
  • Multiple profiles: Separate token management for different services
  • Secure storage: Tokens stored in OS keychain (macOS Keychain, GNOME Keyring, Windows Credential Locker)
  • Cross-platform: Linux, macOS, Windows
  • Mail client support: mbsync, msmtp, OfflineIMAP, and any OAuth2-compatible client
  • Python library: Import and use in your own scripts
  • XDG compliant: Follows XDG Base Directory specification

Installation

Option 1: Using pipx (Recommended)

pipx installs Python CLI tools in isolated environments while making them globally available:

# Install pipx if you don't have it
brew install pipx  # macOS
# or: python3 -m pip install --user pipx

# Ensure pipx is in PATH
pipx ensurepath

# Install M365-Auth
git clone https://github.com/YOUR-USERNAME/M365-Auth
cd M365-Auth
pipx install .

Option 2: System-wide installation

git clone https://github.com/YOUR-USERNAME/M365-Auth
cd M365-Auth
pip3 install --user .
# Or without --user for system-wide (may require sudo)

Option 3: Development installation

For development or if you want to modify the code:

git clone https://github.com/YOUR-USERNAME/M365-Auth
cd M365-Auth
pip install -e .

Note: With development installation in a virtual environment, you'll need to either activate the venv or use full paths (e.g., /path/to/venv/bin/refresh-token) in your mail client configurations.

Commands installed

All installation methods create three commands:

  • get-token - Interactive OAuth2 flow to obtain tokens
  • refresh-token - Refresh and print access tokens
  • get-refresh-token - Get refresh token from keychain (for OfflineIMAP)

Quick Start

Step 1: Get a Client ID

To connect to Azure AD for authentication, you need a client ID and optionally a client secret (an "app registration" in Azure AD).

Understanding client secrets: Confusingly, the client secret doesn't actually need to be secret for mail clients and desktop applications. Public clients (like mail clients) can't keep secrets anyway - anyone can decompile the app and extract them. That's why many public clients use empty secrets or publicly-known ones.

Your options:

Option 1: Create your own app registration (recommended)

This gives you full control and ensures you have all the permissions you need:

  1. Go to Azure Portal → Azure Active Directory → App registrations
  2. Click "New registration"
    • Name: Something like "My Mail Client" or "Personal M365 Tools"
    • Supported account types: Choose based on your account type
    • Redirect URI: Web → https://localhost:7598
  3. After creation, copy the Application (client) ID
  4. Go to "API permissions" → "Add a permission" → "Microsoft Graph" → "Delegated permissions"
  5. Add the permissions you need (see API Permissions section below)
  6. Important: If using a work/school account, you may need admin consent - click "Grant admin consent" or ask your IT admin

Note: The redirect URI is where Azure AD sends the user after authentication. We run a local HTTPS server on port 7598 to catch this redirect.

Option 2: Use a public client ID

Some applications publish their client IDs in their source code, which you can use. For example:

  • Thunderbird: Available in their source code
  • Other mail clients: Check their public repositories or documentation

To find a public client ID:

  1. Look in the application's source code repository
  2. Search for files related to OAuth2 or Microsoft authentication
  3. Look for strings that match the format of Azure AD client IDs (typically 36-character GUIDs)

Pros of using public client IDs:

  • No need to create your own app registration
  • Often already approved by IT departments for common applications
  • Works immediately for personal accounts

Cons:

  • You don't control the permissions
  • May not have calendar or other non-mail permissions
  • Could theoretically be revoked by the original publisher
  • You're using an ID intended for a different application

Whatever client ID you use, it needs to have been granted the appropriate permissions in your M365 tenant (for work/school accounts) or support the permissions you need (for personal accounts).

Step 2: Configure

On first run, get-token will automatically create a default configuration file at ~/.config/m365auth/config.py. You must edit this file to add your client ID:

# The config file will be created automatically on first run
# Edit it to add your client ID
nano ~/.config/m365auth/config.py

Set ClientId = "your-client-id-here" in the config file.

The default config includes two profiles:

  • mail: IMAP.AccessAsUser.All, SMTP.Send
  • calendar: Calendars.ReadWrite

Step 3: Get Your Tokens

Understanding OAuth2 tokens: The OAuth2 flow gives you two types of tokens:

  • Refresh token: Long-lived (months/years), used to get new access tokens. Stored securely in your OS keychain.
  • Access token: Short-lived (~1 hour), used to actually access your mail/calendar. Never persisted to disk for security.

Mail clients need fresh access tokens frequently, so they call refresh-token which uses your stored refresh token to get a new access token each time.

Get your initial tokens:

# For mail clients (IMAP/SMTP)
get-token --profile mail

# For calendar access
get-token --profile calendar

# Over SSH with port forwarding
get-token --profile mail --server

What happens:

  1. The script opens your browser to Microsoft's login page
  2. You authenticate with your Microsoft account
  3. Microsoft redirects to https://localhost:7598 with an authorization code
  4. The script exchanges this code for tokens using Microsoft's MSAL library
  5. The refresh token is stored in your OS keychain (encrypted, secure)
  6. The access token is printed (but not saved - you don't need it, refresh-token will generate new ones)

Security note: The refresh token allows access to your full mailbox/calendar (depending on permissions), so it's stored in your system's secure keychain, not a plain file. Even if someone gets filesystem access, they can't use your credentials without also accessing your keychain.

Usage Examples

mbsync (isync)

IMAPAccount account
Host outlook.office365.com
User your.email@example.com
PassCmd "refresh-token --profile mail"
AuthMechs XOAUTH2
SSLType IMAPS

msmtp

account myaccount
host smtp.office365.com
port 587
from your.email@example.com
user your.email@example.com
auth xoauth2
passwordeval "refresh-token --profile mail"
tls on
tls_starttls on

OfflineIMAP

[Repository Remote]
type = IMAP
remotehost = outlook.office365.com
remoteuser = your.email@example.com
auth_mechanisms = XOAUTH2
oauth2_request_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
oauth2_client_id = <your client ID>
oauth2_client_secret =
oauth2_refresh_token_eval = get-refresh-token --profile mail

# Optional: filter non-mail folders
folderfilter = lambda folder: not folder.startswith('Calendar') and not folder.startswith('Contacts')

Python Scripts

from m365auth import get_access_token

# Get access token for mail
token = get_access_token('mail')

# Get access token for calendar
token = get_access_token('calendar')

# Use with requests
headers = {'Authorization': f'Bearer {token}'}
response = requests.get('https://graph.microsoft.com/v1.0/me/calendar', headers=headers)

API Permissions

Common Microsoft Graph delegated permissions:

Mail:

  • https://outlook.office.com/IMAP.AccessAsUser.All - IMAP access
  • https://outlook.office.com/SMTP.Send - SMTP sending

Calendar:

  • https://graph.microsoft.com/Calendars.ReadWrite - Calendar read/write

Other:

  • https://graph.microsoft.com/Mail.ReadWrite - Mail via Graph API
  • https://graph.microsoft.com/Calendars.Read - Calendar read-only

Note: Azure AD doesn't allow mixing outlook.office.com and graph.microsoft.com scopes in one token. Use separate profiles for different resource types.

Configuration

Directory Structure

  • Config: ~/.config/m365auth/config.py (Linux) or platform equivalent
  • Cache: ~/.cache/m365auth/ (Linux) or platform equivalent
    • Auto-generated SSL certificates for OAuth callback server
  • Keychain: OS-native secure storage
    • Refresh tokens stored as m365auth-{profile}

Custom Profiles

Edit ~/.config/m365auth/config.py:

Profiles = {
    'mail': {
        'scopes': [
            'https://outlook.office.com/IMAP.AccessAsUser.All',
            'https://outlook.office.com/SMTP.Send'
        ]
    },
    'calendar': {
        'scopes': [
            'https://graph.microsoft.com/Calendars.ReadWrite'
        ]
    },
    'custom': {
        'scopes': [
            # Your custom scopes here
        ]
    }
}

Tenant-Specific Configuration

For single-tenant (work/school) accounts:

Authority = "https://login.microsoftonline.com/YOUR-TENANT-ID/"

Security

  • Refresh tokens: Stored encrypted in OS keychain
  • Access tokens: Never persisted to disk - only printed to stdout or held in memory
  • File fallback: For headless environments, tokens stored in ~/.cache/m365auth/ with mode 600
  • Benefit: Even with filesystem access, attackers need keychain access to get credentials

SSH / Headless Usage

When running over SSH, the browser can't reach localhost:7598 on the remote machine. Two options:

Option 1: SSH Port Forwarding (recommended)

SSH port forwarding creates a tunnel so that localhost:7598 on your local machine connects to localhost:7598 on the remote machine.

Step-by-step:

# Step 1: On your local machine, connect with port forwarding
ssh -L 7598:localhost:7598 user@remote-host

# Step 2: On the remote machine (in the SSH session)
get-token --profile mail --server

# Step 3: The script will print a URL and open it in your local browser
# (The --server flag tells the script to start the HTTPS server even over SSH)

# Step 4: Authenticate in your browser
# The redirect to localhost:7598 will go through the SSH tunnel to the remote machine

# Step 5: Done! The token is stored in the remote machine's keychain

How it works:

  • -L 7598:localhost:7598 creates a tunnel from local port 7598 to remote port 7598
  • When Microsoft redirects to https://localhost:7598, your browser connects to your local port 7598
  • SSH forwards that connection through the tunnel to the remote machine
  • The remote machine's OAuth server receives the authorization code

Option 2: Manual URL Entry (for existing SSH sessions)

If you're already connected via SSH without port forwarding:

# On the remote machine
get-token --profile mail

# Script detects SSH (no --server flag) and skips starting the server
# It prints the OAuth URL and waits

# Open the URL in your local browser and authenticate
# Microsoft will redirect to https://localhost:7598/?code=...
# This will fail to load (that's expected!)

# Copy the entire error page URL from your browser
# Paste it back into the SSH terminal

# Done! The script extracts the code and completes authentication

Note: For headless servers with no browser access at all, you need to open the URL on any machine with a browser, then paste the redirect URL back to the server.

Troubleshooting

"No refresh token found"

# Run get-token first to authenticate
get-token --profile mail

"Cannot store in keychain"

  • File fallback is automatic
  • Tokens stored in ~/.cache/m365auth/refresh_token_{profile}
  • On headless Linux, consider installing gnome-keyring

"Permission denied" errors

  • Your Azure app needs the required API permissions
  • Contact your IT admin to grant consent

SSL certificate warnings

  • Normal for self-signed certificates on localhost
  • Safe to click through during OAuth flow

Contributing

Contributions welcome! This is a fork of the original M365-IMAP project, extended to support multiple services beyond just IMAP.

License

MIT License - see original M365-IMAP repository.

Credits

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

m365auth-0.2.0.tar.gz (12.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

m365auth-0.2.0-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file m365auth-0.2.0.tar.gz.

File metadata

  • Download URL: m365auth-0.2.0.tar.gz
  • Upload date:
  • Size: 12.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for m365auth-0.2.0.tar.gz
Algorithm Hash digest
SHA256 16db7f048b2d2d50dc63d3840424ab06e741e05b58adaca530dd117af54332e3
MD5 a3003f495646c3ce75b9d32c1c916ecb
BLAKE2b-256 7eeab23f7c7aa42041ef9c93351b148c4f7803a2e59ddc0b5b5faec2eb331374

See more details on using hashes here.

File details

Details for the file m365auth-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: m365auth-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 12.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for m365auth-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e9af17eb726f52ea568b2e86ca2b6ce43a8bd7dbdf1aa412c9e56e53a3a32a45
MD5 59fd8bf5a440b53ad77f70041ef0cda3
BLAKE2b-256 8cf62b711169d981483c00c21249255ca34721e97dc3e0ab4078f2d2531015bf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page