79151091

Date: 2024-11-02 16:36:44
Score: 0.5
Natty:
Report link

BLUF (Bottom Line Up Front):

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.

Step-by-Step Guide:

1. Register Your Application in Azure:

  1. Log in to the Azure Portal using your Microsoft account.

  2. Register your app:

    • Go to Azure AD > App registrations > New registration.
    • Provide a name (e.g., Outlook Personal App).
    • Choose Accounts in any organizational directory and personal Microsoft accounts.
    • Add a Redirect URI: 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.
  3. Save the registration and note your Client ID.

  4. Navigate to API Permissions in your app registration.

  5. 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.
  6. Grant admin consent if required.(There is no need for it in personal email accounts)

3. Implement OAuth2 Authentication and Email Access in you Python code:

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)
Reasons:
  • Blacklisted phrase (1): This guide
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (1):
Posted by: Alex Plinio