Аутентификация Entra ID в WinForms и WPF: MSAL.NET и брокер WAM
· Обновлено: · Го Комура · Windows, C#, .NET, WinForms, WPF, Entra ID, Аутентификация, Безопасность, Техническая консультация
История изменений (1 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- Первая публикация
Цитирование статьи(DOI (зарегистрированный архив): 10.5281/zenodo.21619954)
Приведённые ниже DOI относятся к ранее зарегистрированным архивным версиям, которые могут отличаться от текущего текста. Для ссылки на текущий текст используйте URL этой страницы.
Го Комура (2026). Аутентификация Entra ID в WinForms и WPF: MSAL.NET и брокер WAM. KomuraSoft LLC. https://comcomponent.com/ru/blog/winforms-wpf-entra-id-auth/
- DOI (зарегистрированный архив)
- 10.5281/zenodo.21619954
- DOI (последняя зарегистрированная версия)
- 10.5281/zenodo.21619955
«Для каждого внутреннего приложения рисуем свой экран входа и сами храним пароли в своей базе. Когда кто-то увольняется, учётку приходится отключать в каждом приложении отдельно — это выматывает». «Microsoft 365 уже стоит на всю компанию: нельзя ли пускать людей под той же учётной записью?» Такие запросы по доработке десктопных приложений за последние годы заметно участились. Иногда формулировка другая: ИТ-служба на фоне утечки паролей или курса на Zero Trust прямо просит «перестать самим вести пароли».
Если коротко: в организации с Microsoft 365 перенести вход во внутренние приложения WinForms / WPF на Entra ID (бывший Azure AD) — разумное вложение. Приложение больше не принимает пароли. MFA, условный доступ и журналы входа на стороне тенанта начинают действовать и на внутренние приложения. Реализация укладывается в библиотеку MSAL.NET и несколько десятков строк кода.
У десктопных приложений при этом есть свои ловушки. Старый приём «принять имя и пароль в текстовых полях и проверить их за кулисами» (ROPC) официально уходит в deprecated — в новой разработке его брать нельзя. Если не сохранять кэш токенов, экран входа всплывает при каждом запуске. На Windows сильно меняются и удобство, и безопасность в зависимости от того, используете ли вы брокер (WAM). Ниже — от минимального набора понятий через регистрацию приложения, реализацию на MSAL.NET, WAM и кэш до критериев внедрения и типичных сюрпризов в эксплуатации.
Для кого эта статья и что предполагается
| Параметр | Содержание |
|---|---|
| Читатель | Разработчик, который добавляет вход под учётной записью Microsoft 365 в существующее или новое бизнес-приложение WinForms / WPF |
| Со стороны тенанта | Microsoft 365 / Entra ID уже внедрены. Регистрация приложения и согласие администратора (глава 3) — работа на стороне тенанта. Если сами это сделать не можете, главу 3 можно отдать в ИТ как готовую заявку |
| Среда выполнения | Windows. Для брокера WAM (глава 5) нужны Windows 10 (1703) или новее / Windows Server 2019 или новее. На более старых версиях, macOS и Linux MSAL сам уходит в браузер1 |
| .NET | .NET Framework 4.6.2 или новее, либо .NET 6 или новее. Браузер по умолчанию для интерактивного входа зависит от платформы (глава 4)2 |
| Пакеты NuGet | Microsoft.Identity.Client (обязателен), Microsoft.Identity.Client.Broker (для WAM; MSAL.NET 4.52.0 или новее)1, Microsoft.Identity.Client.Extensions.Msal (сохранение кэша токенов)3 |
| Сеть | Для первого входа и обновления токена нужна доступность Entra ID. В полностью офлайн-среде схема не работает (глава 8) |
Аббревиатуры в этой статье
| Сокращение | Полное имя | Что имеется в виду здесь |
|---|---|---|
| MSAL | Microsoft Authentication Library | Библиотека аутентификации Microsoft. Для .NET это MSAL.NET (пакет NuGet Microsoft.Identity.Client) |
| SSO | Single Sign-On (единый вход) | Один раз вошли — в другом приложении вводить учётные данные заново не нужно |
| MFA | Multi-Factor Authentication (многофакторная аутентификация) | Кроме пароля требуется второй фактор: приложение на телефоне, биометрия и т. п. |
| FIDO | Fast IDentity Online | Стандарт входа без пароля. В этой статье — USB-ключ безопасности и аналоги |
| JWT | JSON Web Token | Подписанный JSON, который несёт утверждения (claims) о пользователе. В таком виде живут ID-токен и токен доступа |
| ROPC | Resource Owner Password Credentials | Поток, в котором приложение само принимает имя и пароль. Уходит в deprecated (раздел 2.3) |
| WAM | Web Account Manager | Брокер аутентификации, встроенный в Windows (глава 5) |
| UPN | User Principal Name | Имя входа вида taro@example.co.jp. Может измениться, например при смене фамилии (раздел 7.1) |
1. Сначала выводы
- Отказ от собственного хранения идентификаторов и паролей в пользу Entra ID означает, что хранение паролей, сброс, отключение учёток уволившихся и аудит входа целиком становятся работой тенанта. Главный выигрыш — зона ответственности приложения резко сужается.
- Десктопное приложение — общедоступный клиент (public client). exe можно разобрать там, куда его раздали, поэтому секрет клиента хранить нельзя (и не нужно). Регистрацию приложения тоже настраивают как общедоступный клиент.4
- ROPC (Resource Owner Password Credentials), где приложение само принимает имя и пароль, официально помечен как «устаревший (deprecated)», и опубликовано руководство по миграции. Он несовместим с MFA и условным доступом и фактически перестаёт быть рабочим. Для новой разработки считайте его запрещённым.56
- Реализация идёт через MSAL.NET (Microsoft.Identity.Client). Базовый паттерн один: сначала
AcquireTokenSilent, и только приMsalUiRequiredException—AcquireTokenInteractive.7 - На Windows рекомендуется вход через брокер WAM (Web Account Manager). SSO с уже выполненной сессией Windows, условный доступ, Windows Hello, ключи FIDO и привязка токена обновления к устройству получаются одной строкой
WithBroker.1 - Если не сохранять кэш токенов, экран входа появляется после каждого перезапуска. С самого начала подключайте зашифрованный кэш из
Microsoft.Identity.Client.Extensions.Msal.3 - Аутентификация Entra предполагает сеть. Для цехового приложения, которому нужно работать полностью офлайн, схема не подходит — сначала смотрите таблицу решений в главе 8.
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 25, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
2. Общая картина: что значит перестать самим хранить пароли
2.1 В чём проблема собственного хранения
Если бизнес-приложение держит пароли в своей таблице пользователей, на приложение (то есть на нас, разработчиков) ложится вся следующая ответственность.
- Хранение: выбор и реализация схемы хеширования (до сих пор встречаются 15-летние таблицы с MD5 без соли)
- Эксплуатация: обращения по сбросу пароля, блокировки, раздача первичных паролей
- Жизненный цикл: отключение учётки при увольнении или переводе. Пять приложений — пять ручных отключений
- Аудит: кто и когда вошёл, и как это сохранить. MFA в такой схеме практически не реализовать
Если отдать аутентификацию Entra ID, эти четыре обязанности исчезают из кода и собираются в администрировании тенанта. Отключили учётную запись в Entra ID — человек сразу не входит ни в одно приложение, журналы входа пишутся сами. У организации с уже внедрённым Microsoft 365 почти нет причин продолжать свою аутентификацию. Для организаций на Google Workspace парный механизм — вход в Windows под учётной записью Google — разобран в статье «Что такое GCPW».
2.2 Минимум понятий: общедоступный клиент и токены
Учебник по OAuth 2.0 / OpenID Connect опустим и оставим только то, что нужно, чтобы реализовать десктопное приложение.
| Понятие | Что это значит для десктопного приложения |
|---|---|
| Общедоступный клиент (public client) | exe, мобильное приложение и т. п., которое не может безопасно хранить секрет (client secret). Токен получает только от имени пользователя |
| Конфиденциальный клиент (confidential client) | Веб-сервер, демон и подобные, которые могут хранить секрет или сертификат. Десктопное приложение сюда не относится |
| ID-токен | JWT, который говорит, «кто этот человек». Если нужен только вход, этого достаточно |
| Токен доступа | Пропуск к конкретному API (Microsoft Graph или свой веб-API). В него вшиты назначение (audience) и области (scope) |
| Токен обновления (refresh token) | Нужен, чтобы без диалога обновить два предыдущих. MSAL сам ведёт его в кэше; приложение напрямую его не видит |
Важна первая строка. exe можно разобрать и декомпилировать там, куда его раздали, поэтому встроенный «секрет» секретом не является. Поэтому приложение регистрируют как общедоступный клиент без секрета, а сам вход (пароль, MFA) отдают браузеру или брокеру ОС; приложение получает только токен. То, что приложение никогда не касается пароля пользователя, — основа этой схемы.
2.3 ROPC больше не рабочий путь: что говорит документация
Старая привычка подсказывает: «сделаем свой экран входа, примем имя и пароль, а проверку за кулисами отдадим Entra ID». Это и есть ROPC (прямая передача имени и пароля). В MSAL.NET метод AcquireTokenByUsernamePassword ещё есть, но актуальная официальная документация однозначна.
- ROPC для общедоступных клиентов прямо помечен как «устаревший из‑за риска для безопасности», и опубликовано руководство по переходу на более безопасные потоки.6
- ROPC несовместим с MFA и условным доступом. Пользователь, для которого в тенанте включена обязательная MFA, в этом потоке блокируется и не входит.5
- SSO не работает, личные учётные записи Microsoft не принимаются, безпарольные учётные записи (FIDO, Authenticator) тоже не входят.5
- Веб-API Microsoft всё чаще принимают только токены с подтверждённой MFA, и сама официальная документация пишет: приложения, которые зависят от ROPC, будут отсечены (locked out); десктопные приложения должны перейти на вход через брокер.5
Обязательную MFA на стороне тенанта могут включить в любой момент. Брать ROPC потому, что «сейчас же работает», значит рискнуть тем, что в один день не войдёт никто. Если существующее приложение уже на ROPC, планируйте миграцию как данность. Для десктопа по сути допустимы только два способа получить токен.
| Поток | Когда использовать |
|---|---|
| Интерактивный (брокер / браузер) | Обычное GUI-приложение. Основной вариант |
| Поток кода устройства (device code flow) | Среда, где браузер показать нельзя (например, консоль по SSH). Показываете URL и код, пользователь входит в браузере на другом устройстве |
3. Регистрация приложения в центре администрирования Entra
Прежде чем писать код, приложение регистрируют в тенанте. Если разработчик не может сделать это сам, этот раздел можно отдать в ИТ как готовую заявку.
Состав экранов центра администрирования периодически меняют, поэтому ниже — не внешний вид меню, а имя экрана, до которого нужно дойти, и что на нём нажать. Сначала сводка работ.
| Цель | Какой экран открыть | Что сделать на экране |
|---|---|---|
| Зарегистрировать приложение | «Регистрация приложений» | «Создать регистрацию» → указать имя и «Поддерживаемые типы учётных записей» (раздел 3.1) |
| Записать идентификаторы | «Обзор» зарегистрированного приложения | Скопировать «Идентификатор приложения (клиента)» и «Идентификатор каталога (арендатора)» |
| Добавить URI перенаправления | «Проверка подлинности» зарегистрированного приложения | «Добавить платформу» → «Мобильные и классические приложения» → ввести URI (три значения из таблицы в 3.2) |
| Добавить разрешения | «Разрешения API» зарегистрированного приложения | «Добавить разрешение» → «Microsoft Graph» → «Делегированные разрешения» → User.Read (раздел 3.3) |
| Дать согласие администратора | «Разрешения API» зарегистрированного приложения | Выполнить «Предоставить согласие администратора для (имя тенанта)» (раздел 3.3) |
| Разрешить общедоступный клиент | «Проверка подлинности» зарегистрированного приложения | В «Дополнительных параметрах» включить «Разрешить общедоступные потоки клиентов» (раздел 3.4) |
3.1 Сама регистрация
Создаётся в центре администрирования Microsoft Entra (entra.microsoft.com) через [Регистрация приложений] → [Создать регистрацию].8
- Имя: показывается на экране согласия и в журналах входа, поэтому берите название, понятное бизнесу, например «Система учёта склада».
- Поддерживаемые типы учётных записей: для внутреннего приложения разумный выбор один — «Учётные записи только в этом каталоге организации» (один тенант). Несколько тенантов нужны только продукту, который раздают в разные организации.
- После регистрации запишите идентификатор приложения (клиента) и идентификатор каталога (арендатора) и встройте их в конфигурацию приложения (ни то ни другое не секрет).
3.2 URI перенаправления: платформа «Мобильные и классические приложения»
Это объявление, куда после входа вернётся токен. В [Проверка подлинности] → [Добавить платформу] → [Мобильные и классические приложения] зарегистрируйте URI под выбранный способ входа.4
| Способ входа | Какой URI перенаправления регистрировать |
|---|---|
| Брокер WAM (основной вариант, глава 5) | ms-appx-web://microsoft.aad.brokerplugin/{клиентский ID} |
| Системный браузер | http://localhost |
| Встроенный браузер | https://login.microsoftonline.com/common/oauth2/nativeclient |
ms-appx-web://... для WAM в код MSAL не пишут, но в регистрации приложения он обязателен.9 С учётом отката в браузер, если WAM недоступен (глава 5), на практике сразу регистрируют все три URI из таблицы. Отдельно смотрите поведение WithDefaultRedirectUri(): куда он резолвится, зависит от платформы. В .NET Framework это https://login.microsoftonline.com/common/oauth2/nativeclient, в .NET (Core и новее) — http://localhost.10 Если приложение на .NET Framework зарегистрировало только ms-appx-web и http://localhost, а WAM ушёл в браузер, вход падает из‑за несовпадения с nativeclient. Либо регистрируйте все три, либо явно фиксируйте URI через WithRedirectUri(...). Ещё одна типичная ошибка — зарегистрировать URI на платформе «Web»; вход тоже ломается.
3.3 Разрешения API и согласие администратора
В [Разрешения API] добавляют делегированные разрешения (delegated permission) для API, которые будет вызывать приложение. Если нужны только вход и профиль, хватает выдаваемого по умолчанию User.Read для Microsoft Graph.
После добавления попросите выполнить [Предоставить согласие администратора для (имя тенанта)].8 Тогда при первом входе не появится диалог согласия у каждого пользователя. В тенантах, где согласие самих пользователей отключено, без согласия администратора первый вход останавливается на «Требуется утверждение администратора». Поэтому для внутреннего приложения согласие администратора по правилу получают до распространения.
3.4 Флаг «Разрешить общедоступные потоки клиентов»
Переключатель «Разрешить общедоступные потоки клиентов» в дополнительных параметрах проверки подлинности ставят в «Да», если используете поток без URI перенаправления: поток кода устройства или интегрированную проверку подлинности Windows.4 Для интерактивного входа (браузер / брокер) он не обязателен. Для этой регистрации не создают ни секрет клиента, ни сертификат. Пустой раздел «Сертификаты и секреты» — нормальное состояние общедоступного клиента (путаница здесь частая, поэтому к теме вернёмся в главе 9).
4. Реализация на MSAL.NET: базовый паттерн Silent → Interactive
Сначала на схеме — кто что делает в этом паттерне. Достаточно увидеть, что приложение ни разу не принимает пароль и получает только токен.
sequenceDiagram
participant APP as Десктопное приложение
participant MSAL as MSAL.NET (кэш токенов)
participant UI as Брокер / браузер
participant EID as Entra ID
APP->>MSAL: AcquireTokenSilent
alt в кэше есть пригодный токен
MSAL-->>APP: токен доступа (экран не показывается)
else нет или обновить нельзя
MSAL-->>APP: MsalUiRequiredException
APP->>MSAL: AcquireTokenInteractive
MSAL->>UI: куда показать диалог, задаёт конфигурация (гл. 5)
UI->>EID: вход (MFA / Windows Hello / FIDO)
EID-->>UI: результат аутентификации
UI-->>MSAL: результат (что именно возвращается, зависит от пути — рис. 2)
MSAL-->>APP: токен доступа
end
Рис. 1: Пароль вводится только внутри брокера или браузера; приложению уходит лишь токен. Поэтому приложению не нужен секрет. Что делать с полученным токеном при вызове API — в главе 7.
Через NuGet добавьте Microsoft.Identity.Client. Запоминать нужно один паттерн: всегда сначала AcquireTokenSilent, и к интерактивному входу переходите только при MsalUiRequiredException. AcquireTokenInteractive кэш не смотрит совсем: если вызвать его сразу, экран входа будет каждый раз.7
using Microsoft.Identity.Client;
public sealed class AuthService
{
private const string ClientId = "Идентификатор приложения (клиента)";
private const string TenantId = "Идентификатор каталога (арендатора)";
private static readonly string[] Scopes = { "User.Read" };
private readonly IPublicClientApplication _app;
public AuthService()
{
_app = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.WithRedirectUri("http://localhost") // для системного браузера
.Build();
// В боевом коде здесь регистрируют сохранение кэша токенов (глава 6)
}
public async Task<AuthenticationResult> SignInAsync(IntPtr ownerHwnd)
{
// 1. Всегда сначала пробуем тихое получение по учётной записи из кэша
var accounts = await _app.GetAccountsAsync();
var account = accounts.FirstOrDefault();
try
{
return await _app.AcquireTokenSilent(Scopes, account)
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// 2. Экран входа показываем, только когда нужен диалог.
// В .NET Framework по умолчанию старый встроенный WebView,
// поэтому явно задаём перенаправление http://localhost = системный браузер
// (.NET 6+ и так использует только системный браузер)
return await _app.AcquireTokenInteractive(Scopes)
.WithAccount(account)
.WithParentActivityOrWindow(ownerHwnd)
.WithUseEmbeddedWebView(false)
.ExecuteAsync();
}
}
}
Вызывающая сторона передаёт дескриптор окна-владельца. Иначе диалог входа может оказаться за окном приложения; для WAM это обязательно.1 Ещё одно: в приложении на .NET Framework не пропускайте WithUseEmbeddedWebView(false). Интерактивный вход в .NET Framework по умолчанию идёт через встроенный WebView, а перенаправление http://localhost предназначено для системного браузера. Если сочетание разъедется, вы либо свалитесь на старый встроенный браузер, где не работают условный доступ и Windows Hello / FIDO, либо получите несовпадение URI перенаправления.2 В .NET 6 и новее встроенного WebView нет, всегда системный браузер: вызов избыточен, но безвреден.
// WinForms (внутри метода Form)
var result = await _authService.SignInAsync(this.Handle);
// WPF
var hwnd = new System.Windows.Interop.WindowInteropHelper(this).Handle;
var result = await _authService.SignInAsync(hwnd);
this.Text = $"Вход: {result.Account.Username}";
На что ещё смотреть.
- Один экземпляр
IPublicClientApplicationна всё приложение. Кэш привязан к экземпляру: если каждый раз делатьCreate, тихое получение не сработает. MsalUiRequiredException— не «авария», а обычный поток управления: нужен диалог. Так бывает при первом запуске, когда истёк токен обновления, когда изменились требования условного доступа.- Правильно вызывать
AcquireTokenSilentнепосредственно перед каждым обращением к API. Если в кэше есть действующий токен, он вернётся сразу; если срок близко — обновится сам.7 Не храните токен доступа у себя и не ведите его срок жизни вручную. .Resultили.Wait()в потоке UI дают взаимную блокировку (см. «Async и поток UI в WPF/WinForms на одном листе»).
5. Брокер WAM: рекомендуемая конфигурация на Windows
Код из главы 4 открывает браузер, но на Windows есть вариант лучше. WAM (Web Account Manager) — брокер аутентификации, встроенный в Windows 10 (начиная с 1703) и Windows Server 2019 и новее. Официальная документация называет четыре преимущества.1
- Усиление безопасности: токен обновления привязывается к устройству; украденный, он не сработает на другой машине (защита токенов). Улучшения безопасности продолжают приходить с обновлениями ОС.
- Поддержка функций: Windows Hello, условный доступ, ключи FIDO работают без дополнительного кода.
- Интеграция с системой: учётные записи, уже вошедшие в Windows, появляются во встроенном средстве выбора учётной записи, и в большинстве случаев вход заканчивается без ввода пароля. По сути это SSO.
- Защита токенов: можно соблюдать политику защиты токенов условного доступа.
Если офисный ПК в Entra Join (или Hybrid Join), сценарий такой: «запустили приложение → выбрали учётную запись Windows → вход сразу завершён», и пароль нигде не вводится.
В рис. 1 было сказано, что «что именно возвращается, зависит от пути». Здесь как раз эта разница. Брокер — не замена браузера: он сам доводит получение токена до конца и возвращает результат.
flowchart TB
A["AcquireTokenInteractive"] --> Q{"WithBroker включён и<br/>среда умеет WAM?"}
Q -->|"да"| BR["Брокер WAM<br/>сам получает токен до конца<br/>и отдаёт его MSAL"]
Q -->|"нет / среда не поддерживается (гл. 4)"| BW["Системный браузер<br/>возвращает код авторизации в MSAL,<br/>MSAL меняет его на токен в Entra ID"]
BR --> T["MSAL кладёт токен в кэш и отдаёт приложению"]
BW --> T
Рис. 2: Если меняется, куда выводится диалог, меняется и то, что получает MSAL. Через брокер обмен кода авторизации на токен на стороне MSAL уже не остаётся.
5.1 Реализация: WithBroker и пакет
Для WAM нужны MSAL.NET 4.52.0 или новее и дополнительный пакет Microsoft.Identity.Client.Broker.1 К построителю из главы 4 добавьте WithBroker.
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker; // для WithBroker(BrokerOptions)
var brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
{
Title = "Система учёта склада" // заголовок в средстве выбора учётной записи
};
_app = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.WithDefaultRedirectUri()
.WithParentActivityOrWindow(() => _ownerHwnd) // для WAM обязательно
.WithBroker(brokerOptions)
.Build();
Тихое получение можно усилить ещё на одну строку. Если в кэше нет учётной записи, передача PublicClientApplication.OperatingSystemAccount пробует тихий вход под «учётной записью, под которой сейчас выполнен вход в Windows». Это официально рекомендуемый паттерн: вход без диалога уже с первого запуска.9
var accounts = await _app.GetAccountsAsync();
var account = accounts.FirstOrDefault()
?? PublicClientApplication.OperatingSystemAccount;
try
{
return await _app.AcquireTokenSilent(Scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
return await _app.AcquireTokenInteractive(Scopes).ExecuteAsync();
}
Параллельно, как в разделе 3.2, в регистрации приложения на платформе «Мобильные и классические приложения» должен быть ms-appx-web://microsoft.aad.brokerplugin/{клиентский ID}.1 Если забыть, интерактивный вход падает с ошибкой брокера. Ещё одно: WithDefaultRedirectUri() в примере выше задаёт URI перенаправления только на случай, когда WAM недоступен и происходит откат в браузер, и куда он резолвится, зависит от платформы (.NET Framework → nativeclient, .NET → http://localhost).10 Если уже зарегистрированы все три URI из таблицы в 3.2, подойдёт любой вариант; если хотите сузить регистрацию, зафиксируйте URI явно через WithRedirectUri(...).
5.2 Ограничения WAM: без них легко промахнуться
| Ограничение | Содержание |
|---|---|
| ОС | Windows 10 (1703)+ / Windows Server 2019+. На более старых версиях, macOS и Linux — автоматический откат в браузер1 |
| Поставщик удостоверений | Только Entra ID. Authority для Azure AD B2C и AD FS не поддерживаются (откат в браузер)1 |
| Контекст выполнения | Нужна интерактивная сессия пользователя, в которой можно показать UI. Служба Windows, Планировщик заданий (вне сессии пользователя), runas от другого пользователя по задумке дают ошибку1 |
Особенно важна третья строка. «В приложении с окном работает, а тот же код в ночном задании падает» — это спецификация, не баг. Необслуживаемый запуск нужно проектировать отдельно, на разрешениях приложения (конфиденциальный клиент), а не на токене, делегированном пользователем. Откат встроен в MSAL как часть поведения, поэтому «сначала WAM, если нельзя — браузер» получается одним и тем же кодом.
5.3 Итоговый вид: брокер в приоритете, браузер как запасной путь
Глава 4 — только браузер, раздел 5.1 — только добавление брокера. Ниже они сведены в форму, которую можно брать в работу. Сначала — как задавать URI перенаправления: здесь часто путаются.
| Как пишете | Какой URI перенаправления реально используется | Когда так делать |
|---|---|---|
WithRedirectUri("http://localhost") |
Всегда http://localhost (системный браузер) |
Хотите зафиксировать один уже зарегистрированный URI. На .NET 6 и новее он совпадает |
WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") |
Всегда nativeclient | Конфигурация .NET Framework со встроенным WebView |
WithDefaultRedirectUri() |
Зависит от платформы (.NET Framework → nativeclient, .NET → http://localhost)10 |
Все три URI из таблицы в 3.2 уже зарегистрированы, и один и тот же код должен идти на любую платформу |
Пока WAM работает, этот URI не участвует. Он начинает действовать только при откате в браузер. В примере ниже URI явно зафиксирован через WithRedirectUri, чтобы сужение регистрации не ломало вход.
using System.IO;
using System.Linq;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker; // для WithBroker(BrokerOptions)
using Microsoft.Identity.Client.Extensions.Msal; // для MsalCacheHelper
public sealed class AuthService
{
private const string ClientId = "Идентификатор приложения (клиента)";
private const string TenantId = "Идентификатор каталога (арендатора)";
private static readonly string[] Scopes = { "User.Read" };
private readonly IPublicClientApplication _app;
private readonly IntPtr _ownerHwnd;
private AuthService(IPublicClientApplication app, IntPtr ownerHwnd)
{
_app = app;
_ownerHwnd = ownerHwnd;
}
// Регистрация кэша асинхронная, поэтому фабричный метод.
// В приложении создаёте один экземпляр и переиспользуете его (глава 4)
public static async Task<AuthService> CreateAsync(IntPtr ownerHwnd)
{
var brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
{
Title = "Система учёта склада" // заголовок в средстве выбора учётной записи
};
var app = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
// URI перенаправления на случай, когда WAM недоступен и вход уходит в браузер.
// Не WithDefaultRedirectUri(): он резолвится по-разному на разных платформах.
// Явно фиксируем значение, которое уже есть в регистрации приложения
.WithRedirectUri("http://localhost")
.WithParentActivityOrWindow(() => ownerHwnd) // для WAM обязательно
.WithBroker(brokerOptions) // если нельзя — сам уйдёт в браузер
.Build();
// Сохранение кэша токенов (глава 6). Регистрируется один раз сразу после Build()
var storageProperties = new StorageCreationPropertiesBuilder(
"msal_cache.dat",
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"KomuraSoft", "InventoryApp"))
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(app.UserTokenCache);
return new AuthService(app, ownerHwnd);
}
// Идентификатор учётной записи, под которой входили в прошлый раз.
// Сохраняйте в настройках приложения и читайте при запуске
// (файл настроек, реестр — куда удобно)
private string? _homeAccountId;
// Вызывать непосредственно перед каждым обращением к API.
// Токен доступа у себя не хранить (глава 4)
public async Task<AuthenticationResult> AcquireTokenAsync()
{
// 1. Решаем, под какой учётной записью пробовать silent
IAccount? account = await ResolveAccountAsync();
if (account is not null)
{
try
{
return await _app.AcquireTokenSilent(Scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Уходим в диалог
}
}
// 2. Диалог. Если WAM доступен — средство выбора учётной записи,
// если нет — системный браузер, заданный выше
AuthenticationResult result =
await _app.AcquireTokenInteractive(Scopes)
.WithParentActivityOrWindow(_ownerHwnd)
.WithUseEmbeddedWebView(false) // для .NET Framework (глава 4)
.ExecuteAsync();
// Запоминаем, кого выбрали. В следующий раз silent пробуем под этим человеком
_homeAccountId = result.Account?.HomeAccountId?.Identifier;
SaveHomeAccountId(_homeAccountId);
return result;
}
private async Task<IAccount?> ResolveAccountAsync()
{
List<IAccount> accounts = (await _app.GetAccountsAsync()).ToList();
// Если выбранный в прошлый раз человек ещё в кэше — берём его
if (_homeAccountId is not null)
{
IAccount? saved = accounts.FirstOrDefault(
a => a.HomeAccountId?.Identifier == _homeAccountId);
if (saved is not null) { return saved; }
}
// Если кандидат один — его и можно взять
if (accounts.Count == 1) { return accounts[0]; }
// Ноль или несколько, и выбрать не из чего.
// Здесь FirstOrDefault не использовать (см. ниже)
return null;
}
}
Не берите результат GetAccountsAsync() через FirstOrDefault(). В кэше не обязательно одна учётная запись. Переключились на другую, на общем ПК работали несколько человек, проверяли вход через другой тенант — в любом из этих случаев останется несколько. Порядок перечисления ничего не значит, поэтому FirstOrDefault() молча берёт того, кто случайно оказался первым.
Опасно это тем, что при ошибке выбора на экране ничего не происходит. Если токен этой учётной записи в кэше ещё жив, AcquireTokenSilent успешен, средство выбора не открывается. Человек работает «под собой», а на деле показывает и меняет данные Graph другого пользователя. Заметить это некому.
Поэтому в коде выше решение такое.
| Ситуация | Что делать |
|---|---|
| Выбранный в прошлый раз человек ещё в кэше | Пробовать silent под ним |
| Кандидат один | Пробовать silent под ним |
| Ноль или несколько, и выбрать не из чего | Silent не пробовать, пусть пользователь выберет в диалоге |
HomeAccountId.Identifier — строка «какой пользователь какого тенанта». Если сохранить её, в следующий раз войдёте под тем же человеком (это не токен, как чувствительные данные её трактовать не нужно). Если по умолчанию хотите текущую сессию Windows, в последней ветке можно вернуть PublicClientApplication.OperatingSystemAccount. Чего избегать — так это выбора «по порядку».
Вызов со стороны формы выглядит так. CreateAsync выполните один раз при запуске приложения и сохраните результат.
// Поле формы (один экземпляр на приложение)
private AuthService _authService;
// Из обработчика Click кнопки и т. п. В WPF HWND берите через WindowInteropHelper, как в главе 4
private async Task SignInAsync()
{
_authService ??= await AuthService.CreateAsync(this.Handle);
var result = await _authService.AcquireTokenAsync();
this.Text = $"Вход: {result.Account.Username}";
}
Этим одним сервисом закрываются три пути: на машине с WAM достаточно выбрать учётную запись Windows, без WAM открывается системный браузер, после перезапуска — тихое получение из кэша. Остаётся проверить, что в регистрации приложения есть URI из таблицы в 3.2 (как минимум ms-appx-web://... и http://localhost).
6. Сохранение кэша токенов: чтобы экран входа не появлялся после каждого перезапуска
Кэш токенов MSAL.NET по умолчанию живёт только в памяти, и на десктопе сохранение на диск — ответственность приложения. Без сохранения AcquireTokenSilent после каждого перезапуска процесса падает, и вход уходит в диалог.7 Обращение «на пилоте всё было хорошо, а с мест жалуются, что каждое утро снова экран входа» почти всегда объясняется именно этим.
Официальная рекомендация — кроссплатформенная библиотека кэша Microsoft.Identity.Client.Extensions.Msal (NuGet).3
using Microsoft.Identity.Client.Extensions.Msal;
var storageProperties = new StorageCreationPropertiesBuilder(
"msal_cache.dat",
Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"KomuraSoft", "InventoryApp"))
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(_app.UserTokenCache); // один раз сразу после Build()
На Windows кэш сохраняется в зашифрованном виде. Пример самостоятельной реализации в официальной документации шифрует токен через ProtectedData (DPAPI, DataProtectionScope.CurrentUser) и пишет файл; Extensions.Msal — та же идея, доведённая до промышленного качества.3 Принцип «пользовательский секрет защищают DPAPI в области пользователя» тот же, что для файлов настроек в статье «Хранение секретов в Windows-приложениях: не оставлять настройки открытым текстом, DPAPI». Самописное сохранение кэша токенов открытым JSON стоит рассматривать с той же серьёзностью, что и строку подключения открытым текстом.
Три замечания по эксплуатации.
- Сохранение кэша нужно и при WAM. MSAL по-прежнему кладёт ID-токен и метаданные учётной записи в свой кэш.9
- Базовый путь —
%LOCALAPPDATA%\ИмяКомпании\ИмяПриложения. Из‑за привязки DPAPI на другом ПК или под другим пользователем расшифровать не получится, но вред только в том, что тихое получение не сработает и придётся войти снова. - «Выход» делают так: перечислить учётные записи через
GetAccountsAsyncи удалить их черезRemoveAsync. Файл кэша удалять не нужно. НоRemoveAsyncчистит только локальный кэш MSAL: сессии WAM, браузера и входа в Windows остаются. При следующем интерактивном входе та же учётная запись может тихо войти снова. Если на общем ПК нужна настоящая смена учётки, проектируйте это явно: либо добавляйте кAcquireTokenInteractiveWithPrompt(Prompt.SelectAccount), чтобы всегда показывать выбор учётной записи, либо, если требуют, ещё и конечную точку выхода тенанта. «Очистка локального кэша» и «настоящий выход» — разные вещи.
7. Что делать с полученным токеном: три конфигурации
После успешного входа использование делится на три сценария. Насколько далеко идёте, зависит и от дополнительной настройки.
| Конфигурация | Какой токен | Что нужно дополнительно |
|---|---|---|
| (1) Только вход | ID-токен (AuthenticationResult.Account / ClaimsPrincipal) |
Ничего (достаточно User.Read) |
| (2) Вызов Microsoft Graph | Токен доступа для Graph | Разрешения Graph и согласие под нужные API |
| (3) Защита своего веб-API | Токен доступа для своего API | Отдельная регистрация приложения и публикация областей на стороне API, проверка токена на стороне API |
7.1 Только вход: самый маленький старт
Если задача — «заменить свою проверку пароля и облачные API не вызывать», достаточно сопоставить сведения об учётной записи из результата входа со своей таблицей прав. Ключ таблицы users замените на object ID из Entra (он не меняется, даже если изменился UPN, например при смене фамилии) и удалите столбец с паролем. Схему локальной БД трогать не нужно, меняется только аутентификация — это самый удобный первый шаг.
7.2 Вызов Microsoft Graph
Токен доступа User.Read можно сразу отправить в Microsoft Graph и получить профиль или фото вошедшего пользователя.
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
var me = await http.GetStringAsync("https://graph.microsoft.com/v1.0/me");
Если расширяете до календаря, отправки почты, уведомлений Teams и т. п., добавьте соответствующее разрешение (Mail.Send и другие) и заново получите согласие администратора. Перенос уведомлений из внутренних приложений на Graph хорошо стыкуется с подходом из статьи «Как спроектировать массовую рассылку для малого и среднего бизнеса без привязки к одному сервису».
7.3 Защита своего веб-API: проверка audience и областей
Если десктопное приложение вызывает свой веб-API, на стороне API нужна отдельная регистрация, публикация области вида api://{клиентский ID API}/access_as_user, а десктоп запрашивает токен именно с этой областью. На стороне API важно, что одного [Authorize] официально недостаточно.11 Проверять нужно три уровня.
- Подпись и издатель: JWT выдан Entra ID нужного тенанта (в ASP.NET Core + Microsoft.Identity.Web это делает промежуточное ПО)
- Audience (
aud): назначение токена — этот самый API. Токен для Graph нельзя принимать как пропуск к своему API - Область (утверждение
scp): есть ли ожидаемая область. В Microsoft.Identity.Web это объявляют атрибутом[RequiredScope("access_as_user")]11
Без пунктов 2 и 3 получается «API, куда пускают любого, у кого токен просто похож на токен Entra». Включите это в ревью проектных решений вместе с пунктами про сеть и проверку ввода из «Минимального чек-листа безопасности при разработке Windows-приложений».
8. Стоит ли внедрять вход через Entra в полностью внутренний инструмент
Не во все внутренние приложения это нужно класть. Сначала порядок решения на схеме. Впереди два условия: сеть и ID-инфраструктура, и только потом обстоятельства самого приложения.
flowchart TD
Q1{"Среда, где работает приложение,<br/>дотягивается до Entra ID?"}
Q1 -->|"Есть полностью офлайн-зона"| NG1["Нельзя или нужна отдельная схема<br/>Для обновления токена нужна сеть"]
Q1 -->|"Дотягивается"| Q2{"Какая ID-инфраструктура в организации?"}
Q2 -->|"Только локальный AD"| ALT1["Рассмотреть интегрированную проверку подлинности Windows"]
Q2 -->|"Только Google Workspace"| ALT2["Рассмотреть механизмы со стороны Google"]
Q2 -->|"Microsoft 365 / Entra ID"| Q3{"В приложении есть вход,<br/>вызовы API или требования аудита?"}
Q3 -->|"Нет (конвертер, просмотрщик и т. п.)"| NO["Не внедрять<br/>Достаточно входа в Windows"]
Q3 -->|"Есть"| YES["Внедрять<br/>Собственное управление паролями можно снять целиком"]
Рис. 3: Смотрите сверху вниз. Сначала отсекаем по офлайн-требованиям и ID-инфраструктуре, потом смотрим, нужно ли это самому приложению.
| Ситуация | Рекомендация | Почему |
|---|---|---|
| Microsoft 365 / Entra ID внедрены на всю компанию, и в приложении уже есть вход | Внедрять | Долг собственного хранения паролей снимается целиком. Стоимость реализации невелика |
| Приложение вызывает свой веб-API или облачные ресурсы | Внедрять | Для защиты API нужна основа аутентификации. Надёжнее, чем изобретать свой токен |
| Есть требования аудита (кто и когда пользовался, обязательная MFA) | Внедрять | Журналы входа и условный доступ собираются на стороне тенанта |
| Однофункциональный инструмент без входа (конвертер, просмотрщик и т. п.) | Не нужно | Мотивации добавлять аутентификацию нет. Достаточно входа в Windows |
| Работает полностью офлайн (закрытая линия, выносной ПК) | Нельзя или нужна отдельная схема | Первый вход и обновление токена требуют сети |
| Entra ID нет (только локальный AD или только Google Workspace) | Смотреть другой путь | В первом случае естественна проверка AD (интегрированная проверка подлинности Windows), во втором — механизмы Google |
Особо смотрите офлайн-требования. AcquireTokenSilent может отдать токен офлайн, пока жив кэшированный токен доступа (на опыте — чуть больше часа), но после истечения для обновления нужна сеть. До внедрения это предположение о сроке жизни нужно сопоставить с тем, как приложением реально пользуются на местах.
9. Эксплуатационные сюрпризы: обращения, которые приходят после внедрения
Внедрение — не конец. В эксплуатации одни и те же вопросы всплывают регулярно. Их лучше описать заранее.
- «Вчера работало, сегодня внезапно не вхожу»: первый подозреваемый — изменение политики условного доступа. ИТ включает, например, «блокировать незарегистрированные устройства» — и вход начинает падать, хотя приложение не трогали. Быстрее всего смотреть причину ошибки по этому пользователю в журналах входа центра администрирования Entra. Конфигурация с WAM лучше переносит требования политик, и таких трений становится меньше.1
- «Пришло уведомление, что истекает секрет. С этим приложением всё в порядке?»: у общедоступного клиента нет ни секрета, ни сертификата, истекать нечему. Если вопрос возник, либо путают с регистрацией конфиденциального клиента, либо кто-то завёл ненужный секрет в регистрации общедоступного клиента (во втором случае его можно удалить). Авария из‑за истечения секрета здесь структурно невозможна — скрытый плюс этой схемы.
- «При первом запуске: „Требуется утверждение администратора“»: не выдали согласие администратора из раздела 3.3. Если разрешение добавили позже, то же сообщение будет, пока не получите согласие на добавленное разрешение.
- «Встроили в ночное задание — не работает»: как в разделе 5.2, WAM предполагает интерактивную сессию. Необслуживаемую обработку проектируют отдельно, на разрешениях приложения, а не через повторное использование делегированного пользователем токена.
- Распространение и обновления: вокруг MSAL правки идут часто, нужен путь, которым обновление библиотеки дойдёт до всех машин. Смотрите это вместе с проверкой канала обновления из «Безопасности автоматических обновлений».
10. Итог
Вход через Entra ID в приложении WinForms / WPF сводится к шести пунктам.
- Цель — перестать самим хранить пароли. Хранение, сброс, увольнения и аудит собираются на стороне тенанта
- Десктопное приложение — общедоступный клиент. Секрета у него быть не может, и он не нужен
- ROPC уходит в deprecated. Новый экран, который принимает имя и пароль напрямую, не делать
- Реализация — безальтернативно паттерн MSAL.NET
AcquireTokenSilent→AcquireTokenInteractive - На Windows брокер WAM (
WithBroker) даёт SSO, условный доступ и Windows Hello - Сохранение кэша токенов (Extensions.Msal / защита DPAPI) подключают с самого начала
При минимальной схеме «заменить только вход» (раздел 7.1) влияние на существующее приложение обычно ограничивается экраном входа и таблицей пользователей, и доработка часто укладывается в несколько дней. Как только в дело входят условный доступ или офлайн-требования, нужны решения, которые учитывают и настройки тенанта, и то, как работой пользуются на местах. Если неясно, до какой конфигурации доводить приложение и как резать шаги миграции с собственной аутентификации, можем помочь.
Похожие статьи
- Что такое GCPW: как обработать вход в Windows через Google
- Хранение секретов в Windows-приложениях: не оставлять настройки открытым текстом, DPAPI
- Минимальный чек-лист безопасности при разработке Windows-приложений
- Безопасность автоматических обновлений: почему одного HTTPS недостаточно
Смежные направления консультирования
KomuraSoft (合同会社小村ソフト) занимается встраиванием входа через Entra ID в существующие приложения WinForms / WPF (проектирование регистрации приложения, реализация на MSAL.NET, план миграции с собственной аутентификации), ревью проверки токенов для своего веб-API и диагностикой сбоев входа, связанных с условным доступом.
- Разработка Windows-приложений
- Техническая консультация и ревью проектных решений
- Использование и миграция существующих активов
- Контакты
Справочные ссылки
-
Microsoft Learn, Using MSAL.NET with Web Account Manager (WAM). Преимущества брокера (усиление безопасности, Windows Hello / условный доступ / FIDO, средство выбора учётной записи, защита токенов); MSAL.NET 4.52.0+ и пакет Microsoft.Identity.Client.Broker; обязательность WithBroker и дескриптора родительского окна; URI перенаправления ms-appx-web; поддерживаемые ОС и откат; ограничение интерактивной сессии. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11
-
Microsoft Learn, Using web browsers (MSAL.NET). Таблица поддержки браузеров по платформам (в .NET Framework 4.6.2+ по умолчанию встроенный, в .NET 6+ только системный); для системного браузера нужен URI перенаправления http://localhost; переключение через WithUseEmbeddedWebView. ↩ ↩2
-
Microsoft Learn, Token cache serialization. Для десктопа рекомендуется кроссплатформенный кэш Microsoft.Identity.Client.Extensions.Msal; использование MsalCacheHelper; пример самостоятельной сериализации через ProtectedData (DPAPI, область CurrentUser). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Desktop app that calls web APIs: Code configuration. URI перенаправления для десктопных приложений (платформа мобильных и классических приложений, nativeclient / localhost) и смысл настройки «Разрешить общедоступные потоки клиентов». ↩ ↩2 ↩3
-
Microsoft Learn, Microsoft identity platform and OAuth 2.0 Resource Owner Password Credentials. Почему ROPC использовать не следует, несовместимость с MFA и блокировка, тенденция отсекать приложения, которые зависят от ROPC, и рекомендация десктопным приложениям переходить на вход через брокер. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Desktop app that calls web APIs: Acquire a token using username and password. Поток имя/пароль (ROPC) помечен как устаревший (deprecated) из‑за риска для безопасности; руководство по миграции; отсутствие поддержки MFA, условного доступа и SSO. ↩ ↩2
-
Microsoft Learn, Get a token from the token cache using MSAL.NET. Рекомендуемый паттерн: сначала AcquireTokenSilent, при MsalUiRequiredException — интерактивный вход; автоматическое обновление через кэш и токен обновления; очистка кэша удалением учётной записи. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Register an application with the Microsoft identity platform. Регистрация приложения в центре администрирования Entra, выбор поддерживаемых типов учётных записей, получение идентификатора клиента, согласие администратора. ↩ ↩2
-
Microsoft Learn, Desktop app that calls web APIs: Acquire a token by using WAM. Сохранение кэша токенов нужно и при WAM; рекомендуемый паттерн тихого входа через OperatingSystemAccount; URI перенаправления в регистрации приложения. ↩ ↩2 ↩3
-
Microsoft Learn, Default reply URI. URI перенаправления, который ставит WithDefaultRedirectUri, зависит от платформы (для десктопа на .NET Framework — https://login.microsoftonline.com/common/oauth2/nativeclient, для .NET Core — http://localhost). ↩ ↩2 ↩3
-
Microsoft Learn, Protected web API: Verify scopes and app roles. Одного атрибута [Authorize] недостаточно; нужна проверка утверждения scp (области); декларативная проверка через атрибут RequiredScope в Microsoft.Identity.Web. ↩ ↩2
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Окончание драйверов принтера Windows ── как готовить печать форм и этикеток в бизнес-приложениях
Microsoft поэтапно прекращает сопровождение драйверов принтера v3/v4; с июля 2026 IPP class driver предпочтут. Что исчезает в Windows pro...
Значок в области уведомлений и toast в Windows-приложении: ошибки NotifyIcon и выбор AppNotification
Разбираем, как держать бизнес-приложение Windows в области уведомлений и показывать toast. Правильная работа с NotifyIcon, повторная реги...
UI-автотесты десктопных приложений Windows — UI Automation и устойчивые тесты на FlaUI
Разбираем UI-автотесты WinForms и WPF с устройства Windows UI Automation. Минимальная реализация на FlaUI, AutomationId и ожидание по усл...
Высокий DPI в WPF — почему «к DPI должен быть готов», а на деле всё размывается
WPF по умолчанию System DPI Aware, но при переносе окна на монитор с другим DPI всё изображение размывается, а растр теряет резкость. Раз...
Поддержка высокого DPI в WinForms — почему на 4K размывается или ломается макет
Разбираем, почему WinForms-приложение на 4K-мониторе размывается или ломается: виртуализация DPI и режимы осведомлённости о DPI (System A...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Поток UI и таймеры
Поток UI WPF / WinForms, асинхронные операции, Dispatcher и проектирование таймеров.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Зачем добавлять вход через Entra ID в приложение WinForms/WPF?
- Хранение паролей, сброс, отключение учёток уволившихся и аудит входа целиком уходят на сторону тенанта, и зона ответственности приложения резко сужается. Достаточно отключить учётную запись в Entra ID — и человек сразу не войдёт ни в одно приложение. MFA и условный доступ начинают действовать и на внутренние приложения без отдельной реализации. Кода немного: библиотека MSAL.NET и несколько десятков строк. Если Microsoft 365 уже внедрён, почти нет причин держать собственную аутентификацию.
- Можно ли принимать имя и пароль на своём экране и проверять их напрямую (ROPC)?
- Для нового кода считайте это запрещённым. ROPC для общедоступных клиентов официально помечен как «устаревший (deprecated) из‑за риска для безопасности», и есть руководство по миграции. Схема несовместима с MFA и условным доступом: пользователи, для которых в тенанте включена обязательная MFA, блокируются и не входят. Обязательную MFA на стороне тенанта могут включить в любой момент, поэтому даже если сейчас всё работает, в один день вход может пропасть у всех.
- Стоит ли использовать брокер WAM?
- На Windows — да, это рекомендуемый путь. Одна строка WithBroker даёт единый вход (SSO) с уже выполненной сессией Windows, условный доступ, Windows Hello и ключи FIDO, а также привязку токена обновления к устройству. Если ПК в Entra Join, после запуска достаточно выбрать учётную запись Windows — пароль вводить не нужно. Но брокер работает только с Entra ID и по задумке выдаёт ошибку вне интерактивной сессии пользователя: в службе Windows или в Планировщике заданий так запускать нельзя.
- Почему после каждого перезапуска снова появляется экран входа?
- Кэш токенов не сохраняется между запусками. По умолчанию кэш MSAL.NET живёт только в памяти, а на десктопе сохранение на диск — ответственность приложения. Официальная рекомендация — пакет Microsoft.Identity.Client.Extensions.Msal; на Windows кэш пишется в зашифрованном виде. Даже с WAM сохранение кэша всё равно нужно: в нём остаются ID-токен и метаданные учётной записи.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.