Per accedere con Microsoft Entra ID (precedentemente Azure Active Directory) in un'app Capacitor, registra un'app public client nell'admin center di Entra, 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 questo si intende 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. È aggiornata a Capacitor 8 di ottobre 2026.
Cosa ti serve
- Un tenant Microsoft Entra e un ruolo con permessi di registrazione app (Application Developer o superiore).
- Un'app Capacitor 8 e il suo
appId, che riutilizzerai come redirect scheme. - Opzionalmente, un backend API che accetti token Entra.
Perché non usare MSAL.js nella WebView
MSAL.js è progettato per browser che gestiscono la propria origine e possono aprire popup. In una Capacitor WebView l'origine è capacitor://localhost su iOS e https://localhost su Android i popup vengono bloccati e un redirect a tutto schermo porterebbe l'app fuori dal proprio bundle. La guida di Microsoft per le app native consiglia il browser di sistema con PKCE (RFC 8252), che è esattamente ciò 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 reindirizzamento SPA |
Su Android, attiva le Custom Tabs. La WebView integrata di default non può comunicare con Microsoft Authenticator, passkeys o Company Portal, strumenti su cui spesso si basa l'Accesso Condizionale.
Passaggio 1: Registra l'app in Entra ID
- Apri il Centro amministratore Microsoft Entra e vai su Entra ID > App registrations > New registration.
- Inserisci un nome, ad esempio “Example Mobile”.
- Seleziona Tipi di account supportati. Questo determinerà l'authority da usare in seguito.
- Salta l'URI di reindirizzamento per ora e clicca Registrati.
- Su Panoramica, copia l' ID applicazione (client) e ID directory (tenant).
Scegli l'authority
| Tipo di account alla registrazione | Segmento authority negli URL |
|---|---|
| Single tenant (solo la tua org) | Il tuo tenant ID, ad esempio 8f1c... |
| Qualsiasi directory aziendale | organizations |
| Directory aziendali e account Microsoft personali | common |
| Solo account Microsoft personali | consumers |
Usa il tenant ID quando possibile. Garantisce issuer prevedibili ed evita accessi accidentali da altri tenant.
Aggiungi URI di redirect
Vai su Authentication > Add a platform:
- Applicazioni mobile e desktop: aggiungi
com.example.app://oauth/azurecome URI di redirect personalizzato. Aggiungicom.example.app://oauth/logoutanche per il redirect post-logout. - Single-page application (solo se pubblichi 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 amministratore, otherwise users see AADSTS65001 or a “Need admin approval” screen.
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, che può dare il consenso: Admin 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, all'interno di 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 default di Capacitor). Se non usi il login con Google, Apple o Facebook, disabilitali in plugins.SocialLogin.providers in capacitor.config.ts per evitare di includere i relativi SDK nel 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: Accedi
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'precompila l'account e salta il selettore dell'account per quell'utente.prompt: 'select_account'mostra sempre il selettore dell'account, utile per dispositivi condivisi.additionalParameters: { domain_hint: 'contoso.com' }invia gli utenti direttamente al proprio IdP federato.
mail è spesso null per account senza casella email. Ricorri a userPrincipalName o il preferred_username claim dell'ID token.
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 di Graph necessario (ad esempio Calendars.Read) sia in permessi API che nel scope string.
Passaggio 6: Ottieni un token per il tuo API
Un access token punta a una singola risorsa. Per chiamare il tuo backend, aggiungi una seconda voce accanto a azure nella stessa oauth2 mappa del tuo initialize chiama, richiedendo lo scope API invece 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 attiva, quindi il secondo login solitamente avviene senza un prompt. Valida 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 il tenant ID di ogni cliente. Valida la tid claim rispetto alla lista dei tenant accettati invece di un issuer fisso. Gli utenti chiave su oid più tid, non sull'email.
Passaggio 7: Refresh e logout
// 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 una sliding window e vengono sostituiti a ogni utilizzo. Il plugin salva il nuovo token. Se gestisci i token manualmente, 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. Microsoft a volte mostra una pagina "Ti sei disconnesso dal tuo account" invece di reindirizzare a uno scheme custom, quindi l'utente potrebbe doverla chiudere. Se ti serve solo un logout locale, usa 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 dispositivo compliant o hybrid-joined: Entra deve rilevare lo stato del dispositivo. Su Android funziona tramite Custom Tabs se è installato Company Portal o Authenticator. Sui dispositivi iOS gestiti, è fornito dal 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 SDK di Intune all'interno dell'app nativa. L'OIDC da solo non soddisfa i permessi "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 | Il token redemption cross-origin è consentito solo per 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 in uso common |
| AADSTS50020 | Account utente di un altro tenant | Usa una registrazione multitenant o invita l'utente come guest |
| AADSTS65001 | Consenso non concesso | Grant admin consent or allow user consent |
| AADSTS53003 | Bloccato da Conditional Access | Controlla i log di accesso, vedi la sezione sopra |
| AADSTS28000 | Scope per più di una risorsa in una singola richiesta | Richiedi gli scope di Graph e del tuo API in login separati |
| Nessun refresh token | offline_access mancante |
Aggiungilo a scope e permessi |
Il centro admin di Entra Log di accesso mostra 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.
Rilasciare gli aggiornamenti
Le modifiche alle policy su Entra sono istantanee, ma i fix lato client devono comunque raggiungere i dispositivi. Le modifiche TypeScript come scope o gestione errori possono essere inviate tramite live update di Capgo. Le modifiche native (intent filter, nuovi scheme) richiedono un nuovo build per lo store, che Build di Capgo è possibile produrre senza un Mac locale.