Per accedere con Microsoft Entra ID (ex Azure Active Directory) in un'app Capacitor, registra un'app client pubblica nell'interfaccia di amministrazione di Entra e aggiungi un Applicazioni mobile e desktop URI di redirect come com.example.app://oauth/azure, ed esegui il flusso di autorizzazione OpenID Connect code con PKCE tramite il browser di sistema. Con @capgo/capacitor-social-login il che significa una initialize chiamata con gli endpoint della Microsoft identity platform v2.0 e SocialLogin.login({ provider: 'oauth2', options: { providerId: 'azure' } }).
Questa guida copre la registrazione dell'app, la scelta dell'authority corretta, gli scope Graph e custom API, la validazione del token, il Conditional Access, il logout e gli errori AADSTS che incontrerai. Si riferisce a Capacitor 8 di ottobre 2026.
Cosa ti serve
- Un tenant Microsoft Entra e un ruolo che possa creare registrazioni di app (Application Developer o superiore).
- Un'app Capacitor 8 e il suo
appId, che riutilizzerai come schema di redirect. - Opzionalmente, un backend API che debba accettare token Entra.
Perché non usare MSAL.js nella WebView
MSAL.js è progettato per browser che controllano la propria origin e possono aprire popup. In una WebView Capacitor l'origin è capacitor://localhost su iOS e https://localhost su Android i popup vengono bloccati, e un redirect a pagina intera porterebbe l'app fuori dal suo bundle. Le linee guida Microsoft per le app native indicano il browser di sistema con PKCE (RFC 8252), ed è quello che fa il plugin:
| Piattaforma | Browser utilizzato | Extra setup |
|---|---|---|
| iOS | ASWebAuthenticationSession |
Nessuna |
| Android | Chrome Custom Tabs con androidUseCustomTabs: true |
Intent filter |
| Web | Popup o redirect | URI di redirect SPA |
Su Android, attiva le Custom Tabs. La WebView integrata di default non riesce a comunicare con Microsoft Authenticator, passkey o Company Portal, su cui spesso si basa Conditional Access.
Passaggio 1: Registra l'app in Entra ID
- Apri la Centro amministratore Microsoft Entra e vai su Entra ID > Registrazioni app > Nuova registrazione.
- Inserisci un nome, ad esempio “Example Mobile”.
- Scegli Tipi di account supportati. Questo determina l'authority che userai in seguito.
- Salta l'URI di redirect per ora e clicca Registrati.
- Su Panoramica, copia il ID applicazione (client) e ID directory (tenant).
Scegli l'authority
| Tipo di account alla registrazione | Segmento authority negli URL |
|---|---|
| Single tenant (solo la tua organizzazione) | Il tuo tenant ID, ad esempio 8f1c... |
| Qualsiasi directory organizzativa | organizations |
| Qualsiasi directory organizzativa più account Microsoft personali | common |
| Solo account Microsoft personali | consumers |
Usa l'ID del tenant ogni volta che puoi. Produce issuer prevedibili ed evita accessi accidentali da altri tenant.
Aggiungi gli URI di redirect
Vai su Authentication > Add a platform:
- Applicazioni mobile e desktop: aggiungi
com.example.app://oauth/azurecome redirect URI personalizzato. Aggiungicom.example.app://oauth/logoutanche se vuoi un redirect post-logout. - Applicazione a pagina singola (solo se distribuisci una web build): aggiungi
https://app.example.com/auth/callback.
Non registrare l'URI mobile sotto Web. I redirect della piattaforma Web sono client riservati e richiedono un secret all'endpoint del token.
permessi API
permessi API contiene già Microsoft Graph > User.Read. Aggiungi offline_access, openid, profile e email se non sono elencati. Alcuni tenant bloccano il consenso dell'utente. In tal caso, un admin deve cliccare su Concedi consenso admin, altrimenti gli utenti vedono l'errore AADSTS65001 o la schermata “Necessaria approvazione admin”.
Esponi uno scope per il tuo API (opzionale)
Se il tuo backend deve accettare il token:
- Apri Esponi un API e imposta l'Application ID URI, solitamente
api://<client-id>. - Aggiungi uno scope chiamato
access_as_user, chi può dare il consenso: amministratori e utenti. - In Manifest, imposta
"requestedAccessTokenVersion": 2così gli access token usano il formato issuer v2.0.
Passaggio 2: installa il plugin e aggiungi l'intent filter
bun add @capgo/capacitor-social-login
bunx cap sync
In android/app/src/main/AndroidManifest.xml, dentro 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>
Mantieni android:launchMode="singleTask" su MainActivity (il Capacitor predefinito). Se non usi il login con Google, Apple o Facebook, disattivali sotto plugins.SocialLogin.providers nella capacitor.config.ts per escludere i relativi SDK dal build.
Passaggio 3: Inizializza il provider 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 che punta a Graph /me, il plugin chiama Graph subito dopo il login e restituisce il profilo in resourceData.
Se stai migrando da Ionic Auth Connect, SocialLoginAuthConnect ha un azure preset che richiede solo tenantId, clientId e redirectUrl. Vedi la Pagina di integrazione Entra ID.
Passaggio 4: Effettua il login
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;
}
}
Opzioni utili:
loginHint: 'jane@contoso.com'compila automaticamente l'account e salta la selezione account per quell'utente.prompt: 'select_account'mostra sempre la selezione account, utile per i dispositivi condivisi.additionalParameters: { domain_hint: 'contoso.com' }inviata gli utenti direttamente al loro IdP federato.
mail è spesso null per account senza casella di posta. Usa come fallback userPrincipalName o nel preferred_username claim del token ID.
Passaggio 5: Chiama 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();
}
Aggiungi ogni scope Graph di cui hai bisogno (ad esempio Calendars.Read) sia in permessi API che nella scope stringa.
Passaggio 6: Ottieni un token per il tuo API
Un access token è destinato a una singola risorsa. Per chiamare il tuo backend, aggiungi una seconda voce accanto a azure nella stessa oauth2 map del tuo initialize call, richiedendo lo scope API al posto di 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,
},
},
});
L'utente ha già una sessione Microsoft, quindi il secondo login di solito si completa senza prompt. Convalida il token sul server:
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
}
Per le app multitenant, l'issuer contiene l'ID del tenant di ogni cliente. Convalida la tid claim contro l'elenco dei tenant accettati, anziché un issuer fisso. Usa gli utenti come chiave su oid più tid, non sull'email.
Passaggio 7: Aggiorna e disconnettiti
// 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' });
I refresh token di Entra per i client pubblici durano fino a 90 giorni con finestra scorrevole e vengono sostituiti ad ogni utilizzo. Il plugin salva quello nuovo. Se memorizzi i token tu, usa Keychain o Keystore, ad esempio NativeBiometric.setData da @capgo/capacitor-native-biometric, e passali a SocialLogin.refreshToken.
logout cancella i token locali e apre l'endpoint di logout v2.0 nel browser di sistema. A volte Microsoft mostra una pagina "Hai effettuato l'accesso" invece di reindirizzare a uno schema personalizzato, quindi l'utente potrebbe doverla chiudere. Se ti serve solo un logout locale, rimuovi logoutUrl e passa prompt: 'select_account' al prossimo login.
Conditional Access e Intune
L'OIDC basato su browser supporta MFA, passwordless e la maggior parte delle policy di Conditional Access. Due casi richiedono accorgimenti extra:
- Richiedi un dispositivo compliant o hybrid-joined: Entra deve vedere lo stato del dispositivo. Su Android funziona tramite Custom Tabs quando Company Portal o Authenticator sono installati. Sui dispositivi iOS gestiti lo fornisce il plug-in Microsoft Enterprise SSO distribuito dal tuo MDM. Testa subito questa configurazione con una policy reale.
- Policy di protezione app Intune (MAM): queste richiedono l'app Intune SDK nell'app nativa. L'OIDC da solo non soddisfa i grant "Require app protection policy".
Risoluzione problemi
| Errore | Significato | Soluzione |
|---|---|---|
| AADSTS50011 | Mismatch dell'URI di redirect | Aggiungi l'URI esatto sotto Mobile and desktop applications |
| AADSTS7000218 | La richiesta del token richiede un secret | Il redirect è registrato sotto Web. Spostalo in Mobile and desktop |
| AADSTS9002326 | Lo scambio del token cross-origin è consentito solo per le SPA | La build web usa un redirect mobile. Registra l'URL web sotto Single-page application |
| AADSTS700016 | App non trovata nella directory | Tenant errato nell'authority, o app single-tenant usata con common |
| AADSTS50020 | Account utente da un altro tenant | Usa una registrazione multitenant o invita l'utente come guest |
| AADSTS65001 | Consenso non concesso | Concedi il consenso admin o consenti il consenso utente |
| AADSTS53003 | Bloccato da Conditional Access | Controlla i log di accesso, vedi la sezione sopra |
| AADSTS28000 | Scopes per più di una risorsa in una singola richiesta | Richiedi gli scopes di Graph e di API in login separate |
| Nessun refresh token | offline_access mancante |
Aggiungilo agli scope e ai permessi |
L'Entra admin center Log di accesso mostrano il motivo esatto del fallimento per ogni tentativo, e logsEnabled: true nella config del plugin stampa l'URL di autorizzazione e la risposta del token sul dispositivo.
Rilascio degli aggiornamenti
Le modifiche alle policy su Entra sono istantanee, ma i fix lato client devono comunque raggiungere i dispositivi. Modifiche TypeScript come scope o gestione errori possono essere distribuite tramite Live updates di Capgo. Le modifiche native (intent filter, nuovi scheme) richiedono una nuova build per gli store, che Build Capgo può essere generato senza un Mac locale.