2025, Dec 05 21:02

Как добавить правила Outlook через Microsoft Graph: отказ от ROPC и правильный Authorization Code Flow

Разбираемся, почему ROPC даёт AADSTS50034 для личных Outlook‑аккаунтов и как через Authorization Code Flow получить refresh‑токен и создать правила в Graph.

Когда вы пытаетесь добавить правила почтового ящика Outlook через Microsoft Graph для личных учетных записей Outlook, может возникнуть соблазн напрямую обменять логин и пароль на токен или переиспользовать существующий refresh‑токен от другого клиента. Такой путь часто приводит к ошибке AADSTS50034 и тупиковым ситуациям. Ниже — почему это происходит и как простым способом получить рабочий refresh‑токен и стабильно создавать правила.

Что идет не так

Попытка ниже использует поток Resource Owner Password Credentials (ROPC) для конечной точки токенов, привязанной к конкретному тенанту. Для личной учетной записи Outlook, которая не принадлежит вашему тенанту Azure AD, это завершается ошибкой поиска в каталоге.

Учетная запись пользователя {EUII Hidden} не существует в каталоге a7163cca-35ea-483a-837e-ae68f9820cff. Чтобы войти в это приложение, учетную запись нужно добавить в каталог.

Иными словами, конечная точка токенов, ограниченная конкретным тенантом, аутентифицирует только пользователей этого тенанта. Даже при обращении к /common или /consumers ROPC больше не поддерживается для ряда сценариев, особенно для личных аккаунтов. Значит, вам нужен Authorization Code Flow, чтобы один раз интерактивно получить первый refresh‑токен, а затем автоматизировать процесс.

Демонстрация проблемы

Это шаблон, который вызывает AADSTS50034, когда используется личная учетная запись Outlook, а конечная точка привязана к тенанту.

import requests
def ropc_token_exchange(dir_guid, app_client_id, app_client_secret, user_login, user_pwd):
    token_endpoint = f="https://login.microsoftonline.com/{dir_guid}/oauth2/v2.0/token"
    form = {
        "grant_type": "password",
        "client_id": app_client_id,
        "client_secret": app_client_secret,
        "scope": "https://graph.microsoft.com/.default",
        "username": user_login,
        "password": user_pwd
    }
    res = requests.post(token_endpoint, data=form)
    return res.json()

reply = ropc_token_exchange(
    dir_guid=TENANT_GUID,
    app_client_id=APP_ID,
    app_client_secret=APP_SECRET,
    user_login="JobyTrible235@outlook.com",
    user_pwd="5Bfhac7yKB"
)

Ранее нестандартный обмен логина и пароля на конечных точках live.com и common мог давать access‑токен. Этот подход перестал работать и не является поддерживаемым способом получения refresh‑токенов для личных аккаунтов.

Почему это не работает

Личная учетная запись Outlook не является частью вашего тенанта Azure AD, поэтому конечная точка токенов, привязанная к тенанту, не может ее найти и аутентифицировать. Кроме того, ROPC ограничен и не поддерживается для отдельных потоков, в частности для личных аккаунтов. В результате получить refresh‑токен таким способом не получится.

Решение: интерактивный Authorization Code Flow, затем автоматизация

Правильный подход — выполнить однократный интерактивный вход по Authorization Code Flow, чтобы получить исходный refresh‑токен с нужными областями доступа. После этого можно использовать refresh‑токен для получения access‑токенов и вызывать Microsoft Graph для создания правил почтового ящика.

import requests
import webbrowser
import threading
from flask import Flask, request

# Параметры приложения
APP_CLIENT_ID = "client_id_of your_app"
APP_CLIENT_SECRET = "client_secret"
LOCAL_REDIRECT = "http://localhost:8000/callback"
REQ_SCOPES = "offline_access Mail.ReadWrite MailboxSettings.ReadWrite"

authorize_page = (
    f="https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
    f="?client_id={APP_CLIENT_ID}"
    f="&response_type=code"
    f="&redirect_uri={LOCAL_REDIRECT}"
    f="&response_mode=query"
    f="&scope={REQ_SCOPES}"
    f="&state=12345"
)

# Локальная конечная точка для перехвата кода авторизации
srv = Flask(__name__)
auth_code_value = None

@srv.route('/callback')
def oauth_return():
    global auth_code_value
    auth_code_value = request.args.get('code')
    return "Authorization code received. You may close this tab."

def start_http():
    srv.run(port=8000)

threading.Thread(target=start_http, daemon=True).start()
print("Opening browser for login...")
webbrowser.open(authorize_page)

# Ждем, пока не получим код
while auth_code_value is None:
    pass

# Обмениваем код на токены
token_endpoint = "https://login.microsoftonline.com/consumers/oauth2/v2.0/token"
payload = {
    "client_id": APP_CLIENT_ID,
    "client_secret": APP_CLIENT_SECRET,
    "grant_type": "authorization_code",
    "code": auth_code_value,
    "redirect_uri": LOCAL_REDIRECT,
    "scope": REQ_SCOPES,
}

resp = requests.post(token_endpoint, data=payload)
token_bundle = resp.json()
print("Access Token:", token_bundle.get("access_token", ""))
print("Refresh Token:", token_bundle.get("refresh_token", ""))

# Используем access‑токен для создания правила почтового ящика
access = token_bundle.get("access_token")

if access:
    rules_endpoint = "https://graph.microsoft.com/v1.0/me/mailFolders/inbox/messageRules"
    headers = {
        "Authorization": f"Bearer {access}",
        "Content-Type": "application/json"
    }
    rule_body = {
        "displayName": "Test Rule",
        "sequence": 1,
        "conditions": {
            "subjectContains": ["phishing"]
        },
        "actions": {
            "moveToFolder": "junkemail",
            "stopProcessingRules": True
        },
        "isEnabled": True
    }
    r = requests.post(rules_endpoint, headers=headers, json=rule_body)
    print("Rule creation status:", r.status_code)
    print("Response:", r.json())
else:
    print("Failed to get access token.")

Эта последовательность дает refresh‑токен, который можно переиспользовать для последующих запросов access‑токена, и позволяет вызывать Microsoft Graph для добавления правил почтового ящика.

Почему это важно

Область применимости конечной точки токенов, тип учетной записи и выбор потока тесно связаны в Microsoft Identity. Конечная точка, привязанная к конкретному тенанту, работает только для пользователей этого тенанта. ROPC — непригодный путь для личных аккаунтов. Authorization Code Flow обеспечивает обнаружение учетной записи и согласие с нужными областями, а также возвращает refresh‑токен, необходимый для автоматизации.

Выводы

Для личных учетных записей Outlook избегайте tenant‑scoped ROPC и начинайте с интерактивного Authorization Code Flow. Используйте конечную точку авторизации /common и обменивайте код на /consumers. Запрашивайте конкретные области Microsoft Graph, которые вам нужны, включая offline_access, чтобы получить refresh‑токен. С этим вы сможете программно создавать правила через Microsoft Graph без ошибок каталога или grant_type.