Встраиваем аутентификацию Entra ID в приложения WinForms/WPF — практическая архитектура на MSAL.NET и брокере WAM
· Го Комура · Windows, C#, .NET, WinForms, WPF, Entra ID, Аутентификация, Безопасность, Техническая консультация
«Для каждого внутреннего бизнес-приложения делаем свой экран входа, пароли храним в собственной базе. Каждый раз, когда кто-то увольняется, приходится вручную отключать его учётную запись в каждом приложении по отдельности — это утомительно». «Microsoft 365 у нас развёрнут на всю компанию, нельзя ли просто пускать людей под той же учётной записью?» Эти темы в последние годы стабильно всё чаще звучат в обращениях по модернизации десктопных приложений. Иногда это формулируется иначе: ИТ-отдел на фоне утечки паролей или курса на нулевое доверие прямо просит «прекратить вести собственное управление паролями».
Если сразу к выводу: если в организации используется Microsoft 365, перенос входа во внутренние приложения WinForms/WPF на Entra ID (ранее Azure AD) — разумное вложение сил. Приложение перестаёт хранить пароли вообще, а защита и аудит на стороне тенанта — многофакторная аутентификация, условный доступ, журналы входов — автоматически распространяются и на внутренние приложения. Сама реализация укладывается в библиотеку MSAL.NET и несколько десятков строк кода.
Тем не менее у десктопных приложений есть ряд характерных подводных камней. Старый подход «принять имя пользователя и пароль в текстовых полях и проверить их за кулисами» (ROPC) официально движется к статусу устаревшего, и его нельзя использовать в новой разработке. Если не сохранять кэш токенов постоянно, экран входа будет появляться при каждом запуске, а на Windows использование брокера (WAM) или отказ от него сильно меняет и пользовательский опыт, и безопасность. В этой статье мы последовательно разберём минимальный набор понятий, регистрацию приложения, реализацию на MSAL.NET, WAM, кэш, критерии внедрения и эксплуатационные ловушки.
1. Сначала вывод
- Отказ от самостоятельного управления идентификаторами и паролями в пользу Entra ID означает, что хранение паролей, обработка сброса, отключение учётных записей уволенных сотрудников и аудит входов полностью становятся работой тенанта. Резкое сокращение зоны ответственности приложения — главное преимущество.
- Десктопное приложение — это публичный клиент. Поскольку exe-файл может быть проанализирован там, куда его распространили, он не может (и не должен) хранить секрет клиента. Регистрация приложения также настраивается как публичный клиент. 1
- ROPC (Resource Owner Password Credentials), при котором приложение само принимает имя пользователя и пароль, официально задокументирован как «устаревший (deprecated)», и вместе с этим опубликовано руководство по миграции. Он несовместим с MFA и условным доступом и фактически становится непригодным к использованию. Считайте применение этой схемы в новой разработке запрещённым. 23
- Реализация выполняется через MSAL.NET (Microsoft.Identity.Client), и по сути есть только один базовый паттерн вызова: сначала всегда вызывать
AcquireTokenSilent, и только при полученииMsalUiRequiredExceptionпереходить кAcquireTokenInteractive. 4 - На Windows рекомендуется аутентификация через брокер WAM (Web Account Manager). SSO с уже выполненным входом в Windows, поддержка условного доступа, Windows Hello и ключей FIDO, а также привязка refresh-токена к устройству — всё это достигается одной строкой
WithBroker. 5 - Забудете сохранять кэш токенов постоянно — и экран входа будет появляться при каждом перезапуске приложения. Встраивайте зашифрованный кэш из
Microsoft.Identity.Client.Extensions.Msalс самого начала. 6 - Аутентификация Entra — это механизм, предполагающий наличие сети. Он не подходит для цеховых приложений, которым нужно работать полностью офлайн, поэтому прежде чем внедрять его, оцените применимость по таблице решений из главы 8.
2. Общая картина — что значит отказаться от собственного управления паролями
2.1 В чём проблема собственного управления
Когда бизнес-приложение хранит пароли в собственной таблице пользователей, на приложение (то есть на нас, разработчиков) ложится вся следующая ответственность.
- Хранение: выбор и реализация схемы хеширования (до сих пор нередко встречаются 15-летние таблицы с несолёным MD5, до которых так и не дошли руки)
- Эксплуатация: обработка обращений по сбросу пароля, блокировки, раздача первичных паролей
- Жизненный цикл: отключение учётных записей при увольнении или переводе. Если приложений пять, придётся вручную отключать учётную запись пять раз
- Аудит: фиксация и хранение того, кто и когда вошёл в систему. Многофакторную аутентификацию в такой схеме реализовать практически невозможно
Делегировав аутентификацию Entra ID, вы убираете эти четыре обязанности из кода приложения и консолидируете их в администрировании тенанта. Отключение учётной записи Entra ID уволенного сотрудника мгновенно блокирует вход во все приложения, а журналы входов ведутся автоматически. У организации, уже внедрившей Microsoft 365, почти не остаётся причин продолжать поддерживать собственную аутентификацию. (Для организаций на Google Workspace, которым нужен парный механизм — перенос самого входа в Windows на учётную запись Google, — см. статью «Что такое GCPW».)
2.2 Минимальный набор понятий — публичные клиенты и токены
Опустим учебное изложение OAuth 2.0 / OpenID Connect и перечислим только те понятия, которые нужны для реализации десктопного приложения.
| Понятие | Что оно означает для десктопного приложения |
|---|---|
| Публичный клиент | Приложение — exe, мобильное приложение и т. п. — которое не может безопасно хранить секрет (client secret). Может получать токены только от имени пользователя |
| Конфиденциальный клиент (confidential client) | Приложение вроде веб-сервера или демона, которое может хранить секрет или сертификат. Десктопное приложение к этой категории не относится |
| ID-токен | JWT, представляющий «кто этот человек». Если нужна только функция входа, этого достаточно |
| Токен доступа | Пропуск для вызова конкретного API (Microsoft Graph или собственного веб-API). В него встроены назначение (audience) и области доступа (scope) |
| Refresh-токен | Токен для обновления двух вышеперечисленных без диалога с пользователем. MSAL автоматически управляет им внутри кэша, и приложение никогда не видит его напрямую |
Важна первая строка. Поскольку exe-файл можно проанализировать и декомпилировать там, куда он распространён, любой встроенный в него «секрет» перестаёт быть секретом. Именно поэтому приложение регистрируется как публичный клиент, работающий без секрета, а сама аутентификация (ввод пароля, MFA) делегируется браузеру или брокеру ОС, и приложение получает только токен. То, что приложение никогда не касается пароля пользователя, — фундамент всей этой архитектуры.
2.3 ROPC — тупиковый путь: что на самом деле говорит документация
Старый инстинкт подсказывает: «сделаем собственный экран входа, принимающий имя пользователя и пароль, а проверку за кулисами доверим Entra ID». Это и есть ROPC (прямая передача имени пользователя и пароля); он всё ещё существует в MSAL.NET как AcquireTokenByUsernamePassword, но текущая официальная документация недвусмысленна.
- ROPC для публичных клиентов прямо задокументирован как «устаревший из-за риска для безопасности», и опубликовано руководство по миграции на более безопасные схемы. 3
- ROPC несовместим с MFA и условным доступом. Пользователь, для которого в тенанте включена обязательная MFA, в этой схеме просто блокируется и не может войти. 2
- SSO не работает, личные учётные записи Microsoft использовать нельзя, и учётные записи без пароля (FIDO, Authenticator) войти тоже не могут. 2
- Собственные веб-API Microsoft всё активнее переходят к приёму только токенов с подтверждённой MFA, и сама официальная документация утверждает: «приложения, зависящие от ROPC, будут заблокированы (locked out); десктопные приложения должны перейти на аутентификацию через брокер». 2
Поскольку обязательность MFA может быть включена настройкой на стороне тенанта в любой момент, применение ROPC под девизом «сейчас же работает» рано или поздно приведёт к тому, что в один день все пользователи внезапно потеряют возможность войти. Если существующее приложение уже работает на ROPC, планируйте миграцию как данность. Для десктопного приложения фактически допустимы лишь два способа получения токена.
| Схема | Где применяется |
|---|---|
| Интерактивная (брокер / браузер) | Обычные GUI-приложения. Основной выбор |
| Схема с кодом устройства (device code flow) | Окружения, где нельзя показать браузер (например, консоль по SSH). Отображает URL и код, и пользователь входит через браузер на другом устройстве |
3. Регистрация приложения — настройка в центре администрирования Entra
Прежде чем писать код, нужно зарегистрировать приложение в тенанте. Если разработчик не может сделать это самостоятельно, содержимое этого раздела можно использовать как готовую заявку для ИТ-отдела.
3.1 Сама регистрация
Создаётся через [Регистрация приложений] → [Новая регистрация] в центре администрирования Microsoft Entra (entra.microsoft.com). 7
- Имя: отображается на экране согласия и в журналах входов, поэтому используйте название, понятное бизнес-стороне, например «Система учёта склада».
- Поддерживаемые типы учётных записей: для внутреннего приложения единственный разумный вариант — «учётные записи только в этом каталоге организации» (один тенант). Мультитенантность нужна только для продуктов, распространяемых среди нескольких организаций.
- Запишите отображаемые после регистрации идентификатор приложения (клиента) и идентификатор каталога (тенанта) и встройте их в конфигурацию приложения (оба значения не являются секретными).
3.2 URI перенаправления — платформа «мобильные и классические приложения»
Это объявление того, куда приложение получает токен после аутентификации. В разделе [Проверка подлинности] → [Добавление платформы] → [Мобильные и классические приложения] зарегистрируйте URI, соответствующий выбранному способу аутентификации. 1
| Способ аутентификации | Какой URI перенаправления регистрировать |
|---|---|
| Брокер WAM (основной выбор, глава 5) | ms-appx-web://microsoft.aad.brokerplugin/{ID клиента} |
| Системный браузер | http://localhost |
| Встроенный браузер | https://login.microsoftonline.com/common/oauth2/nativeclient |
Значение ms-appx-web://... для WAM никогда не прописывается в коде MSAL, но на стороне регистрации приложения оно обязательно. 8 С учётом отката к браузеру, который происходит, если WAM недоступен (глава 5), практично сразу зарегистрировать все три URI из таблицы. Отдельного внимания заслуживает поведение WithDefaultRedirectUri(): результат зависит от платформы — в .NET Framework он разрешается в https://login.microsoftonline.com/common/oauth2/nativeclient, а в .NET (Core и новее) — в http://localhost. 9 Если приложение на .NET Framework зарегистрировало только ms-appx-web и http://localhost, а WAM откатывается к браузеру, вы получите ошибку аутентификации из-за несовпадения с nativeclient. Либо регистрируйте все три URI, либо явно фиксируйте нужный через WithRedirectUri(...). Ещё одна классическая ошибка — случайно зарегистрировать URI на платформе «Web», что тоже приводит к ошибке аутентификации.
3.3 Разрешения API и согласие администратора
В разделе [Разрешения API] добавьте делегированные разрешения (delegated permission) для API, которые будет вызывать приложение. Если нужны только вход и отображение профиля, достаточно предоставляемого по умолчанию разрешения User.Read для Microsoft Graph.
После добавления попросите кого-нибудь выполнить [Предоставить согласие администратора для (имя тенанта)]. 7 Это устраняет диалог согласия для каждого пользователя при первом входе. В тенантах, где согласие самих пользователей отключено, без согласия администратора первый вход останавливается на сообщении «требуется одобрение администратора», поэтому для приложений, распространяемых внутри компании, как правило, согласие администратора нужно получить до распространения.
3.4 Флаг «Разрешить публичные клиентские потоки»
Переключатель «Разрешить публичные клиентские потоки» в дополнительных настройках проверки подлинности включается («Да»), если вы используете схему, не полагающуюся на URI перенаправления, например схему с кодом устройства или интегрированную проверку подлинности Windows. 1 Для интерактивной аутентификации (браузер/брокер) это не обязательно. Отметим также, что для этой регистрации приложения не создаются ни секрет клиента, ни сертификат. Пустой раздел «Сертификаты и секреты» — корректное состояние для публичного клиента (по этому поводу часто возникает путаница, поэтому мы вернёмся к теме в главе 9).
4. Реализация на MSAL.NET — базовая схема Silent → Interactive
Добавьте через NuGet пакет Microsoft.Identity.Client. Реально нужно запомнить только один паттерн реализации: всегда сначала вызывать AcquireTokenSilent, и переходить к интерактивному режиму только при получении MsalUiRequiredException. AcquireTokenInteractive спроектирован так, что вообще не заглядывает в кэш, поэтому его прямой вызов каждый раз показывает экран входа. 4
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 это обязательно. 5 Ещё один момент: не опускайте WithUseEmbeddedWebView(false) в приложении на .NET Framework. По умолчанию интерактивная аутентификация в .NET Framework использует встроенный WebView, тогда как перенаправление http://localhost предназначено именно для системного браузера. (Если перепутать сочетание, вы либо откатитесь к устаревшему встроенному браузеру, не поддерживающему условный доступ и Windows Hello/FIDO, либо получите несовпадение URI перенаправления.) 10 В .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— не «ошибка», а обычный, ожидаемый поток управления, означающий необходимость диалога. Он возникает при первом запуске, при истечении refresh-токена или при изменении требований условного доступа.- Правильное использование — вызывать
AcquireTokenSilentнепосредственно перед каждым вызовом API. Если в кэше есть действительный токен, он возвращается немедленно, а при приближении срока истечения обновляется автоматически. 4 Нельзя самостоятельно хранить токен доступа и управлять его временем жизни. - Ожидание в потоке пользовательского интерфейса через
.Resultили.Wait()приводит к взаимной блокировке (см. «Async и UI-поток в WPF/WinForms на одном листе»).
5. Брокер WAM — рекомендуемая конфигурация для Windows
Код из главы 4 открывает браузер, но на Windows есть вариант ещё лучше. WAM (Web Account Manager) — брокер аутентификации, встроенный в Windows 10 (начиная с 1703) и Windows Server 2019 и новее; официальная документация перечисляет четыре его преимущества. 5
- Усиление безопасности: refresh-токены привязываются к устройству, поэтому даже украденные, они не могут использоваться на другой машине (защита токенов). Улучшения безопасности продолжают поступать через обновления ОС.
- Поддержка функций: функции аутентификации, интегрированные с ОС и сервисами — Windows Hello, условный доступ, ключи FIDO — работают без дополнительного кода.
- Системная интеграция: учётные записи, уже вошедшие в Windows, появляются во встроенном средстве выбора учётной записи, поэтому в большинстве случаев вход завершается вообще без ввода пароля — фактически это SSO.
- Защита токенов: поддерживаются политики защиты токенов условного доступа.
Если офисный ПК подключён к Entra (или к гибридному подключению), опыт сводится к «запустил приложение → выбрал учётную запись Windows → вход мгновенно завершён», и пароль нигде не вводится.
5.1 Реализация — WithBroker и пакет
Для использования WAM требуется MSAL.NET версии 4.52.0 или новее и дополнительный пакет Microsoft.Identity.Client.Broker. 5 Добавьте WithBroker к строителю из главы 4.
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». Это официально рекомендуемый паттерн, позволяющий добиться входа без диалога уже с первого запуска. 8
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 клиента} на платформе «Мобильные и классические приложения». 5 Если этого не сделать, интерактивная аутентификация будет завершаться ошибкой брокера. Ещё один момент: WithDefaultRedirectUri() в примере выше определяет URI перенаправления только для случая, когда WAM недоступен и происходит откат к браузеру, и его результат зависит от платформы (.NET Framework → nativeclient, .NET → http://localhost). 9 Если вы уже зарегистрировали все три URI из таблицы в разделе 3.2, подойдёт любой вариант; если же хотите сузить регистрацию, зафиксируйте нужный URI явно через WithRedirectUri(...).
5.2 Ограничения WAM — о чём нужно знать заранее
| Ограничение | Содержание |
|---|---|
| ОС | Windows 10 (1703)+ / Windows Server 2019+. На более старых версиях, macOS и Linux происходит автоматический откат к браузеру 5 |
| Поставщик удостоверений | Только Entra ID. Authority для Azure AD B2C и AD FS не поддерживаются (откат к браузеру) 5 |
| Контекст выполнения | Предполагает возможность показать UI в интерактивной пользовательской сессии. Службы Windows, планировщик заданий (вне пользовательской сессии) или выполнение от имени другого пользователя через runas по конструкции завершаются ошибкой 5 |
Особенно важна третья строка. То, что «в приложении с интерфейсом код работает, а тот же код, перенесённый в ночной пакетный процесс, отказывает», — это спецификация, а не баг. Автоматическое (безучастное) выполнение — область, где проектирование нужно разделять в сторону прав приложения (конфиденциальный клиент), а не переиспользовать делегированный пользователем токен. Поскольку откат встроен как часть проектной спецификации, «сначала пробуем WAM, если не выходит — откатываемся к браузеру» реализуется в MSAL одной строкой кода.
6. Постоянное хранение кэша токенов — не показывать экран входа при каждом перезапуске
Кэш токенов MSAL.NET по умолчанию существует только в памяти, и на десктопе реализация постоянного хранения — ответственность самого приложения. Без постоянного хранения AcquireTokenSilent завершается неудачей при каждом перезапуске процесса, и происходит откат к интерактивному входу. 4 Обращение вида «на пилоте всё было хорошо, а с площадки пришла жалоба, что каждое утро появляется экран входа» практически всегда объясняется именно этим.
Официальная рекомендация — использовать кроссплатформенную библиотеку кэширования Microsoft.Identity.Client.Extensions.Msal (NuGet). 6
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 позиционируется как библиотека промышленного качества, построенная на той же идее. 6 Принцип «пользовательские секреты нужно защищать пользовательской областью DPAPI» — тот же самый, что был описан для конфигурационных файлов в статье «Хранение секретов в Windows-приложениях - избегаем открытых настроек с помощью DPAPI». Самописную реализацию, сохраняющую кэш токенов в виде открытого JSON, стоит рассматривать с той же степенью серьёзности, что и хранение строки подключения в открытом виде.
Три эксплуатационных замечания.
- Постоянное хранение кэша необходимо даже при использовании WAM. MSAL по-прежнему сохраняет ID-токен и метаданные учётной записи в своём собственном кэше. 8
- Место хранения по умолчанию —
%LOCALAPPDATA%\ИмяКомпании\ИмяПриложения. Из-за привязки DPAPI расшифровать данные на другом ПК или под другим пользователем не получится, но единственное следствие — неудача тихого получения токена и повторный вход, реального вреда нет. - «Выход из системы» реализуется перечислением учётных записей через
GetAccountsAsyncи их удалением черезRemoveAsync; удалять файл кэша не нужно. ОднакоRemoveAsyncочищает только локальный кэш MSAL — сессии WAM, браузера и самого входа в Windows остаются нетронутыми. Поскольку при следующем интерактивном входе та же учётная запись может быть тихо повторно аутентифицирована, если на общем ПК нужно обеспечить настоящую смену учётной записи, проектируйте это явно: либо добавляйтеWithPrompt(Prompt.SelectAccount)кAcquireTokenInteractive, чтобы всегда показывался экран выбора учётной записи, либо, в зависимости от требований, используйте также конечную точку выхода тенанта. Чётко различайте «очистку локального кэша» и «настоящий выход из системы».
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 в полностью внутренний инструмент
Внедрять её во все внутренние приложения без разбора не стоит. Приведём таблицу решений.
| Ситуация | Рекомендация | Причина |
|---|---|---|
| Microsoft 365 / Entra ID внедрены по всей компании, и в приложении уже есть концепция входа | Внедрять | Весь долг собственного управления паролями исчезает целиком. Стоимость реализации невелика |
| Приложение вызывает собственный веб-API или облачные ресурсы | Внедрять | Основа аутентификации необходима для защиты API. Надёжнее, чем изобретать собственную схему токенов |
| Есть требования аудита (запись того, кто и когда использовал систему, обязательная MFA) | Внедрять | Журналы входов и условный доступ консолидируются на стороне тенанта |
| Однофункциональный инструмент без концепции входа (конвертер, просмотрщик и т. п.) | Не требуется | Нет мотивации добавлять аутентификацию. Достаточно входа в Windows |
| Работает в полностью офлайн-среде (закрытая производственная линия, вынесенный ПК) | Невозможно или требует особого проектирования | Первый вход и обновление токена требуют сети |
| Entra ID не внедрён (только локальный AD, только Google Workspace) | Рассмотреть альтернативу | Для первого случая естественна аутентификация AD (интегрированная проверка подлинности Windows), для второго — механизм со стороны Google |
Особое внимание уделите офлайн-требованиям. AcquireTokenSilent может вернуть токен офлайн, пока действителен кэшированный токен доступа (по опыту — чуть больше часа), но после истечения срока для обновления потребуется сеть. Перед внедрением нужно сопоставить это предположение о времени жизни токена с реальными паттернами использования на площадке.
9. Эксплуатационные ловушки — обращения, поступающие после внедрения
Внедрение — не конец истории; в фазе эксплуатации регулярно возникают одни и те же обращения. Опишем их заранее.
- «Вчера всё работало, а сегодня внезапно не могу войти»: главный подозреваемый — изменение политики условного доступа. Когда ИТ-отдел включает что-то вроде «блокировать незарегистрированные устройства», вход начинает завершаться неудачей, хотя само приложение не менялось. Быстрее всего разобраться, посмотрев причину ошибки для соответствующего пользователя в журналах входов центра администрирования Entra. Конфигурация с WAM повышает устойчивость к таким требованиям политик, что само по себе снижает частоту подобных трений. 5
- «Пришло уведомление об истечении срока действия секрета — всё ли в порядке с этим приложением?»: у публичных клиентов изначально нет ни секрета, ни сертификата, поэтому истекать нечему. Если возникает такой вопрос, это либо путаница с регистрацией конфиденциального клиента, либо кто-то создал ненужный секрет для регистрации публичного клиента (во втором случае его можно спокойно удалить). Структурная невозможность аварии из-за истечения срока секрета — скрытое преимущество этой конфигурации.
- «При первом запуске появляется сообщение „требуется одобрение администратора“»: это пропущенное согласие администратора из раздела 3.3. Если позже добавляется новое разрешение, то же сообщение появится снова, пока не будет заново получено согласие на добавленное разрешение.
- «Встроили в ночной пакетный процесс — не работает»: как указано в разделе 5.2, WAM предполагает интерактивную сессию. Автоматическую обработку нужно проектировать отдельно, на правах приложения, а не через переиспользование делегированного пользователем токена.
- Распространение и обновления: экосистема MSAL активно исправляется, поэтому нужен механизм, доводящий обновления библиотеки до всех устройств. Рассматривайте это вместе с проверкой пути обновления, описанной в статье «Безопасность автоматических обновлений».
10. Итог
Поддержку аутентификации Entra ID в приложении WinForms/WPF можно свести к следующим шести пунктам.
- Сама цель — отказаться от собственного управления паролями. Хранение, сброс, обработка увольнений и аудит консолидируются на стороне тенанта
- Десктопное приложение — это публичный клиент. У него не может быть секрета, да он и не нужен
- ROPC движется к статусу устаревшего. Не создавайте новый экран, принимающий имя пользователя и пароль напрямую
- Реализация — безальтернативно паттерн MSAL.NET
AcquireTokenSilent→AcquireTokenInteractive - На Windows брокер WAM (
WithBroker) даёт SSO, условный доступ и поддержку Windows Hello - Постоянное хранение кэша токенов (Extensions.Msal / защита DPAPI) встраивается с самого начала
При минимальной конфигурации «заменить только вход» (раздел 7.1) влияние на существующее приложение можно ограничить экраном входа и таблицей пользователей, и доработка часто укладывается в несколько дней. С другой стороны, как только в дело вступают условный доступ или офлайн-требования, нужны проектные решения, учитывающие как настройки тенанта, так и реальную практику работы бизнеса. Если вы не уверены, до какой конфигурации стоит доводить приложение, или как выстроить шаги миграции с собственной аутентификации, мы можем помочь.
Похожие статьи
- Что такое GCPW - обработка входа в Windows через аутентификацию Google
- Хранение секретов в Windows-приложениях - избегаем открытых настроек с помощью DPAPI
- Минимальный чек-лист безопасности при разработке Windows-приложений
- Безопасность автоматических обновлений - почему одного HTTPS недостаточно
Смежные направления консультирования
KomuraSoft LLC (合同会社小村ソフト) занимается встраиванием аутентификации Entra ID в существующие приложения WinForms/WPF (проектирование регистрации приложения, реализация на MSAL.NET, планирование миграции с собственной аутентификации), ревью проектных решений по проверке токенов для собственных веб-API, а также диагностикой сбоев входа, связанных с условным доступом.
- Разработка Windows-приложений
- Техническая консультация и ревью проектных решений
- Использование и миграция существующих активов
- Контакты
Справочные ссылки
-
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) устарела из-за риска для безопасности, о руководстве по миграции, а также об отсутствии поддержки MFA, условного доступа и SSO. ↩ ↩2
-
Microsoft Learn, Get a token from the token cache using MSAL.NET. О рекомендуемом паттерне: сначала вызывать AcquireTokenSilent и откатываться к интерактивному режиму при MsalUiRequiredException, об автоматическом обновлении через кэш и refresh-токен, а также об очистке кэша путём удаления учётной записи. ↩ ↩2 ↩3 ↩4
-
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
-
Microsoft Learn, Token cache serialization. О рекомендации использовать для десктопных приложений кроссплатформенный кэш из Microsoft.Identity.Client.Extensions.Msal, об использовании MsalCacheHelper и о примере самостоятельной сериализации через ProtectedData (DPAPI, область CurrentUser). ↩ ↩2 ↩3
-
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, зависит от платформы (https://login.microsoftonline.com/common/oauth2/nativeclient для десктопных приложений на .NET Framework, http://localhost для .NET Core). ↩ ↩2
-
Microsoft Learn, Using web browsers (MSAL.NET). О таблице поддержки браузеров по фреймворкам (в .NET Framework 4.6.2+ по умолчанию используется встроенный браузер, в .NET 6+ — только системный), о необходимости URI перенаправления http://localhost для системного браузера и о переключении через WithUseEmbeddedWebView. ↩
-
Microsoft Learn, Protected web API: Verify scopes and app roles. О том, что одного лишь атрибута [Authorize] недостаточно, о необходимости проверки утверждения scp (области доступа) и о декларативной проверке через атрибут RequiredScope из Microsoft.Identity.Web. ↩ ↩2
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Значки в области уведомлений и всплывающие (toast) уведомления в Windows-приложениях — подводные камни NotifyIcon и выбор правильного AppNotification
Практическое руководство о том, как удерживать бизнес-приложение Windows в области уведомлений (system tray) и оповещать пользователя с п...
Автоматическое UI-тестирование десктопных приложений Windows — как устроен UI Automation и как строить устойчивые тесты на FlaUI
Разбираем автоматическое UI-тестирование WinForms/WPF-приложений от основ Windows UI Automation. Минимальная реализация на FlaUI, проекти...
Высокий DPI в WPF — почему всё «должно быть само в порядке», а на деле размывается и плывёт, и что с этим делать
WPF по умолчанию System DPI Aware, но при переносе окна на монитор с другим DPI всё изображение размывается, а растровые картинки становя...
Поддержка высокого DPI в WinForms — почему интерфейс размывается или разваливается на 4K-мониторах, и как это исправить
Разбираем причины размытия и поломки интерфейса WinForms-приложений на 4K-мониторах через DPI-виртуализацию и режимы DPI-осведомлённости ...
После IE-режима — WebView2? Ограничение по ActiveX и реалистичный план миграции
Разбираем базовую архитектуру WebView2, стратегии распространения Evergreen и Fixed Version, ловушку с папкой пользовательских данных, сп...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Поток UI и таймеры
Поток UI WPF / WinForms, асинхронные операции, Dispatcher и проектирование таймеров.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- В чём преимущество внедрения аутентификации Entra ID в приложение WinForms/WPF?
- Хранение паролей, обработка сброса, отключение учётных записей уволенных сотрудников и аудит входов полностью переходят на сторону тенанта, а зона ответственности приложения резко сокращается. Достаточно отключить учётную запись Entra ID уволенного сотрудника — и он мгновенно теряет доступ ко всем приложениям, а многофакторная аутентификация и условный доступ автоматически распространяются и на внутренние приложения. Реализация также укладывается в библиотеку MSAL.NET и несколько десятков строк кода. У организации, уже внедрившей Microsoft 365, почти не остаётся причин продолжать поддерживать собственную аутентификацию.
- Можно ли использовать схему, при которой имя пользователя и пароль принимаются на собственном экране и проверяются напрямую (ROPC)?
- Считайте, что для нового кода это запрещено. ROPC для публичных клиентов официально задокументирован как «устаревший (deprecated) из-за риска для безопасности», и опубликовано руководство по миграции. Схема несовместима с MFA и условным доступом, поэтому пользователи, для которых в тенанте включена обязательная MFA, попросту блокируются и не могут войти. Обязательность MFA может быть включена настройкой на стороне тенанта в любой момент, поэтому даже если сейчас всё работает, есть риск, что однажды все пользователи внезапно потеряют возможность войти.
- Стоит ли использовать брокер WAM?
- На Windows это рекомендуется. Одна строка WithBroker даёт единый вход (SSO) с уже выполненным входом в Windows, поддержку условного доступа, Windows Hello и ключей FIDO, а также привязку refresh-токена к устройству. Если офисный ПК подключён к Entra, после запуска приложения достаточно выбрать учётную запись Windows — вход завершится без ввода пароля. Однако брокер работает только с Entra ID, и по конструкции выдаёт ошибку вне интерактивной пользовательской сессии — например, в службах Windows или в планировщике заданий.
- Почему при каждом перезапуске приложения снова появляется экран входа?
- Потому что кэш токенов не сохраняется постоянно. По умолчанию кэш токенов MSAL.NET существует только в памяти, и на десктопе реализация постоянного хранения — ответственность самого приложения. Официальная рекомендация — пакет Microsoft.Identity.Client.Extensions.Msal, при использовании которого на Windows кэш сохраняется в зашифрованном виде. Даже при использовании WAM постоянное хранение кэша всё равно необходимо для сохранения ID-токена и метаданных учётной записи.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.
Публичные ссылки