Microsoft has replaced basic authentication with OAuth2 for enhanced security, breaking existing Python applications using imaplib
with simple username/password logins. As a result, imaplib
can no longer connect to Outlook personal accounts directly.
To address this, Python applications need to adopt OAuth2 for authentication. This guide details how I updated my script to use requests_oauthlib
for OAuth2 and Microsoft Graph API to access my inbox.
Log in to the Azure Portal using your Microsoft account.
Register your app:
http://localhost:8000/callback
.(This is an important value. Since you need to run a localhost in your machine to perform one time account authentication.Save the registration and note your Client ID.
Navigate to API Permissions in your app registration.
Add Microsoft Graph > Delegated permissions:
Mail.Read
: To read user mail.Mail.ReadWrite
: To read and write user mail.User.Read
: To read the user profile.Grant admin consent if required.(There is no need for it in personal email accounts)
Replace imaplib
with requests_oauthlib
for OAuth2 support:
import os
from flask import Flask, request, Response
import threading
from requests_oauthlib import OAuth2Session
import requests
# Allow HTTP for local testing (not recommended for production)
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
# Configuration
CLIENT_ID = 'XXXXXXXXXXXXXXXXXXX' # Replace with your Application (client) ID
REDIRECT_URI = 'http://localhost:8000/callback'
AUTH_BASE_URL = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize'
TOKEN_URL = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token'
SCOPE = ['https://graph.microsoft.com/Mail.ReadWrite']
# Create an OAuth2 session
oauth = OAuth2Session(CLIENT_ID, redirect_uri=REDIRECT_URI, scope=SCOPE)
# Step 1: Get the authorization URL and print it
authorization_url, state = oauth.authorization_url(AUTH_BASE_URL)
print(f'Please go to this URL and authorize access: {authorization_url}')
# Step 2: Start a Flask server to capture the redirect response
app = Flask(__name__)
auth_code = None
@app.route('/callback')
def callback():
global auth_code
auth_code = request.url # Capture the full URL with the authorization code
print('Authorization code received!')
def shutdown_server():
func = request.environ.get('werkzeug.server.shutdown')
if func is not None:
func()
threading.Thread(target=shutdown_server).start()
return 'Authorization code received! You can close this tab.'
# Run Flask server in a separate thread
server_thread = threading.Thread(target=app.run, kwargs={'port': 8000})
server_thread.start()
# Wait for the authorization code to be set by the Flask server
while auth_code is None:
pass
# Step 3: Fetch the access token using the captured authorization code
token = oauth.fetch_token(
TOKEN_URL,
authorization_response=auth_code,
client_id=CLIENT_ID,
include_client_id=True
)
print('Access token obtained successfully! Lets continue')
# Proceed with API requests or other logic
# Step 4: Use the access token to access the user's mailbox
# Retrieve the first three emails from the inbox
headers = {'Authorization': f'Bearer {token["access_token"]}'}
response = requests.get('https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messages?$top=3&$select=id,from,subject,body', headers=headers)
if response.status_code == 200:
emails = response.json().get('value', [])
if emails:
print("\nDisplaying the first three emails:\n")
for i, email in enumerate(emails, 1):
print(f"Email {i}:")
print(f" ID: {email.get('id', 'N/A')}")
print(f" From: {email.get('from', {}).get('emailAddress', {}).get('address', 'N/A')}")
print(f" Subject: {email.get('subject', 'No Subject')}")
# Extract the body content and display only text (no HTML)
body_content = email.get('body', {}).get('content', '')
if email.get('body', {}).get('contentType', '').lower() == 'html':
# Strip HTML tags if the content type is HTML
from bs4 import BeautifulSoup
body_content = BeautifulSoup(body_content, 'html.parser').get_text()
print(f" Body (Text Only): {body_content.strip()[:500]}") # Limit to 500 chars for readability
print("\n" + "-"*50 + "\n")
else:
print('No emails found.')
else:
print(f"Failed to fetch emails. Status code: {response.status_code}")
print("Response content:", response.text)