メインコンテンツへスキップ

Capacitor で Microsoft Entra ID (Azure AD) を使ってサインイン

PKCEを使ったCapacitor 8アプリへのMicrosoft Entra ID (Azure AD) ログインの追加:アプリ登録、リダイレクトURI、GraphとAPIスコープ、AADSTSエラーの修正。

Capacitor アプリから起動される Microsoft Entra ID サインインページ

CapacitorアプリでMicrosoft Entra ID (旧Azure Active Directory) を使ってサインインするには、Entra管理センターでパブリッククライアントアプリを登録し、追加します モバイルアプリとデスクトップアプリ redirect URI として、 com.example.app://oauth/azure、システムブラウザ経由で PKCE を使用した OpenID Connect 認可 code フローを実行します。 @capgo/capacitor-social-login つまり1つ initialize Microsoft identity platform v2.0 エンドポイントへの呼び出しと SocialLogin.login({ provider: 'oauth2', options: { providerId: 'azure' } }).

本ガイドでは、アプリ登録、適切な authority の選択、Graph およびカスタム API スコープ、トークン検証、Conditional Access、ログアウト、および発生しうる AADSTS エラーについて説明します。対象は 2026年10月の Capacitor 8 です。

必要なもの

  • Microsoft Entra テナントと、アプリ登録を作成できるロール(Application Developer 以上)。
  • Capacitor 8 アプリとその appId、これをリダイレクトスキームとして再利用します。
  • 必要に応じて、Entra トークンを受け取るバックエンド API。

WebView内でMSAL.jsを使わない理由

MSAL.js は、オリジンを所有しポップアップを開けるブラウザ向けに設計されています。Capacitor WebView ではオリジンは capacitor://localhost (iOS)と https://localhost Android ではポップアップがブロックされ、フルページリダイレクトはアプリを自身の bundle から離れてしまいます。ネイティブアプリ向けの Microsoft のガイダンスは、PKCE(RFC 8252)付きシステムブラウザであり、プラグインもこれを実装しています:

プラットフォーム 使用ブラウザ 追加セットアップ
iOSと ASWebAuthenticationSession なし
Androidでは Chrome Custom Tabs と androidUseCustomTabs: true Intent フィルター
Web ポップアップまたはリダイレクト SPA リダイレクト URI

Android では Custom Tabs をオンにしてください。デフォルトの埋め込み WebView は、Conditional Access がしばしば依存する Microsoft Authenticator、パスキー、Company Portal と連携できません。

ステップ1: Entra IDにアプリを登録する

  1. 次を開きます: Microsoft Entra 管理センター に移動し Entra ID > アプリの登録 > 新規登録.
  2. 「Example Mobile」などの名前を入力します。
  3. 選ぶ サポートされているアカウントの種類。これにより、後で使用される authority が決まります。
  4. リダイレクト URI は一旦スキップし、 新規登録.
  5. オンラインに 概要、コピー アプリケーション (クライアント) ID と ディレクトリ (テナント) ID.

authorityを選択

登録時のアカウントタイプ URL内のAuthorityセグメント
シングルテナント (自社組織のみ) テナントID、例 8f1c...
任意の組織ディレクトリ organizations
任意の組織ディレクトリおよび個人用Microsoftアカウント common
個人用 Microsoft アカウントのみ consumers

可能な限りテナント ID を使用してください。発行元が予測可能になり、他のテナントからの誤ったサインインを防げます。

リダイレクトURIを追加する

以下に移動します: Authentication > Add a platform:

  • モバイルアプリとデスクトップアプリ:追加 com.example.app://oauth/azure をカスタムリダイレクト URI として追加します。 com.example.app://oauth/logout ログアウト後のリダイレクトが必要な場合は、こちらにも追加してください。
  • シングルページアプリケーション (Webビルドを配信する場合のみ):追加 https://app.example.com/auth/callback.

モバイルURIは以下に登録しないでください: Web。Webプラットフォームのリダイレクトは Confidential Client であり、トークンエンドポイントでシークレットが必要です。

API の権限

API の権限 は既に含まれています Microsoft Graph > User.Read. 追加 offline_access, openid, profile と email 一覧にない場合。一部のテナントではユーザーの同意がブロックされます。その場合、管理者は 「管理者の同意を与える」をクリックする必要があります。そうしないと、ユーザーには AADSTS65001 エラーや「管理者の承認が必要です」画面が表示されます。

独自のAPI用のスコープを公開する(任意)

バックエンドでトークンを受け入れる場合:

  1. 開く APIを公開し、 Application ID URIを設定します(通常は以下)。 api://<client-id>.
  2. 次の名前でスコープを追加: access_as_user、同意できるユーザー:管理者とユーザー。
  3. では マニフェスト、設定する "requestedAccessTokenVersion": 2 これにより、アクセストークンがv2.0発行者形式になります。

ステップ2: プラグインをインストールしてintent filterを追加

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

では android/app/src/main/AndroidManifest.xml、内側 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>

保持 android:launchMode="singleTask" で MainActivity (Capacitor のデフォルト)。Google、Apple、Facebook ログインを使わない場合は、以下で無効にしてください。 plugins.SocialLogin.providers における capacitor.config.ts SDK がビルドに含まれないようにするためです。

ステップ3: 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,
      },
    },
  });
}

を使用すると、 resourceUrl Graph を指す /me、プラグインはログイン直後に Graph を呼び出し、プロファイルをその中に含めて返します resourceData.

Ionic Auth Connect から移行する場合、 SocialLoginAuthConnect があります。 azure だけで済むプリセット tenantId, clientId と redirectUrl。参照: Entra ID 連携ページ.

ステップ 4: サインイン

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

便利なオプション:

  • loginHint: 'jane@contoso.com' そのユーザーのアカウントを事前入力し、アカウント選択画面をスキップします。
  • prompt: 'select_account' 常にアカウント選択画面を表示します。共有デバイスを利用する場合に便利です。
  • additionalParameters: { domain_hint: 'contoso.com' } ユーザーを直接フェデレーション済みの IdP へリダイレクトします。

mail であることが多いです。 null メールボックスのないアカウント向けです。フォールバック先: userPrincipalName または preferred_username IDトークンのクレームです。

ステップ5: 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();
}

必要な各Graphスコープを追加します(例: Calendars.Read) を両方に API の権限 および scope 文字列に。

ステップ6: 独自のAPI用のトークンを取得する

アクセストークンは1つのリソースを対象とします。バックエンドを呼び出すには、 azure の隣に同じ oauth2 の initialize 呼び出し内で、Graphの代わりにAPIスコープを要求する2番目のエントリを追加します:

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

ユーザーはすでにMicrosoftセッションを持っているため、2回目のログインは通常プロンプトなしで完了します。サーバーでトークンを検証します:

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
}

マルチテナントアプリの場合、issuer には各顧客のテナント ID が含まれます。 tid 固定の issuer ではなく、許可するテナントのリストと claim を照合してください。ユーザーは oid と tidで識別し、メールではなく

ステップ 7: リフレッシュとサインアウト

// 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 のパブリッククライアント向けリフレッシュトークンは、スライディングウィンドウで最大90日間有効で、使用のたびに置き換えられます。プラグインが新しいトークンを保存します。トークンを自分で保存する場合は、たとえば Keychain や Keystore を使用してください。 NativeBiometric.setData と @capgo/capacitor-native-biometric、それらを渡します SocialLogin.refreshToken.

logout はローカルトークンをクリアし、システムブラウザで v2.0 ログアウトエンドポイントを開きます。Microsoft はカスタムスキームへのリダイレクトの代わりに「アカウントからサインアウトしました」ページを表示することがあるため、ユーザーがそれを閉じる必要がある場合があります。ローカルログアウトのみ必要な場合は、 logoutUrl 渡します prompt: 'select_account' 次回のログイン時に削除してください。

Conditional Access と Intune

ブラウザベースの OIDC は、MFA、パスワードレス、ほとんどの Conditional Access ポリシーで動作します。次の2つのケースでは追加対応が必要です:

  • 準拠デバイスまたはハイブリッド参加デバイスを要求:Entra がデバイス状態を認識できる必要があります。Android では、Company Portal または Authenticator がインストールされていれば Custom Tabs 経由で動作します。管理対象の iOS デバイスでは、MDM がデプロイした Microsoft Enterprise SSO プラグインがこれを提供します。実際のポリシーを使って早めにテストしてください。
  • Intune アプリ保護ポリシー (MAM):これらにはネイティブアプリ内の Intune App SDK が必要です。OIDC だけでは「アプリ保護ポリシーを要求する」許可コントロールを満たしません。

トラブルシューティング

エラー 意味 解決策
AADSTS50011 リダイレクト URI の不一致 「モバイル アプリとデスクトップ アプリ」に正確な URI を追加してください
AADSTS7000218 トークンリクエストにはシークレットが必要です リダイレクト URI が「Web」に登録されています。「モバイル アプリとデスクトップ アプリ」に移動してください
AADSTS9002326 クロスオリジンでのトークン交換は SPA でのみ許可されています Web ビルドがモバイル用のリダイレクト URI を使用しています。Web URL を「シングルページ アプリケーション」に登録してください
AADSTS700016 ディレクトリにアプリが見つかりません Authority のテナントが間違っているか、シングルテナント アプリが併用されています common
AADSTS50020 別テナントのユーザーアカウントです マルチテナント登録を使用するか、ユーザーをゲストとして招待してください
AADSTS65001 同意が付与されていません 管理者の同意を付与するか、ユーザーの同意を許可してください
AADSTS53003 条件付きアクセスによってブロックされました サインインログを確認してください(上記のセクションを参照)
AADSTS28000 1つのリクエストに複数リソースのスコープが含まれています Graph と API のスコープは、別々のログインでリクエストしてください
リフレッシュトークンなし offline_access 不足 スコープと権限に追加してください

Entra 管理センター サインインログ 各試行の正確な失敗理由を表示し、 logsEnabled: true プラグイン設定で、デバイス上に authorize URL とトークンレスポンスを出力します。

アップデートの配信

Policy changes on the Entra side are instant, but client-side fixes still need to reach devices. TypeScript changes such as scopes or error handling can ship through Capgo Live Updates。ネイティブの変更(インテントフィルター、新しいスキームなど)にはストア向けの新しいビルドが必要ですが、 Capgo のビルド はローカルの Mac なしで生成できます。

Capacitor アプリ向けの Live Updates

Web レイヤーのバグが本番で発生したら、App Store の審査を何日も待たずに Capgo で修正を配信しましょう。ネイティブの変更は通常の審査フローに残したまま、ユーザーはバックグラウンドでアップデートを受け取れます。

最新のブログ記事

Capgoは、真にプロフェッショナルなモバイルアプリを作るために必要な最高のインサイトを提供します。