Zum Hauptinhalt springen

Anmeldung mit Microsoft Entra ID (Azure AD) in Capacitor

Microsoft Entra ID (Azure AD) Login zu einer Capacitor 8 App mit PKCE hinzufügen: App-Registrierung, Redirect URIs, Graph- und API Scopes sowie Fixes für AADSTS-Fehler.

Microsoft Entra ID-Anmeldeseite, aufgerufen aus einer Capacitor-App

Um sich mit Microsoft Entra ID (ehemals Azure Active Directory) in einer Capacitor App anzumelden, registrieren Sie eine Public Client App im Entra Admin Center und fügen Sie eine Mobile und Desktop-Anwendungen redirect URI wie z. B. com.example.app://oauth/azure, und führe den OpenID Connect Authorization code Flow mit PKCE über den Systembrowser aus. Mit @capgo/capacitor-social-login das bedeutet eine initialize Aufruf mit den Microsoft identity platform v2.0-Endpunkten und SocialLogin.login({ provider: 'oauth2', options: { providerId: 'azure' } }).

Dieser Guide behandelt die App-Registrierung, die Wahl der richtigen Authority, Graph- und benutzerdefinierte API-Scopes, Token-Validierung, Conditional Access, Logout und die AADSTS-Fehler, die Sie sehen werden. Er richtet sich an Capacitor 8 im Oktober 2026.

Voraussetzungen

  • Ein Microsoft Entra-Tenant und eine Rolle, die App-Registrierungen erstellen kann (Application Developer oder höher).
  • Eine Capacitor 8-App und deren appId, die Sie als Redirect-Schema wiederverwenden werden.
  • Optional ein Backend-API, das Entra-Tokens akzeptieren soll.

Warum nicht MSAL.js in der WebView?

MSAL.js ist für Browser konzipiert, die ihren eigenen Origin besitzen und Popups öffnen können. In einer Capacitor-WebView ist der Origin capacitor://localhost auf iOS und https://localhost auf Android sind Popups blockiert, und ein Full-Page-Redirect würde Ihre App von ihrem eigenen Bundle weg navigieren. Die Empfehlung von Microsoft für Native Apps ist der Systembrowser mit PKCE (RFC 8252), was genau das tut, was das Plugin macht:

Plattform Verwendeter Browser Extra setup
iOS ASWebAuthenticationSession Keine
Android Chrome Custom Tabs mit androidUseCustomTabs: true Intent-Filter
Web Popup oder Redirect SPA-Redirect-URI

Aktiviere auf Android Custom Tabs. Die standardmäßige eingebettete WebView kann nicht mit Microsoft Authenticator, Passkeys oder dem Company Portal kommunizieren, auf die Conditional Access oft angewiesen ist.

Schritt 1: App in Entra ID registrieren

  1. Öffne die Microsoft Entra Admin Center und gehen Sie zu Entra ID > App-Registrierungen > Neue Registrierung.
  2. Gib einen Namen ein, z. B. „Example Mobile“.
  3. Wähle Unterstützte Kontotypen. Damit wird die Authority festgelegt, die du später verwendest.
  4. Überspringe die Redirect-URI vorerst und klicke auf Registrieren.
  5. An Übersicht, kopiere die Anwendungs-ID (Client) und Verzeichnis-ID (Mandant).

Wähle die Authority

Kontotyp bei der Registrierung Authority-Segment in URLs
Single-Tenant (nur deine Org) Deine Tenant-ID, zum Beispiel 8f1c...
Beliebiges Organisationsverzeichnis organizations
Beliebiges Organisationsverzeichnis plus persönliche Microsoft-Konten common
Nur persönliche Microsoft-Konten consumers

Nutze die Tenant-ID, wann immer möglich. Das liefert vorhersehbare Issuer und vermeidet versehentliche Anmeldungen aus anderen Tenants.

Redirect-URIs hinzufügen

Gehe zu Authentifizierung > Plattform hinzufügen:

  • Mobile und Desktop-Anwendungen: hinzufügen com.example.app://oauth/azure als benutzerdefinierte Redirect-URI. Füge com.example.app://oauth/logout auch, wenn du nach dem Logout weiterleiten möchtest.
  • Single-Page-Application (nur bei Web-Builds): hinzufügen https://app.example.com/auth/callback.

Registriere die mobile URI nicht unter WebWeb-Plattform-Redirects sind Confidential Clients und benötigen ein Secret am Token-Endpoint.

API Berechtigungen

API Berechtigungen enthält bereits Microsoft Graph > User.Read. Füge hinzu offline_access, openid, profile und email , falls sie nicht gelistet sind. Manche Tenants blockieren die Benutzereinwilligung. In diesem Fall muss ein Admin Administratorzustimmung erteilen, andernfalls sehen Nutzer AADSTS65001 oder einen „Need admin approval“-Bildschirm.

Scope für dein eigenes API bereitstellen (optional)

Falls dein Backend den Token akzeptieren soll:

  1. Öffne Einen API bereitstellen und die Application ID URI festlegen, üblicherweise api://<client-id>.
  2. Einen Scope mit dem Namen hinzufügen access_as_user, wer zustimmen kann: Admins und Benutzer.
  3. Unter Manifest, setzen "requestedAccessTokenVersion": 2 damit Access Tokens das v2.0-Issuer-Format nutzen.

Schritt 2: Plugin installieren und Intent-Filter hinzufügen

bun add @capgo/capacitor-social-login
bunx cap sync

Unter android/app/src/main/AndroidManifest.xml, innerhalb von 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>

Behalten android:launchMode="singleTask" auf MainActivity (den Capacitor-Standard). Falls du Google-, Apple- oder Facebook-Login nicht nutzt, deaktiviere sie unter plugins.SocialLogin.providers in einem capacitor.config.ts um deren SDKs aus dem Build fernzuhalten.

Schritt 3: Den Entra ID-Provider initialisieren

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,
      },
    },
  });
}

Mit resourceUrl der auf Graph zeigt /me, ruft das Plugin direkt nach dem Login Graph auf und gibt das Profil in zurück resourceData.

Falls du von Ionic Auth Connect migrierst, SocialLoginAuthConnect hat ein azure Preset, das nur benötigt tenantId, clientId und redirectUrl. Siehe die Entra-ID-Integrationsseite.

Schritt 4: Anmelden

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;
  }
}

Nützliche Optionen:

  • loginHint: 'jane@contoso.com' füllt das Konto vorab aus und überspringt den Account-Picker für diesen User.
  • prompt: 'select_account' zeigt immer den Account-Picker an, praktisch für gemeinsam genutzte Geräte.
  • additionalParameters: { domain_hint: 'contoso.com' } leitet User direkt zu ihrem föderierten IdP weiter.

mail ist oft null für Konten ohne Postfach. Weiche auf userPrincipalName oder den preferred_username Claim des ID-Tokens.

Schritt 5: Microsoft Graph aufrufen

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();
}

Füge jeden benötigten Graph-Scope hinzu (z. B. Calendars.Read) sowohl in API Berechtigungen als auch im scope String.

Schritt 6: Token für dein eigenes API abrufen

Ein Access Token ist für eine Ressource bestimmt. Um dein Backend aufzurufen, füge einen zweiten Eintrag neben azure in derselben oauth2 Map deines initialize aufrufen und dabei deinen API-Scope statt Graph anfordern:

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,
    },
  },
});

Der Benutzer hat bereits eine Microsoft-Sitzung, daher läuft der zweite Login normalerweise ohne Prompt durch. Validiere das Token auf dem 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
}

Bei Multitenant-Apps enthält der Issuer die Tenant-ID jedes Kunden. Validiere den tid Claim gegen die Liste der Tenants, die du akzeptierst, statt gegen einen festen Issuer. Verwende als Key die oid sowie tid, nicht die E-Mail-Adresse.

Schritt 7: Aktualisieren und abmelden

// 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' });

Entra-Refresh-Tokens für öffentliche Clients gelten bis zu 90 Tage mit gleitendem Fenster und werden bei jeder Nutzung ersetzt. Das Plugin speichert das neue Token. Wenn du Tokens selbst speicherst, nutze z. B. den Keychain oder Keystore NativeBiometric.setData von @capgo/capacitor-native-biometric, und übergib sie an SocialLogin.refreshToken.

logout löscht lokale Tokens und öffnet den v2.0-Logout-Endpunkt im Systembrowser. Microsoft zeigt manchmal eine Seite „Du hast dich von deinem Konto abgemeldet“ an, statt auf einen benutzerdefinierten Scheme umzuleiten, sodass der Benutzer sie möglicherweise schließen muss. Wenn du nur einen lokalen Logout brauchst, lass logoutUrl und übergeben prompt: 'select_account' beim nächsten Login.

Conditional Access und Intune

Browser-basiertes OIDC funktioniert mit MFA, Passwordless und den meisten Conditional Access Policies. Zwei Fälle brauchen mehr:

  • Compliant-Gerät oder hybrid-joined Gerät erforderlich: Entra muss den Gerätestatus sehen. Auf Android klappt das über Custom Tabs, wenn Company Portal oder Authenticator installiert sind. Auf verwalteten iOS-Geräten übernimmt das Microsoft Enterprise SSO Plug-in, das Ihr MDM ausrollt. Testen Sie das früh mit einer echten Policy.
  • Intune App Protection Policies (MAM): diese erfordern das Intune App SDK in der nativen App. OIDC allein erfüllt die Grant-Anforderung „Require app protection policy“ nicht.

Fehlerbehebung

Fehler Bedeutung Lösung
AADSTS50011 Redirect-URI stimmt nicht überein Füge die exakte URI unter „Mobile and desktop applications“ hinzu
AADSTS7000218 Token-Request benötigt ein Secret Redirect wurde unter „Web“ registriert. Verschiebe ihn zu „Mobile and desktop“
AADSTS9002326 Cross-Origin-Token-Redemption nur für SPAs erlaubt Web-Build nutzt einen Mobile-Redirect. Registriere die Web-URL unter „Single-page application“
AADSTS700016 App im Verzeichnis nicht gefunden Falscher Tenant in der Authority oder Single-Tenant-App verwendet common
AADSTS50020 Benutzerkonto aus einem anderen Tenant Nutze eine Multitenant-Registrierung oder lade den User als Gast ein
AADSTS65001 Zustimmung nicht erteilt Admin-Zustimmung erteilen oder User-Zustimmung erlauben
AADSTS53003 Durch Conditional Access blockiert Prüfe die Sign-in-Logs (siehe Abschnitt oben)
AADSTS28000 Scopes für mehrere Ressourcen in einem Request Fordere Graph-Scopes und deine API Scopes in separaten Logins an
Kein Refresh Token offline_access fehlt Zu Scopes und Berechtigungen hinzufügen

Das Entra Admin Center Anmeldeprotokolle zeigen den genauen Fehlergrund für jeden Versuch an, und logsEnabled: true gibt in der Plugin-Konfiguration die Authorize-URL und die Token-Response auf dem Gerät aus.

Updates ausliefern

Richtlinienänderungen auf der Entra-Seite erfolgen sofort, aber clientseitige Fixes müssen trotzdem erst auf den Geräten ankommen. TypeScript-Änderungen wie Scopes oder Error Handling können über Capgo Live Updates. Native Änderungen (Intent-Filter, neue Schemes) erfordern einen neuen Store-Build, der Capgo Build kann ohne einen lokalen Mac erstellt werden.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.