Para iniciar sesión con Microsoft Entra ID (anteriormente Azure Active Directory) en una app de Capacitor, registra una app de cliente público en el centro de administración de Entra y añade una Aplicaciones móviles y de escritorio URI de redirección como com.example.app://oauth/azure, y ejecuta el flujo de autorización OpenID Connect code con PKCE a través del navegador del sistema. Con @capgo/capacitor-social-login eso significa una sola initialize llamada a los endpoints de la plataforma de identidad de Microsoft v2.0 y SocialLogin.login({ provider: 'oauth2', options: { providerId: 'azure' } }).
Esta guía cubre el registro de la app, la elección de la autoridad correcta, los scopes de Graph y los personalizados de API, la validación de tokens, el Acceso condicional, el cierre de sesión y los errores AADSTS que verás. Está orientada a Capacitor 8 en octubre de 2026.
Qué necesitas
- Un tenant de Microsoft Entra y un rol que pueda crear registros de aplicaciones (Application Developer o superior).
- Una app Capacitor 8 y su
appId, que reutilizarás como esquema de redirección. - Opcionalmente, un backend API que deba aceptar tokens de Entra.
Por qué no usar MSAL.js dentro del WebView
MSAL.js está diseñado para navegadores que controlan su propio origen y pueden abrir popups. En un WebView de Capacitor, el origen es capacitor://localhost en iOS y https://localhost en Android, los popups están bloqueados y un redirect de página completa sacaría a tu app de su propio bundle. La guía de Microsoft para apps nativas recomienda el navegador del sistema con PKCE (RFC 8252), que es lo que hace el plugin:
| Plataforma | Navegador utilizado | Configuración extra |
|---|---|---|
| iOS | ASWebAuthenticationSession |
Ninguno |
| Android | Chrome Custom Tabs con androidUseCustomTabs: true |
Filtro de intents |
| Web | Popup o redirect | URI de redirección de SPA |
En Android, activa Custom Tabs. El WebView integrado por defecto no puede comunicarse con Microsoft Authenticator, passkeys ni Company Portal, en los que el Conditional Access suele depender.
Paso 1: Registrar la app en Entra ID
- Abre la Centro de administración de Microsoft Entra y ve a Entra ID > Registros de aplicaciones > Nuevo registro.
- Introduce un nombre, como «Example Mobile».
- Elige Tipos de cuenta admitidos. Esto determina la autoridad que usarás más adelante.
- Omite la URI de redirección por ahora y haz clic en Registrarse.
- En Resumen, copia el ID de la aplicación (cliente) y ID del directorio (tenant).
Elegir la autoridad
| Tipo de cuenta al registrarse | Segmento de autoridad en las URLs |
|---|---|
| Inquilino único (solo tu organización) | Tu ID de inquilino, por ejemplo 8f1c... |
| Cualquier directorio de la organización | organizations |
| Cualquier directorio de la organización y cuentas personales de Microsoft | common |
| Solo cuentas personales de Microsoft | consumers |
Usa el tenant ID siempre que puedas. Esto genera emisores predecibles y evita inicios de sesión accidentales desde otros tenants.
Añadir URIs de redirección
Ve a Autenticación > Añadir una plataforma:
- Aplicaciones móviles y de escritorio: añadir
com.example.app://oauth/azurecomo una URI de redirección personalizada. Añadecom.example.app://oauth/logouttambién si quieres una redirección tras el logout. - Aplicación de página única (solo si publicas un build web): añade
https://app.example.com/auth/callback.
No registres la URI móvil bajo Web. Las redirecciones de la plataforma Web son clientes confidenciales y requieren un secreto en el endpoint de token.
permisos de API
permisos de API already contains Microsoft Graph > User.Read. Añade offline_access, openid, profile y email si no aparecen. Algunos inquilinos bloquean el consentimiento del usuario. En ese caso, un administrador debe hacer clic Conceder consentimiento de administrador, otherwise users see AADSTS65001 or a “Need admin approval” screen.
Exponer un scope para tu propio API (opcional)
Si tu backend debe aceptar el token:
- Abrir Exponer un API y establecer la Application ID URI, normalmente
api://<client-id>. - Agregar un scope llamado
access_as_user, who can consent: Admins and users. - En Manifiesto, establece
"requestedAccessTokenVersion": 2los access tokens utilizan el formato de emisor v2.0.
Paso 2: Instala el plugin y añade el intent filter
bun add @capgo/capacitor-social-login
bunx cap sync
En android/app/src/main/AndroidManifest.xml, dentro de MainActivity:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.example.app" android:host="oauth" />
</intent-filter>
Mantén android:launchMode="singleTask" en MainActivity (el valor predeterminado de Capacitor). Si no usas inicio de sesión con Google, Apple o Facebook, desactívalos en plugins.SocialLogin.providers en capacitor.config.ts para mantener sus SDKs fuera del build.
Paso 3: Inicializar el proveedor de Entra ID
import { Capacitor } from '@capacitor/core';
import { SocialLogin } from '@capgo/capacitor-social-login';
const TENANT = 'YOUR_TENANT_ID'; // or organizations / common
const AUTHORITY = `https://login.microsoftonline.com/${TENANT}/oauth2/v2.0`;
const isWeb = Capacitor.getPlatform() === 'web';
export async function initAuth() {
await SocialLogin.initialize({
oauth2: {
azure: {
appId: 'YOUR_CLIENT_ID',
authorizationBaseUrl: `${AUTHORITY}/authorize`,
accessTokenEndpoint: `${AUTHORITY}/token`,
redirectUrl: isWeb
? 'https://app.example.com/auth/callback'
: 'com.example.app://oauth/azure',
scope: 'openid profile email offline_access User.Read',
pkceEnabled: true,
resourceUrl: 'https://graph.microsoft.com/v1.0/me',
logoutUrl: `${AUTHORITY}/logout`,
postLogoutRedirectUrl: isWeb ? 'https://app.example.com/' : 'com.example.app://oauth/logout',
androidUseCustomTabs: true,
},
},
});
}
Con resourceUrl apuntando a Graph /me, el plugin llama a Graph justo después del inicio de sesión y devuelve el perfil en resourceData.
Si estás migrando desde Ionic Auth Connect, SocialLoginAuthConnect tiene un azure preset que solo necesita tenantId, clientId y redirectUrl. Consulta la página de integración de Entra ID.
Paso 4: Iniciar sesión
import { SocialLogin } from '@capgo/capacitor-social-login';
export async function signInWithMicrosoft() {
try {
const { result } = await SocialLogin.login({
provider: 'oauth2',
options: { providerId: 'azure' },
});
const me = result.resourceData as {
id: string;
displayName?: string;
mail?: string | null;
userPrincipalName?: string;
} | null;
return {
idToken: result.idToken,
graphToken: result.accessToken?.token,
refreshToken: result.refreshToken,
user: me,
};
} catch (error: any) {
if (error?.code === 'USER_CANCELLED') return null;
throw error;
}
}
Opciones útiles:
loginHint: 'jane@contoso.com'precarga la cuenta y omite el selector de cuentas para ese usuario.prompt: 'select_account'muestra siempre el selector de cuentas, útil para dispositivos compartidos.additionalParameters: { domain_hint: 'contoso.com' }envía a los usuarios directamente a su IdP federado.
mail es a menudo null para cuentas sin buzón. Usa como alternativa userPrincipalName o el preferred_username claim del ID token.
Paso 5: Llamar a Microsoft Graph
export async function getMyPhotoBlob(graphToken: string) {
const res = await fetch('https://graph.microsoft.com/v1.0/me/photo/$value', {
headers: { Authorization: `Bearer ${graphToken}` },
});
if (res.status === 404) return null; // no photo set
if (!res.ok) throw new Error(`Graph error ${res.status}`);
return res.blob();
}
Añade cada scope de Graph que necesites (por ejemplo Calendars.Read) tanto en permisos de API como en el scope string.
Paso 6: Obtener un token para tu propio API
Un access token apunta a un recurso. Para llamar a tu backend, añade una segunda entrada junto a azure en el mismo oauth2 mapa de tu initialize llamada, solicitando tu ámbito API en lugar de Graph:
await SocialLogin.initialize({
oauth2: {
azure: graphConfig, // the object from step 3, moved into a const
azureApi: {
appId: 'YOUR_CLIENT_ID',
authorizationBaseUrl: `${AUTHORITY}/authorize`,
accessTokenEndpoint: `${AUTHORITY}/token`,
redirectUrl: 'com.example.app://oauth/azure-api', // register this URI too
scope: 'openid profile offline_access api://YOUR_CLIENT_ID/access_as_user',
pkceEnabled: true,
androidUseCustomTabs: true,
},
},
});
El usuario ya tiene una sesión de Microsoft, por lo que el segundo inicio de sesión suele completarse sin un prompt. Valida el token en el servidor:
import { createRemoteJWKSet, jwtVerify } from 'jose';
const TENANT_ID = 'YOUR_TENANT_ID';
const jwks = createRemoteJWKSet(
new URL(`https://login.microsoftonline.com/${TENANT_ID}/discovery/v2.0/keys`),
);
export async function verifyEntraToken(token: string) {
const { payload } = await jwtVerify(token, jwks, {
issuer: `https://login.microsoftonline.com/${TENANT_ID}/v2.0`,
audience: 'YOUR_CLIENT_ID', // v2 tokens use the client ID as aud
});
if (!String(payload.scp ?? '').split(' ').includes('access_as_user')) {
throw new Error('Missing scope');
}
return payload; // payload.oid is the stable user object ID
}
Para apps multitenant, el issuer contiene el tenant ID de cada cliente. Valida el tid claim contra la lista de tenants que aceptas, en lugar de un issuer fijo. Clave a los usuarios por oid además de tid, no por email.
Paso 7: Refresh y cierre de sesión
// Get a valid Graph token, refreshing it when it has expired
export async function getGraphToken() {
const { isLoggedIn } = await SocialLogin.isLoggedIn({ provider: 'oauth2', providerId: 'azure' });
if (!isLoggedIn) {
try {
await SocialLogin.refresh({ provider: 'oauth2', options: { providerId: 'azure' } });
} catch {
// no session, or the refresh token expired or was revoked
const loginResult = await signInWithMicrosoft();
if (!loginResult) throw new Error('Sign-in cancelled');
}
}
const { accessToken } = await SocialLogin.getAuthorizationCode({ provider: 'oauth2', providerId: 'azure' });
return accessToken;
}
// Sign out
await SocialLogin.logout({ provider: 'oauth2', providerId: 'azure' });
Los tokens de refresco de Entra para clientes públicos duran hasta 90 días con una ventana deslizante y se reemplazan en cada uso. El plugin almacena el nuevo. Si almacenas los tokens tú mismo, usa Keychain o Keystore, por ejemplo NativeBiometric.setData de los @capgo/capacitor-native-biometric, y pásalos a SocialLogin.refreshToken.
logout borra los tokens locales y abre el endpoint de cierre de sesión v2.0 en el navegador del sistema. A veces Microsoft muestra una página de «Has cerrado sesión en tu cuenta» en lugar de redirigir a un esquema personalizado, por lo que el usuario puede tener que cerrarla. Si solo necesitas un cierre de sesión local, elimina logoutUrl y pasar prompt: 'select_account' en el próximo inicio de sesión.
Acceso condicional e Intune
El OIDC basado en navegador funciona con MFA, passwordless y la mayoría de las políticas de Conditional Access. Dos casos requieren más:
- Requerir un dispositivo compatible o con unión híbrida: Entra debe ver el estado del dispositivo. En Android, esto funciona mediante Custom Tabs si Company Portal o Authenticator están instalados. En dispositivos iOS gestionados, lo proporciona el plug-in Microsoft Enterprise SSO que despliega tu MDM. Pruébalo cuanto antes con una política real.
- Intune app protection policies (MAM): esto requiere la SDK de Intune App dentro de la app nativa. OIDC por sí solo no cumple con los permisos de "Require app protection policy".
Solución de problemas
| Error | Significado | Solución |
|---|---|---|
| AADSTS50011 | Coincidencia incorrecta de URI de redirección | Añade la URI exacta en Aplicaciones móviles y de escritorio |
| AADSTS7000218 | La solicitud de token requiere un secreto | La redirección estaba registrada en Web. Muévela a Móvil y escritorio |
| AADSTS9002326 | Redención de tokens cross-origin solo permitida para SPA | El build web usa una redirección móvil. Registra la URL web bajo Single-page application |
| AADSTS700016 | App no encontrada en el directorio | Tenant incorrecto en la authority, o app de un solo tenant usada con common |
| AADSTS50020 | Cuenta de usuario de otro tenant | Usa un registro multitenant, o invita al usuario como invitado |
| AADSTS65001 | Consentimiento no otorgado | Grant admin consent or allow user consent |
| AADSTS53003 | Bloqueado por Acceso Condicional | Revisa los logs de inicio de sesión, ver la sección anterior |
| AADSTS28000 | Scopes para más de un recurso en una sola solicitud | Solicita Graph y tus scopes de API en inicios de sesión separados |
| Sin refresh token | offline_access falta |
Añádelo a scopes y permisos |
El centro de administración de Entra Logs de inicio de sesión muestran la razón exacta del fallo en cada intento, y logsEnabled: true en la configuración del plugin imprime la URL de autorización y la respuesta del token en el dispositivo.
Publicar actualizaciones
Los cambios de política en el lado de Entra son instantáneos, pero las correcciones en el cliente aún deben llegar a los dispositivos. Los cambios en TypeScript, como scopes o manejo de errores, se pueden publicar a través de actualizaciones Live Update de Capgo. Los cambios nativos (intent filters, nuevos schemes) requieren un nuevo build de la tienda, que Build de Capgo puede generar sin una Mac local.