Salta al contenuto principale

Accedi con Microsoft Entra ID (Azure AD) in Capacitor

Aggiungi il login Microsoft Entra ID (Azure AD) a un'app Capacitor 8 con PKCE: app registration, redirect URI, scope Graph e API, e fix degli errori AADSTS.

Pagina di accesso Microsoft Entra ID avviata da un'app Capacitor

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

  1. Apri la Centro amministratore Microsoft Entra e vai su Entra ID > Registrazioni app > Nuova registrazione.
  2. Inserisci un nome, ad esempio “Example Mobile”.
  3. Scegli Tipi di account supportati. Questo determina l'authority che userai in seguito.
  4. Salta l'URI di redirect per ora e clicca Registrati.
  5. 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/azure come redirect URI personalizzato. Aggiungi com.example.app://oauth/logout anche 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:

  1. Apri Esponi un API e imposta l'Application ID URI, solitamente api://<client-id>.
  2. Aggiungi uno scope chiamato access_as_user, chi può dare il consenso: amministratori e utenti.
  3. In Manifest, imposta "requestedAccessTokenVersion": 2 così 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.

Live updates per le app Capacitor

Quando un bug nel web-layer è live, rilascia la fix tramite Capgo invece di aspettare giorni l'approvazione degli store. Gli utenti ricevono l'update in background, mentre le modifiche native restano nel normale processo di review.

Ultimi articoli dal blog

Capgo ti offre gli insight migliori che ti servono per creare un'app mobile davvero professionale.