Integrar la autenticación de Entra ID en aplicaciones WinForms/WPF — Configuración práctica con MSAL.NET y el bróker WAM
· Actualizado el: · Go Komura · Windows, C#, .NET, WinForms, WPF, Entra ID, Autenticación, Seguridad, Consultoría técnica
«Creamos una pantalla de inicio de sesión para cada aplicación interna y gestionamos las contraseñas en nuestra propia base de datos. Cada vez que alguien deja la empresa es un suplicio desactivar su cuenta aplicación por aplicación» o «Ya tenemos Microsoft 365 implantado en toda la empresa: ¿no podríamos usar directamente esa cuenta para iniciar sesión?». Este es un tema que crece de forma constante desde hace varios años entre las consultas de modernización de aplicaciones de escritorio. A veces llega también en la forma de una petición del departamento de sistemas —motivada por un incidente de filtración de contraseñas o por el contexto de la adopción de Zero Trust— para «dejar de gestionar contraseñas por nuestra cuenta».
Yendo directo a la conclusión: en una organización que usa Microsoft 365, migrar el inicio de sesión de las aplicaciones internas WinForms / WPF a Entra ID (antes Azure AD) es una inversión bien orientada. La aplicación deja de custodiar contraseñas por completo, y las defensas y auditorías del lado del inquilino —autenticación multifactor (MFA), acceso condicional, registros de inicio de sesión— pasan a aplicarse directamente también a las aplicaciones internas. La implementación, además, se resuelve con la biblioteca MSAL.NET y unas pocas decenas de líneas de código.
Sin embargo, hay varias trampas propias de las aplicaciones de escritorio. El diseño tradicional de «recibir el nombre de usuario y la contraseña en cuadros de texto y autenticar por detrás» (ROPC) va camino de la obsolescencia oficial, y no debe adoptarse en desarrollos nuevos. Si no se persiste la caché de tokens, la pantalla de inicio de sesión aparece cada vez que se arranca la aplicación, y en Windows usar o no el bróker (WAM) cambia enormemente tanto la experiencia como la seguridad. Este artículo recorre, de principio a fin, desde una introducción mínima de los conceptos hasta el registro de la aplicación, la implementación con MSAL.NET, WAM, la caché, el criterio de adopción y las trampas operativas.
Público destinatario y entorno de referencia de este artículo
| Elemento | Contenido |
|---|---|
| Público destinatario | Desarrolladores que van a incorporar el inicio de sesión con una cuenta de Microsoft 365 en una aplicación de negocio WinForms / WPF existente o nueva |
| Requisitos previos del inquilino | Tener Microsoft 365 / Entra ID ya implantado. El registro de la aplicación y el consentimiento del administrador (capítulo 3) son tareas del lado del inquilino, así que si usted no puede realizarlas, use directamente el capítulo 3 como solicitud para el departamento de sistemas |
| Entorno de ejecución | Windows. Si va a usar el bróker WAM (capítulo 5), se requiere Windows 10 (1703) o posterior / Windows Server 2019 o posterior. En versiones anteriores, o en Mac/Linux, se recurre automáticamente al navegador1 |
| .NET | .NET Framework 4.6.2 o posterior, o .NET 6 o posterior. El navegador predeterminado que se usa en la autenticación interactiva difiere según el framework (capítulo 4)2 |
| Paquetes NuGet | Microsoft.Identity.Client (obligatorio), Microsoft.Identity.Client.Broker (para WAM; MSAL.NET 4.52.0 o posterior)1, Microsoft.Identity.Client.Extensions.Msal (persistencia de la caché de tokens)3 |
| Red | El primer inicio de sesión y la renovación de tokens requieren conectividad con Entra ID. No es viable en un entorno completamente sin conexión (capítulo 8) |
Abreviaturas usadas en este artículo
| Abreviatura | Nombre completo | Significado en este artículo |
|---|---|---|
| MSAL | Microsoft Authentication Library | Biblioteca de autenticación que ofrece Microsoft. La versión para .NET es MSAL.NET (paquete NuGet Microsoft.Identity.Client) |
| SSO | Single Sign-On (inicio de sesión único) | Estado en el que, tras iniciar sesión una vez, no hace falta volver a autenticarse en otra aplicación |
| MFA | Multi-Factor Authentication (autenticación multifactor) | Autenticación que exige, además de la contraseña, otro factor como una app del teléfono o datos biométricos |
| FIDO | Fast IDentity Online | Estándar de autenticación sin contraseña. En este artículo se refiere a llaves de seguridad de tipo USB, entre otras |
| JWT | JSON Web Token | Formato de token que transporta reclamaciones (atributos del usuario) en un JSON firmado. Es la sustancia del token de ID y del token de acceso |
| ROPC | Resource Owner Password Credentials | Flujo en el que la aplicación recibe directamente el nombre de usuario y la contraseña para autenticar. Va camino de la obsolescencia (apartado 2.3) |
| WAM | Web Account Manager | Bróker de autenticación integrado en Windows (capítulo 5) |
| UPN | User Principal Name | Nombre de inicio de sesión con el formato taro@example.co.jp. Puede cambiar, por ejemplo, si cambia el apellido (apartado 7.1) |
1. La conclusión, primero
- Si abandona la gestión propia de ID y contraseña y la traslada a Entra ID, el almacenamiento de contraseñas, la atención de restablecimientos, la desactivación de cuentas al causar baja y la auditoría de inicios de sesión pasan por completo a ser tareas del inquilino. La drástica reducción del alcance de responsabilidad de la aplicación es la mayor ventaja.
- Una aplicación de escritorio es un cliente público. Como el ejecutable puede analizarse en el destino de distribución, no puede (ni debe) tener un secreto de cliente. El registro de la aplicación también se configura como cliente público.4
- El ROPC (Resource Owner Password Credentials), en el que la aplicación recibe directamente el nombre de usuario y la contraseña, está documentado oficialmente como «obsoleto (deprecated)», y ya existe una guía de migración. No es compatible con MFA ni con el acceso condicional, y va camino de quedar inutilizable en la práctica. Considere prohibida su adopción en desarrollos nuevos.56
- La implementación se hace con MSAL.NET (Microsoft.Identity.Client), y el único patrón básico de llamada es primero
AcquireTokenSilent, y si apareceMsalUiRequiredException, entoncesAcquireTokenInteractive.7 - En Windows se recomienda la autenticación a través del bróker WAM (Web Account Manager). Con una sola línea,
WithBroker, se obtiene SSO con la cuenta con la que ya se inició sesión en Windows, compatibilidad con acceso condicional, Windows Hello y llaves FIDO, y el enlace del token de actualización al dispositivo.1 - Si se olvida la persistencia de la caché de tokens, la pantalla de inicio de sesión aparece cada vez que se reinicia la aplicación. Incorpore desde el principio la caché cifrada de
Microsoft.Identity.Client.Extensions.Msal.3 - La autenticación de Entra es un mecanismo que presupone conectividad de red. No es viable en aplicaciones de campo que deban funcionar completamente sin conexión, así que use primero la tabla de criterios del capítulo 8 para decidir si conviene implantarla.
2. Panorama general — qué significa dejar de gestionar contraseñas por cuenta propia
2.1 Qué problema tiene la gestión propia
Cuando una aplicación de negocio gestiona contraseñas en su propia tabla de usuarios, las siguientes responsabilidades recaen por completo en la aplicación (es decir, en quienes la desarrollamos).
- Almacenamiento: elegir e implementar el esquema de hash (todavía nos encontramos con tablas de 15 años que siguen usando MD5 sin sal)
- Operación: atender consultas de restablecimiento de contraseña, bloqueos de cuenta, distribución de contraseñas iniciales
- Ciclo de vida: desactivación de cuentas al causar baja o cambiar de puesto. Si hay 5 aplicaciones, hay que desactivar la cuenta 5 veces
- Auditoría: registro y conservación de quién inició sesión y cuándo. La autenticación multifactor resulta, en la práctica, imposible de implementar
Al delegar la autenticación en Entra ID, estas cuatro responsabilidades desaparecen del código de la aplicación y se unifican en la gestión del inquilino. Al desactivar la cuenta de Entra ID de un empleado que se va, este pierde el acceso a todas las aplicaciones de inmediato, y el registro de inicio de sesión queda guardado automáticamente. En una organización que ya tiene implantado Microsoft 365, prácticamente no hay motivo para seguir manteniendo autenticación propia. Por cierto, el mecanismo equivalente para organizaciones con Google Workspace que trasladan el propio inicio de sesión de Windows a una cuenta de Google lo explico en «Qué es GCPW».
2.2 Los conceptos mínimos — cliente público y tokens
Omitimos la explicación de manual de OAuth 2.0 / OpenID Connect y enumeramos solo los conceptos necesarios para implementar una aplicación de escritorio.
| Concepto | Significado en una aplicación de escritorio |
|---|---|
| Cliente público | Aplicaciones como un ejecutable o una app móvil que no pueden guardar de forma segura un secreto (secreto de cliente). Solo pueden obtener tokens en nombre del usuario |
| Cliente confidencial (confidential client) | Aplicaciones como un servidor web o un demonio que sí pueden guardar secretos o certificados. Una aplicación de escritorio no entra en esta categoría |
| Token de ID | JWT (JSON Web Token) que representa «quién es esta persona». Basta con esto si solo se necesita la función de inicio de sesión |
| Token de acceso | Pase para llamar a una API concreta (Microsoft Graph o una API web propia). Lleva incorporados el destinatario (audience) y el ámbito (scope) |
| Token de actualización (refresh token) | Token para renovar los dos anteriores sin interacción. MSAL lo gestiona automáticamente dentro de la caché y no es visible directamente para la aplicación |
Lo importante es la primera fila. Como el ejecutable puede analizarse y descompilarse en el destino de distribución, cualquier «secreto» que se incruste deja de ser secreto. Por eso se registra como un cliente público que funciona sin secreto, y el diseño consiste en delegar la autenticación en sí (la introducción de la contraseña, el MFA) al navegador o al bróker del sistema operativo, y que la aplicación reciba únicamente el token. Que la aplicación nunca toque la contraseña del usuario es, precisamente, la base de todo este mecanismo.
2.3 ROPC es un método superado — lo que confirma la documentación oficial
El planteamiento tradicional tiende a ser «basta con recibir el nombre de usuario y la contraseña en nuestra propia pantalla de inicio de sesión y pedirle a Entra ID que los verifique por detrás». Esto es ROPC (entrega directa de usuario y contraseña), que aún se mantiene en MSAL.NET como AcquireTokenByUsernamePassword, pero la documentación oficial actual es tajante al respecto.
- El ROPC para clientes públicos está documentado explícitamente como «obsoleto (deprecated) por riesgo de seguridad», y hay publicada una guía de migración hacia flujos más seguros.6
- ROPC es incompatible con MFA y con el acceso condicional. Los usuarios para los que el inquilino exige MFA quedan bloqueados y no pueden iniciar sesión con este flujo.5
- No funciona el SSO, tampoco pueden usarse cuentas personales de Microsoft, ni pueden iniciar sesión cuentas sin contraseña (FIDO, Authenticator).5
- Del lado de las API web de Microsoft también avanza la tendencia a aceptar solo tokens ya verificados con MFA, y la propia documentación oficial afirma que «las aplicaciones que dependen de ROPC quedarán excluidas (locked out); las aplicaciones de escritorio deben migrar a una autenticación basada en bróker».5
Como la exigencia de MFA puede activarse en cualquier momento mediante la configuración del inquilino, adoptar ROPC con el argumento de «funciona ahora» hace que un día, de repente, todos los usuarios dejen de poder iniciar sesión. Si una aplicación existente ya funciona con ROPC, planifique también su migración. En la práctica, los métodos de obtención de tokens que sí pueden usarse en una aplicación de escritorio son estos dos:
| Flujo | Cuándo usarlo |
|---|---|
| Interactivo (bróker / navegador) | Aplicaciones GUI habituales. La opción principal |
| Flujo de código de dispositivo | Entornos donde no se puede mostrar un navegador (por ejemplo, una consola a través de SSH). Se muestra una URL y un código, y el usuario inicia sesión desde el navegador de otro dispositivo |
3. Registro de la aplicación — configuración en el centro de administración de Entra
Antes de escribir código, hay que registrar la aplicación en el inquilino. Si el desarrollador no puede hacerlo por sí mismo, use directamente el contenido de este apartado como solicitud para el departamento de sistemas.
Como el diseño de las pantallas del centro de administración puede cambiar con las actualizaciones, en este artículo describimos el nombre de la pantalla a la que se llega y lo que hay que pulsar en ella, en lugar del aspecto de los menús. Primero presentamos una lista con el conjunto del trabajo.
| Objetivo | Pantalla que hay que abrir | Qué hacer en esa pantalla |
|---|---|---|
| Registrar la aplicación | «Registros de aplicaciones» | «Nuevo registro» → indicar el nombre y el «Tipo de cuentas compatibles» (apartado 3.1) |
| Anotar los ID | «Información general» de la aplicación registrada | Copiar el «Id. de aplicación (cliente)» y el «Id. de directorio (inquilino)» |
| Añadir el URI de redirección | «Autenticación» de la aplicación registrada | «Agregar una plataforma» → «Aplicaciones móviles y de escritorio» → introducir el URI (los 3 de la tabla del apartado 3.2) |
| Añadir permisos | «Permisos de API» de la aplicación registrada | «Agregar un permiso» → «Microsoft Graph» → «Permisos delegados» → User.Read (apartado 3.3) |
| Otorgar el consentimiento del administrador | «Permisos de API» de la aplicación registrada | Ejecutar «Conceder consentimiento de administrador para (nombre del inquilino)» (apartado 3.3) |
| Permitir clientes públicos | «Autenticación» de la aplicación registrada | «Configuración avanzada» → «Permitir flujos de cliente público» (apartado 3.4) |
3.1 El registro en sí
Se crea desde el Centro de administración de Microsoft Entra (entra.microsoft.com), en [Registros de aplicaciones] → [Nuevo registro].8
- Nombre: aparece en la pantalla de consentimiento y en el registro de inicios de sesión, así que use un nombre reconocible para el negocio, como «Sistema de gestión de inventario».
- Tipos de cuentas compatibles: para una aplicación interna, la única opción es «Cuentas en este directorio organizativo únicamente» (inquilino único). El multiinquilino solo se aplica a productos que se distribuyen a varias organizaciones.
- Anote el Id. de aplicación (cliente) y el Id. de directorio (inquilino) que aparecen tras el registro, e incorpórelos a la configuración de la aplicación (ninguno de los dos es información secreta).
3.2 URI de redirección — la plataforma es «Aplicaciones móviles y de escritorio»
Es la declaración del lugar donde se recibe el token tras la autenticación. Vaya a [Autenticación] → [Agregar una plataforma] → [Aplicaciones móviles y de escritorio] y registre el URI correspondiente al método de autenticación.4
| Método de autenticación | URI de redirección que hay que registrar |
|---|---|
| Bróker WAM (opción principal, capítulo 5) | ms-appx-web://microsoft.aad.brokerplugin/{ID de cliente} |
| Navegador del sistema | http://localhost |
| Navegador incrustado | https://login.microsoftonline.com/common/oauth2/nativeclient |
El ms-appx-web://... para WAM no se escribe en el código del lado de MSAL, pero sí es obligatorio en el registro de la aplicación.9 Teniendo en cuenta la posibilidad de recurrir al navegador (capítulo 5) en entornos donde WAM no está disponible, en la práctica conviene registrar desde el principio los 3 URI de la tabla. Hay que prestar especial atención al comportamiento de WithDefaultRedirectUri(): el valor al que se resuelve depende de la plataforma. En .NET Framework se resuelve a https://login.microsoftonline.com/common/oauth2/nativeclient, y en .NET (Core en adelante) se resuelve a http://localhost.10 Si una aplicación .NET Framework que solo tiene registrados ms-appx-web y http://localhost recurre al navegador desde WAM, se produce un error de autenticación por la falta de coincidencia con nativeclient. Registre los tres URI, o fíjelo explícitamente con WithRedirectUri(...). Otro tropiezo habitual es registrarlo por error en la plataforma «Web», lo que también provoca un error de autenticación.
3.3 Permisos de API y consentimiento del administrador
En [Permisos de API] se añaden los permisos delegados (delegated permission) de la API que la aplicación va a llamar. Si solo se necesita el inicio de sesión y mostrar el perfil, basta con User.Read de Microsoft Graph, que se otorga de forma predeterminada.
Tras añadirlo, pida que se ejecute [Conceder consentimiento de administrador para (nombre del inquilino)].8 Con esto deja de aparecer el diálogo de consentimiento individual para cada usuario en el primer inicio de sesión. En los inquilinos donde el consentimiento del propio usuario está deshabilitado, sin el consentimiento del administrador el primer inicio de sesión se detiene con el mensaje «Se necesita la aprobación del administrador», así que la norma para aplicaciones de distribución interna es dejar resuelto el consentimiento del administrador antes de distribuirlas.
3.4 El indicador «Permitir flujos de cliente público»
La opción «Permitir flujos de cliente público» de la configuración avanzada de [Autenticación] se pone en «Sí» cuando se usa un flujo que no utiliza un URI de redirección, como el flujo de código de dispositivo o la autenticación integrada de Windows.4 No es obligatorio si solo se usa el modo interactivo (navegador / bróker). Además, en este registro de aplicación no se crea ni un secreto de cliente ni un certificado. Que la sección «Certificados y secretos» esté vacía es el estado correcto de un cliente público (como es un motivo frecuente de confusión, lo retomamos en el capítulo 9).
4. Implementación con MSAL.NET — el patrón básico Silent → Interactive
Antes de nada, mostramos en un diagrama quién hace qué en este patrón básico. Basta con captar el punto de que la aplicación nunca recibe la contraseña; solo recibe el token.
sequenceDiagram
participant APP as Aplicación de escritorio
participant MSAL as MSAL.NET (caché de tokens)
participant UI as Bróker / navegador
participant EID as Entra ID
APP->>MSAL: AcquireTokenSilent
alt Hay un token utilizable en la caché
MSAL-->>APP: Token de acceso (sin mostrar pantalla)
else No hay ninguno, o no se puede renovar
MSAL-->>APP: MsalUiRequiredException
APP->>MSAL: AcquireTokenInteractive
MSAL->>UI: El destino del diálogo lo decide la configuración (capítulo 5)
UI->>EID: Inicio de sesión (MFA / Windows Hello / FIDO)
EID-->>UI: Resultado de la autenticación
UI-->>MSAL: Devuelve el resultado (lo que se devuelve varía según la ruta, figura 2)
MSAL-->>APP: Token de acceso
end
Figura 1: la introducción de la contraseña se completa dentro del bróker o del navegador, y lo único que llega a la aplicación es el token. Por eso la aplicación no necesita un secreto. Lo que se hace a partir de ahí con el token recibido para llamar a una API se trata en el capítulo 7
Añada Microsoft.Identity.Client desde NuGet. Basta con recordar un único patrón de implementación: llame siempre primero a AcquireTokenSilent, y recurra al modo interactivo solo cuando reciba MsalUiRequiredException. AcquireTokenInteractive está diseñado para no consultar la caché en absoluto, así que si se llama directamente, la pantalla de inicio de sesión aparece cada vez.7
using Microsoft.Identity.Client;
public sealed class AuthService
{
private const string ClientId = "Id. de aplicación (cliente)";
private const string TenantId = "Id. de directorio (inquilino)";
private static readonly string[] Scopes = { "User.Read" };
private readonly IPublicClientApplication _app;
public AuthService()
{
_app = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.WithRedirectUri("http://localhost") // Para el navegador del sistema
.Build();
// En producción, aquí se registra la persistencia de la caché de tokens (capítulo 6)
}
public async Task<AuthenticationResult> SignInAsync(IntPtr ownerHwnd)
{
// 1. Intentar siempre primero la obtención silenciosa con una cuenta ya en caché
var accounts = await _app.GetAccountsAsync();
var account = accounts.FirstOrDefault();
try
{
return await _app.AcquireTokenSilent(Scopes, account)
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// 2. Mostrar la pantalla de inicio de sesión solo cuando hace falta interacción.
// El valor predeterminado de .NET Framework es el WebView incrustado antiguo,
// así que se indica explícitamente la redirección http://localhost = navegador del sistema
// (.NET 6+ ya usa siempre el navegador del sistema)
return await _app.AcquireTokenInteractive(Scopes)
.WithAccount(account)
.WithParentActivityOrWindow(ownerHwnd)
.WithUseEmbeddedWebView(false)
.ExecuteAsync();
}
}
}
En el lado que hace la llamada, se pasa el identificador (handle) de la ventana propietaria. Esto evita el problema de que el diálogo de autenticación quede oculto detrás de la aplicación, y en WAM es obligatorio.1 Otro detalle: no omita WithUseEmbeddedWebView(false) en una aplicación .NET Framework. El valor predeterminado de la autenticación interactiva en .NET Framework es el WebView incrustado, mientras que la redirección http://localhost es la del navegador del sistema (si la combinación no coincide, se puede caer en el navegador incrustado antiguo, donde no funcionan el acceso condicional ni Windows Hello / FIDO, o producirse una discrepancia de URI de redirección).2 En .NET 6 en adelante ya no existe el WebView incrustado y siempre se usa el navegador del sistema, así que esta indicación es redundante pero inofensiva.
// WinForms (dentro de un método del 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 = $"Sesión iniciada: {result.Account.Username}";
Puntos a tener en cuenta:
IPublicClientApplicationse reutiliza como una única instancia en toda la aplicación. Como cada instancia tiene su propia caché, crearla conCreateen cada llamada hace que la obtención silenciosa deje de funcionar.MsalUiRequiredExceptionno es un «error», sino el flujo de control normal que indica «hace falta interacción». Se produce en el primer arranque, cuando caduca el token de actualización, cuando cambian los requisitos de acceso condicional, etc.- El uso correcto es llamar a
AcquireTokenSilentcada vez, justo antes de llamar a la API. Si hay un token válido en la caché, se devuelve de inmediato, y si está próximo a caducar, se renueva automáticamente.7 No conserve ni gestione usted mismo el ciclo de vida del token de acceso. - Esperar con
.Resulto.Wait()en el hilo de la interfaz de usuario produce un interbloqueo (vea «Async y el hilo de la interfaz en WPF/WinForms en una sola hoja»).
5. El bróker WAM — configuración recomendada en Windows
El código del capítulo 4 abre un navegador, pero en Windows existe un método todavía mejor. WAM (Web Account Manager) es un bróker de autenticación integrado en Windows 10 (1703 en adelante) y Windows Server 2019 en adelante, y la documentación oficial señala las siguientes cuatro ventajas.1
- Refuerzo de la seguridad: el token de actualización queda enlazado al dispositivo, así que aunque se robe no puede usarse en otro equipo (protección de tokens). Las mejoras de seguridad llegan de forma continua con las actualizaciones del sistema operativo.
- Soporte de funciones: pueden usarse sin código adicional funciones de autenticación integradas con el sistema operativo y los servicios, como Windows Hello, el acceso condicional y las llaves FIDO.
- Integración con el sistema: las cuentas con las que ya se inició sesión en Windows aparecen en el selector de cuentas integrado, de modo que en muchos casos el inicio de sesión se completa sin introducir ninguna contraseña. Es, en la práctica, un SSO.
- Protección de tokens: es compatible con las directivas de protección de tokens del acceso condicional.
Si el PC corporativo está unido a Entra (o en unión híbrida, Hybrid Join), la experiencia se reduce a «iniciar la aplicación → elegir la cuenta de Windows → inicio de sesión completado al instante», sin escribir ninguna contraseña en ningún momento.
Esto es precisamente lo que la figura 1 señalaba como «lo que se devuelve varía según la ruta». El bróker no es un simple sustituto del navegador: es el actor que completa por sí mismo la obtención del token y devuelve el resultado.
flowchart TB
A["AcquireTokenInteractive"] --> Q{"¿WithBroker está habilitado<br/>y el entorno admite WAM?"}
Q -->|"Sí"| BR["Bróker WAM<br/>El bróker completa la obtención del token<br/>y lo devuelve a MSAL"]
Q -->|"No / entorno no compatible (capítulo 4)"| BW["Navegador del sistema<br/>Devuelve el código de autorización a MSAL,<br/>que lo intercambia por un token con Entra ID"]
BR --> T["MSAL lo guarda en la caché y lo devuelve a la app"]
BW --> T
Figura 2: al cambiar el destino de la interacción, cambia también lo que recibe MSAL. A través del bróker, el intercambio del código de autorización no queda en el lado de MSAL
5.1 Implementación — WithBroker y el paquete
Para usar WAM se necesitan MSAL.NET 4.52.0 o posterior y el paquete adicional Microsoft.Identity.Client.Broker.1 Se añade WithBroker al constructor (builder) del capítulo 4.
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker; // Para WithBroker(BrokerOptions)
var brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
{
Title = "Sistema de gestión de inventario" // Título que se muestra en el selector de cuentas
};
_app = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
.WithDefaultRedirectUri()
.WithParentActivityOrWindow(() => _ownerHwnd) // Obligatorio con WAM
.WithBroker(brokerOptions)
.Build();
También se puede reforzar con una línea el lado de la obtención silenciosa. Cuando no hay ninguna cuenta en la caché, pasar PublicClientApplication.OperatingSystemAccount permite intentar un inicio de sesión silencioso con «la cuenta con la que se está actualmente conectado en Windows». Es el patrón recomendado oficialmente, que permite completar el inicio de sesión sin ningún diálogo desde el primer arranque.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();
}
Además, tal como se indicó en el apartado 3.2, registre en el lado de la aplicación ms-appx-web://microsoft.aad.brokerplugin/{ID de cliente} en la plataforma «Aplicaciones móviles y de escritorio».1 Si se olvida esto, la autenticación interactiva falla con un error del bróker. Otro punto: WithDefaultRedirectUri() en el ejemplo anterior determina el URI de redirección para cuando WAM no está disponible y se recurre al navegador, y su resultado depende de la plataforma (.NET Framework → nativeclient, .NET → http://localhost).10 Si ya tiene registrados los 3 URI de la tabla del apartado 3.2, cualquiera de las dos formas funciona; si prefiere limitar el registro, fíjelo explícitamente con WithRedirectUri(...).
5.2 Limitaciones de WAM — problemas si no las conoce
| Limitación | Contenido |
|---|---|
| Sistema operativo | Windows 10 (1703)+ / Windows Server 2019+. En versiones anteriores, o en Mac/Linux, se recurre automáticamente al navegador1 |
| Proveedor de identidad | Exclusivo de Entra ID. Las autoridades de Azure AD B2C y AD FS no son compatibles (se recurre al navegador)1 |
| Contexto de ejecución | Se da por hecho que se puede mostrar la interfaz en una sesión de usuario interactiva. En un servicio de Windows, el Programador de tareas (fuera de una sesión de usuario) o al ejecutarse como otro usuario con runas, produce un error por diseño1 |
La tercera fila es especialmente importante. Que «funcione en una aplicación con interfaz gráfica, pero falle si se reutiliza el mismo código en un proceso por lotes nocturno» es un comportamiento esperado, no un fallo. La ejecución desatendida es un territorio que debe diseñarse aparte, con permisos de aplicación (cliente confidencial) en lugar de delegación de usuario. Como el mecanismo de reserva (fallback) está incorporado como parte de la especificación, lo bueno de MSAL es que se puede lograr «WAM como primera opción, y si no funciona, el navegador» con un único bloque de código.
5.3 La forma final — ejemplo integrado con el bróker como prioridad y reserva al navegador
El capítulo 4 mostró una configuración solo con navegador, y el apartado 5.1 mostró únicamente lo que se añade para el bróker; ahora los combinamos en una forma que se puede usar tal cual en la práctica. Antes, organizamos las formas de especificar el URI de redirección, que suelen prestarse a confusión.
| Forma de escribirlo | URI de redirección realmente usado | Cuándo usarlo |
|---|---|---|
WithRedirectUri("http://localhost") |
Siempre http://localhost (para el navegador del sistema) |
Cuando se quiere fijar uno de los ya registrados. En .NET 6 en adelante coincide con esto |
WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient") |
Siempre nativeclient | Cuando se usa una configuración con WebView incrustado en .NET Framework |
WithDefaultRedirectUri() |
Depende de la plataforma (.NET Framework → nativeclient, .NET → http://localhost)10 |
Cuando ya están registrados los 3 URI de la tabla del apartado 3.2 y se quiere el mismo código sin importar el framework |
Elija la que elija, mientras WAM esté disponible este URI no llega a entrar en juego. Solo importa cuando se recurre al navegador. El ejemplo integrado que sigue lo fija explícitamente con WithRedirectUri para que no falle aunque se limite el número de URI registrados.
using System.IO;
using System.Linq;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Broker; // Para WithBroker(BrokerOptions)
using Microsoft.Identity.Client.Extensions.Msal; // Para MsalCacheHelper
public sealed class AuthService
{
private const string ClientId = "Id. de aplicación (cliente)";
private const string TenantId = "Id. de directorio (inquilino)";
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;
}
// El registro de la caché es asíncrono, por eso se usa un método de fábrica.
// Crear una única instancia por aplicación y reutilizarla (capítulo 4)
public static async Task<AuthService> CreateAsync(IntPtr ownerHwnd)
{
var brokerOptions = new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
{
Title = "Sistema de gestión de inventario" // Título que aparece en el selector de cuentas
};
var app = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, TenantId)
// URI de redirección para cuando WAM no está disponible y se recurre al navegador.
// En lugar de WithDefaultRedirectUri(), cuyo resultado depende de la plataforma,
// se fija explícitamente el valor ya registrado en la aplicación
.WithRedirectUri("http://localhost")
.WithParentActivityOrWindow(() => ownerHwnd) // Obligatorio con WAM
.WithBroker(brokerOptions) // Si no está disponible, se pasa automáticamente al navegador
.Build();
// Persistencia de la caché de tokens (capítulo 6). Se registra una sola vez, justo después de 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);
}
// Identificador de la cuenta con la que se inició sesión la última vez. Se guarda en la
// configuración de la aplicación y se carga al arrancar (el lugar de guardado puede ser
// la configuración de la app, el registro de Windows, etc.)
private string? _homeAccountId;
// Llamar a esto siempre justo antes de llamar a la API. No conservar el token de acceso por cuenta propia (capítulo 4)
public async Task<AuthenticationResult> AcquireTokenAsync()
{
// 1. Decidir con qué cuenta se intenta el modo silencioso
IAccount? account = await ResolveAccountAsync();
if (account is not null)
{
try
{
return await _app.AcquireTokenSilent(Scopes, account).ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Recurrir al modo interactivo
}
}
// 2. Interactivo. Si WAM está disponible se abre el selector de cuentas;
// si no, se abre el navegador del sistema indicado arriba
AuthenticationResult result =
await _app.AcquireTokenInteractive(Scopes)
.WithParentActivityOrWindow(_ownerHwnd)
.WithUseEmbeddedWebView(false) // Para .NET Framework (capítulo 4)
.ExecuteAsync();
// Recordar a la persona elegida. La próxima vez se intentará el modo silencioso con ella
_homeAccountId = result.Account?.HomeAccountId?.Identifier;
SaveHomeAccountId(_homeAccountId);
return result;
}
private async Task<IAccount?> ResolveAccountAsync()
{
List<IAccount> accounts = (await _app.GetAccountsAsync()).ToList();
// Si la persona elegida la última vez sigue en la caché, usarla
if (_homeAccountId is not null)
{
IAccount? saved = accounts.FirstOrDefault(
a => a.HomeAccountId?.Identifier == _homeAccountId);
if (saved is not null) { return saved; }
}
// Si solo hay un candidato, se puede usar directamente
if (accounts.Count == 1) { return accounts[0]; }
// Cero candidatos, o varios sin ningún criterio para decidir.
// No usar FirstOrDefault aquí (ver más abajo)
return null;
}
}
No reciba el resultado de GetAccountsAsync() con FirstOrDefault(). La caché no tiene por qué contener una sola cuenta. Después de cambiar a otra cuenta, después de que varias personas hayan usado un equipo compartido, después de hacer pruebas cruzando de inquilino — en cualquiera de estos casos quedan varias cuentas. El orden de enumeración no tiene ningún significado, así que FirstOrDefault() elige en silencio a «quien resultó estar primero».
Lo peligroso de esto es que aunque se elija mal, no aparece nada en pantalla. Si el token de esa cuenta sigue vigente en la caché, AcquireTokenSilent tiene éxito y el selector de cuentas ni siquiera se abre. El usuario cree que está operando como sí mismo, pero en realidad muestra y actualiza los datos de Graph de otra persona. Nadie se da cuenta.
Por eso, en el código anterior se decide en el siguiente orden.
| Situación | Qué hacer |
|---|---|
| La persona elegida la última vez sigue en la caché | Intentar el modo silencioso con esa persona |
| Solo hay un candidato | Intentar el modo silencioso con esa persona |
| Cero candidatos, o varios sin ningún criterio para decidir | No intentar el modo silencioso; dejar que el usuario elija de forma interactiva |
HomeAccountId.Identifier es una cadena que representa «qué usuario de qué inquilino», así que si se guarda, la próxima vez se puede volver a entrar como la misma persona (no es un token, así que no hace falta tratarlo como información sensible). Si se quiere que la cuenta con la que se inició sesión en Windows sea el valor predeterminado, también es válido diseñar la última línea para que devuelva PublicClientApplication.OperatingSystemAccount. Lo único que hay que evitar es «decidir por orden de aparición».
El lado que hace la llamada queda así. Ejecute CreateAsync una sola vez al iniciar la aplicación y conserve el valor que devuelve.
// Mantenerlo como campo del formulario (una única instancia por aplicación)
private AuthService _authService;
// Desde, por ejemplo, el controlador de evento Click de un botón. En WPF, igual que en el
// capítulo 4, se obtiene el HWND con WindowInteropHelper
private async Task SignInAsync()
{
_authService ??= await AuthService.CreateAsync(this.Handle);
var result = await _authService.AcquireTokenAsync();
this.Text = $"Sesión iniciada: {result.Account.Username}";
}
Con esto solo, quedan cubiertas las tres rutas: en un equipo donde WAM funciona, basta con elegir la cuenta de Windows; en uno donde no funciona, se abre el navegador del sistema; y tras un reinicio, se obtiene el token de forma silenciosa desde la caché. Lo único que queda es confirmar que el registro de la aplicación tiene los URI de la tabla del apartado 3.2 (al menos ms-appx-web://... y http://localhost).
6. Persistencia de la caché de tokens — evitar que la pantalla de inicio de sesión aparezca en cada reinicio
La caché de tokens de MSAL.NET reside solo en memoria de forma predeterminada, y en una aplicación de escritorio implementar su persistencia es responsabilidad de la propia aplicación. Si no se persiste, cada vez que se reinicia el proceso, AcquireTokenSilent falla y se recurre al inicio de sesión interactivo.7 Casi siempre esta es la causa de la queja «en las pruebas de implantación iba bien, pero ahora llegan quejas del personal porque la pantalla de inicio de sesión aparece cada mañana».
La recomendación oficial es usar la biblioteca de caché multiplataforma 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); // Registrar una vez, justo después de Build()
En Windows, la caché se guarda cifrada. El ejemplo de implementación propia que aparece en la documentación oficial cifra el token con ProtectedData (DPAPI, DataProtectionScope.CurrentUser) y lo guarda en un archivo; Extensions.Msal se sitúa como la biblioteca que lleva ese enfoque a calidad de producto.3 El principio de «proteger los secretos por usuario con el DPAPI de ámbito de usuario» es el mismo del que hablamos, a propósito de los archivos de configuración, en «Almacenamiento de información sensible en aplicaciones de Windows - evitar la configuración en texto plano con DPAPI». Trate una implementación propia que guarde la caché de tokens en JSON en texto plano con la misma gravedad que guardar una cadena de conexión en texto plano.
Tres puntos a tener en cuenta en la operación diaria.
- Incluso usando WAM, la persistencia de la caché es necesaria, porque MSAL sigue guardando el token de ID y los metadatos de la cuenta en su propia caché.9
- El lugar de guardado básico es
%LOCALAPPDATA%\NombreDeLaEmpresa\NombreDeLaApp. Por la vinculación del DPAPI, no se puede descifrar en otro PC o con otro usuario, pero eso solo hace que la obtención silenciosa falle y se vuelva a pedir el inicio de sesión; no causa ningún daño real. - El «cierre de sesión» se implementa eliminando con
RemoveAsynclas cuentas que enumeraGetAccountsAsync. No hace falta borrar el archivo de caché. Ahora bien, lo que borraRemoveAsynces solo la caché local de MSAL; las sesiones del lado de WAM, del navegador y del inicio de sesión de Windows siguen intactas. Como en el siguiente inicio de sesión interactivo la misma cuenta puede volver a iniciar sesión de forma silenciosa, si en un PC compartido se necesita que el cambio de cuenta funcione de verdad, diseñe distinguiendo entre «borrar la caché local» y «un cierre de sesión real»: añadaWithPrompt(Prompt.SelectAccount)aAcquireTokenInteractivepara forzar siempre la pantalla de selección de cuenta, o, según los requisitos, combínelo también con el punto de conexión de cierre de sesión del inquilino.
7. Qué hacer con el token obtenido — tres configuraciones
El uso posterior a una autenticación correcta se divide en tres patrones. Según hasta dónde se llegue, cambia también la configuración necesaria.
| Configuración | Token que se usa | Qué más se necesita |
|---|---|---|
| (1) Solo inicio de sesión | Token de ID (AuthenticationResult.Account / ClaimsPrincipal) |
Nada (basta con User.Read) |
| (2) Llamar a Microsoft Graph | Token de acceso para Graph | Los permisos y el consentimiento de Graph correspondientes a la API que se quiera llamar |
| (3) Proteger una API web propia | Token de acceso para la API propia | Un registro de aplicación y la publicación de un ámbito (scope) del lado de la API, y la verificación del token en la propia API |
7.1 Configuración solo de inicio de sesión — el punto de partida más sencillo
Si solo se quiere «reemplazar la verificación propia de contraseñas, sin llamar a ninguna API en la nube», basta con cotejar la información de la cuenta del resultado del inicio de sesión con la tabla de permisos interna de la aplicación. Se sustituye la clave de la tabla users por el ID de objeto de Entra (que no cambia aunque el UPN cambie, por ejemplo, por un cambio de apellido) y se elimina la columna de contraseña. Como permite sustituir solo la autenticación sin cambiar el diseño de la base de datos local, es la configuración más recomendable como primer paso.
7.2 Llamar a Microsoft Graph
Si se envía tal cual el token de acceso de User.Read a Microsoft Graph, se puede obtener el perfil o la foto del usuario que inició sesión.
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
var me = await http.GetStringAsync("https://graph.microsoft.com/v1.0/me");
Si se amplía a calendario, envío de correo, notificaciones de Teams, etc., hay que añadir el permiso correspondiente (por ejemplo, Mail.Send) y volver a obtener el consentimiento del administrador. Un diseño que traslade a Graph los correos de notificación de una aplicación interna encaja también con el enfoque que tratamos en «Cómo diseñar el envío masivo de correo para pymes sin atarse a un servicio concreto».
7.3 Proteger una API web propia — hasta la verificación de audience y del ámbito
Si la aplicación de escritorio llama a una API web propia, hay que crear también un registro de aplicación aparte para la API, publicar un ámbito como api://{ID de cliente de la API}/access_as_user, y hacer que el lado de escritorio solicite el token con ese ámbito. Lo importante del lado de la API es que está documentado oficialmente que no basta con añadir solo [Authorize].11 Hay tres niveles que verificar.
- Firma y emisor: si el JWT lo emitió el Entra ID del inquilino correcto (con ASP.NET Core + Microsoft.Identity.Web, el middleware se encarga de esto)
- Audience (
aud): si el destinatario del token es esta misma API. No permitir que se reutilice un token de Graph en la API propia - Ámbito (reclamación
scp): si contiene el ámbito esperado. Con Microsoft.Identity.Web se puede declarar con el atributo[RequiredScope("access_as_user")]11
Si se omiten los puntos 2 y 3, se acaba con «una API que deja pasar a cualquiera con tal de que el token parezca de Entra». Inclúyalo en la revisión de diseño junto con los apartados de comunicación y validación de entradas de «Lista de comprobación mínima de seguridad para el desarrollo de aplicaciones de Windows».
8. Criterio de adopción — ¿conviene incorporar la autenticación de Entra a una herramienta puramente interna?
No hace falta incorporarla en todas las aplicaciones internas. Primero, mostramos en un diagrama el orden de la decisión: primero van los dos requisitos previos —la red y la base de identidad— y después se evalúan las circunstancias propias de la aplicación.
flowchart TD
Q1{"¿Se puede llegar a Entra ID<br/>desde el entorno donde corre la app?"}
Q1 -->|"Hay un segmento totalmente offline"| NG1["No es posible, o requiere diseño especial<br/>La renovación del token necesita red"]
Q1 -->|"Se puede llegar"| Q2{"¿Cuál es la base de identidad de la organización?"}
Q2 -->|"Solo AD local"| ALT1["Considerar la autenticación integrada de Windows"]
Q2 -->|"Solo Google Workspace"| ALT2["Considerar el mecanismo del lado de Google"]
Q2 -->|"Microsoft 365 / Entra ID"| Q3{"¿La app tiene el concepto de inicio de sesión,<br/>llamadas a API o requisitos de auditoría?"}
Q3 -->|"No (herramienta de conversión de función única, etc.)"| NO["No implementar<br/>Basta con el inicio de sesión de Windows"]
Q3 -->|"Sí"| YES["Implementar<br/>Se puede abandonar por completo la gestión propia de contraseñas"]
Figura 3: la decisión se toma de arriba abajo. Primero se filtra por el requisito de funcionamiento sin conexión y la base de identidad, y después se evalúa la necesidad del lado de la aplicación
| Situación | Recomendación | Motivo |
|---|---|---|
| Microsoft 365 / Entra ID implantado en toda la empresa, y la app tiene el concepto de inicio de sesión | Implementarlo | Desaparece por completo la deuda técnica de la gestión propia de contraseñas. El coste de implementación es bajo |
| La app llama a una API web propia o a recursos en la nube | Implementarlo | Una base de autenticación es imprescindible para proteger la API. Es más fiable que inventar un token propio |
| Hay requisitos de auditoría (registro de quién la usó y cuándo, exigencia de MFA) | Implementarlo | El registro de inicio de sesión y el acceso condicional quedan centralizados del lado del inquilino |
| Herramienta de función única sin concepto de inicio de sesión (herramienta de conversión, visor, etc.) | No es necesario | No hay motivo para añadir autenticación. Basta con el inicio de sesión de Windows |
| Funciona en un entorno completamente sin conexión (línea de producción aislada, PC que sale de las instalaciones) | No es posible, o requiere diseño especial | El primer inicio de sesión y la renovación de tokens necesitan red obligatoriamente |
| Entra ID no implantado (solo AD local, solo Google Workspace) | Considerar otra solución | En el primer caso, autenticación de AD (autenticación integrada de Windows); en el segundo, el mecanismo del lado de Google resulta más natural |
Preste especial atención al requisito de funcionamiento sin conexión. AcquireTokenSilent puede devolver el token incluso sin conexión mientras el token de acceso de la caché siga siendo válido (por lo general, algo más de una hora, según la experiencia), pero en cuanto caduca, la renovación necesita red. Antes de implantarla, hay que contrastar este supuesto de vigencia con el patrón real de uso sobre el terreno para confirmar que el negocio puede funcionar con esa limitación.
9. Trampas operativas — las consultas que llegan tras la implantación
La implantación no es el final: en la fase de operación surgen consultas habituales. Las anticipamos aquí.
- «Hasta ayer funcionaba y de repente no puedo iniciar sesión»: el primer sospechoso es un cambio en la directiva de acceso condicional. Si el departamento de sistemas activa algo como «bloquear dispositivos no registrados», el inicio de sesión empieza a fallar sin que la aplicación haya cambiado en nada. La forma más rápida de diagnosticarlo es mirar el motivo del error del usuario afectado en el registro de inicio de sesión del centro de administración de Entra. Tener configurado WAM mejora la capacidad de responder a los requisitos de la directiva y, con ello, reduce este tipo de fricción.1
- «Me llegó un aviso de que caduca un secreto: ¿está bien esta aplicación?»: un cliente público no tiene, de entrada, ni secreto ni certificado, así que tampoco hay nada que caduque. Si llega esta consulta, o bien hay confusión con el registro de una aplicación de tipo cliente confidencial, o bien alguien creó un secreto innecesario en el registro pensado para cliente público (en este último caso, se puede eliminar sin problema). Que no pueda producirse, por diseño, una interrupción por caducidad de un secreto es una ventaja oculta de esta configuración.
- «En el primer arranque aparece “Se necesita la aprobación del administrador”»: es una falta de consentimiento del administrador, como se explica en el apartado 3.3. También aparece el mismo mensaje si se añaden permisos más adelante, hasta que se vuelva a obtener el consentimiento para lo añadido.
- «Al incorporarlo a un proceso por lotes nocturno, no funciona»: como se explicó en el apartado 5.2, WAM presupone una sesión interactiva. Para el procesamiento desatendido, en lugar de reutilizar un token delegado de usuario, diseñe algo aparte basado en permisos de aplicación.
- Distribución y actualizaciones: el entorno de MSAL recibe correcciones con frecuencia, así que hace falta un mecanismo que reparta las actualizaciones de la biblioteca a todos los equipos. Considérelo junto con la verificación de la vía de actualización que tratamos en «Diseño de seguridad de la actualización automática».
10. Resumen
La adaptación de una aplicación WinForms / WPF a la autenticación de Entra ID se resume en estos seis puntos.
- El objetivo en sí es dejar de gestionar contraseñas por cuenta propia. El almacenamiento, los restablecimientos, la atención a las bajas de personal y la auditoría se centralizan del lado del inquilino
- Una aplicación de escritorio es un cliente público: no puede tener un secreto, y tampoco lo necesita
- El ROPC va camino de la obsolescencia. No cree pantallas nuevas que reciban el nombre de usuario y la contraseña
- La implementación se resuelve con un único patrón de MSAL.NET:
AcquireTokenSilent→AcquireTokenInteractive - En Windows, el bróker WAM (
WithBroker) aporta SSO, acceso condicional y compatibilidad con Windows Hello - Incorpore desde el principio la persistencia de la caché de tokens (Extensions.Msal / protección con DPAPI)
Con la configuración mínima de «sustituir solo el inicio de sesión» (apartado 7.1), el impacto en una aplicación existente puede limitarse a la pantalla de inicio de sesión y al entorno de la tabla de usuarios, y a menudo se resuelve con una modificación de pocos días. Por otro lado, cuando entran en juego el acceso condicional o los requisitos de funcionamiento sin conexión, hace falta una decisión de diseño que tenga en cuenta tanto la configuración del inquilino como la realidad del negocio. Si no tiene claro hasta qué configuración debería llevar su aplicación, o cómo dividir en pasos la migración desde la autenticación propia, podemos ayudarle.
Artículos relacionados
- Qué es GCPW - cómo gestionar el inicio de sesión de Windows con la autenticación de Google
- Almacenamiento de información sensible en aplicaciones de Windows - evitar la configuración en texto plano con DPAPI
- Lista de comprobación mínima de seguridad para el desarrollo de aplicaciones de Windows
- Diseño de seguridad de la actualización automática - por qué HTTPS no basta
Áreas de consultoría relacionadas
En KomuraSoft LLC nos encargamos de incorporar la autenticación de Entra ID en aplicaciones WinForms / WPF existentes (diseño del registro de la aplicación, implementación con MSAL.NET, plan de migración desde la autenticación propia), de la revisión de diseño de la verificación de tokens en APIs web propias, y del diagnóstico de fallos de inicio de sesión relacionados con el acceso condicional.
- Desarrollo de aplicaciones de Windows
- Consultoría técnica y revisión de diseño
- Aprovechamiento de activos existentes y apoyo a la migración
- Contacto
Referencias
-
Microsoft Learn, Using MSAL.NET with Web Account Manager (WAM). Sobre las ventajas del bróker (refuerzo de la seguridad, compatibilidad con Windows Hello, acceso condicional y FIDO, selector de cuentas, protección de tokens), MSAL.NET 4.52.0+ y el paquete Microsoft.Identity.Client.Broker, la obligatoriedad de WithBroker y del identificador de la ventana propietaria, el URI de redirección ms-appx-web, y las limitaciones de sistemas operativos compatibles, reserva al navegador y necesidad de una sesión interactiva. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11
-
Microsoft Learn, Using web browsers (MSAL.NET). Sobre la tabla de compatibilidad de navegadores por framework (el valor predeterminado de .NET Framework 4.6.2+ es el incrustado, y .NET 6+ usa siempre el navegador del sistema), la necesidad del URI de redirección http://localhost para el navegador del sistema, y el cambio mediante WithUseEmbeddedWebView. ↩ ↩2
-
Microsoft Learn, Token cache serialization. Sobre la recomendación de que las aplicaciones de escritorio usen la caché multiplataforma de Microsoft.Identity.Client.Extensions.Msal, el uso de MsalCacheHelper, y el ejemplo de serialización propia con ProtectedData (DPAPI, ámbito CurrentUser). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Desktop app that calls web APIs: Code configuration. Sobre el URI de redirección de las aplicaciones de escritorio (la plataforma móvil y de escritorio, nativeclient / localhost) y el significado de la configuración «Permitir flujos de cliente público». ↩ ↩2 ↩3
-
Microsoft Learn, Microsoft identity platform and OAuth 2.0 Resource Owner Password Credentials. Sobre por qué no debe usarse ROPC, su incompatibilidad con MFA y el bloqueo que provoca, la tendencia a excluir a las aplicaciones que dependen de ROPC, y la recomendación de que las aplicaciones de escritorio migren a una autenticación basada en bróker. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Desktop app that calls web APIs: Acquire a token using username and password. Sobre la consideración del flujo de usuario y contraseña (ROPC) como obsoleto (deprecated) por riesgo de seguridad, la referencia a la guía de migración, y las limitaciones de incompatibilidad con MFA, acceso condicional y SSO. ↩ ↩2
-
Microsoft Learn, Get a token from the token cache using MSAL.NET. Sobre el patrón recomendado de llamar primero a AcquireTokenSilent y recurrir al modo interactivo con MsalUiRequiredException, la renovación automática mediante la caché y el token de actualización, y la limpieza de la caché al eliminar una cuenta. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Register an application with the Microsoft identity platform. Sobre el procedimiento de registro de una aplicación en el centro de administración de Entra, la elección del tipo de cuentas compatibles, la obtención del ID de cliente y el consentimiento del administrador. ↩ ↩2
-
Microsoft Learn, Desktop app that calls web APIs: Acquire a token by using WAM. Sobre la necesidad de persistir la caché de tokens también al usar WAM, el patrón recomendado de inicio de sesión silencioso con OperatingSystemAccount, y la configuración del URI de redirección en el registro de la aplicación. ↩ ↩2 ↩3
-
Microsoft Learn, Default reply URI. Sobre el hecho de que el URI de redirección que fija WithDefaultRedirectUri depende de la plataforma (en un escritorio .NET Framework es https://login.microsoftonline.com/common/oauth2/nativeclient, y en .NET Core es http://localhost). ↩ ↩2 ↩3
-
Microsoft Learn, Protected web API: Verify scopes and app roles. Sobre que el atributo [Authorize] por sí solo no es suficiente y es necesario verificar la reclamación scp (el ámbito), y sobre la verificación declarativa mediante el atributo RequiredScope de Microsoft.Identity.Web. ↩ ↩2
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Iconos de la bandeja del sistema y notificaciones toast en aplicaciones Windows — los escollos de NotifyIcon y cómo elegir el AppNotification adecuado
Organiza la implementación de la residencia en la bandeja del sistema y las notificaciones toast en aplicaciones Windows empresariales: e...
Pruebas de UI automatizadas para aplicaciones de escritorio de Windows ── el funcionamiento de UI Automation y pruebas resistentes a roturas con FlaUI
Organizamos las pruebas de UI automatizadas para apps WinForms/WPF desde el funcionamiento de Windows UI Automation: implementación mínim...
Compatibilidad de WPF con alto DPI ── causas y soluciones del desenfoque pese a que «debería ser resistente al DPI»
WPF es System DPI Aware, pero al moverse a un monitor con otro DPI toda la ventana se difumina y los mapas de bits se ven borrosos. Repas...
Práctica de CI/CD para aplicaciones WinForms / WPF — automatizar desde la compilación hasta la firma y la distribución con GitHub Actions
CI/CD para WinForms/WPF con GitHub Actions: build y pruebas, versión por tags, firma con signtool y tabla de decisión por formato de dist...
Cuando su aplicación Windows de desarrollo propio es tratada como virus — cómo abordar los falsos positivos de Microsoft Defender y su impacto en el rendimiento
Procedimiento oficial para resolver falsos positivos de Microsoft Defender en apps Windows propias: por qué ocurren, cómo reportarlos, re...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Hilo de UI y temporizadores
Hilo de UI de WPF / WinForms, flujos asíncronos, Dispatcher y diseño de temporizadores.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Qué ventajas tiene incorporar la autenticación de Entra ID en una aplicación WinForms/WPF?
- El almacenamiento de contraseñas, la atención de restablecimientos, la desactivación de cuentas de empleados que dejan la empresa y la auditoría de inicios de sesión pasan a ser responsabilidad del inquilino (tenant), lo que reduce drásticamente el alcance de responsabilidad de la aplicación. Al desactivar la cuenta de Entra ID de un empleado que se va, este pierde el acceso a todas las aplicaciones de inmediato, y la autenticación multifactor y el acceso condicional se aplican directamente a las aplicaciones internas. La implementación también se resuelve con la biblioteca MSAL.NET y unas pocas decenas de líneas de código. En una organización que ya usa Microsoft 365, prácticamente no hay motivo para seguir manteniendo autenticación propia.
- ¿Se puede usar el método de recibir el nombre de usuario y la contraseña en una pantalla propia y autenticarlos por detrás (ROPC)?
- Considere que su adopción en nuevos desarrollos está prohibida. El ROPC para clientes públicos está documentado oficialmente como «obsoleto (deprecated) por riesgo de seguridad», y ya existe una guía de migración publicada. Al ser incompatible con MFA y el acceso condicional, los usuarios para los que el inquilino exige MFA quedan bloqueados y no pueden iniciar sesión. La exigencia de MFA puede activarse en cualquier momento mediante la configuración del inquilino, así que aunque hoy funcione, existe el riesgo de que un día todos los usuarios dejen de poder iniciar sesión de repente.
- ¿Conviene usar el bróker WAM?
- En Windows, sí, es lo recomendado. Con una sola línea, WithBroker, se obtiene SSO con la cuenta con la que el usuario ya inició sesión en Windows, compatibilidad con acceso condicional, Windows Hello y llaves FIDO, y el enlace del token de actualización al dispositivo. Si el PC corporativo está unido a Entra, tras iniciar la aplicación basta con elegir la cuenta de Windows para completar el inicio de sesión sin escribir ninguna contraseña. Sin embargo, es exclusivo de Entra ID y tiene la limitación de que, por diseño, produce un error fuera de una sesión de usuario interactiva, como en un servicio de Windows o el Programador de tareas.
- ¿Por qué aparece la pantalla de inicio de sesión cada vez que se reinicia la aplicación?
- Porque la caché de tokens no se está persistiendo. La caché de tokens de MSAL.NET reside en memoria de forma predeterminada, y en una aplicación de escritorio implementar su persistencia es responsabilidad de la propia aplicación. La recomendación oficial es el paquete Microsoft.Identity.Client.Extensions.Msal, con el que en Windows la caché se guarda cifrada. Incluso si se usa WAM, la persistencia de la caché sigue siendo necesaria para guardar el token de ID y los metadatos de la cuenta.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.