跳转至正文

在 Capacitor 中使用 Microsoft Entra ID (Azure AD) 登录

为 Capacitor 8 应用添加支持 PKCE 的 Microsoft Entra ID (Azure AD) 登录:应用注册、重定向 URI、Graph 与 API 权限范围,以及 AADSTS 错误修复。

从 Capacitor 应用启动的 Microsoft Entra ID 登录页面

要在 Capacitor 应用中通过 Microsoft Entra ID(原 Azure Active Directory)登录,请在 Entra 管理中心注册一个公共客户端应用,并添加 移动和桌面应用程序 重定向 URI,例如 com.example.app://oauth/azure,并通过系统浏览器运行带有 PKCE 的 OpenID Connect 授权 code 流程。使用 @capgo/capacitor-social-login ,这意味着一次 initialize 调用,使用 Microsoft 身份平台 v2.0 端点以及 SocialLogin.login({ provider: 'oauth2', options: { providerId: 'azure' } }).

本指南涵盖应用注册、选择正确的 authority、Graph 和自定义 API scopes、token 验证、Conditional Access、登出以及您可能会遇到的 AADSTS 错误。目标为 2026 年 10 月的 Capacitor 8。

准备工作

  • 一个 Microsoft Entra 租户,以及可以创建应用注册的角色(Application Developer 或更高权限)。
  • 一个 Capacitor 8 应用及其 appId,稍后将其用作重定向 scheme。
  • 可选:一个后端 API,用于接收 Entra token。

为什么不在 WebView 中使用 MSAL.js

MSAL.js 专为拥有自身 origin 且能打开弹窗的浏览器设计。在 Capacitor WebView 中,origin 是 capacitor://localhost (iOS 端)以及 https://localhost 在 Android 上,弹窗会被拦截,而整页重定向会将应用导航至其自身 bundle 之外。微软针对原生应用的建议是使用系统浏览器配合 PKCE(RFC 8252),该插件正是如此实现的:

平台 使用的浏览器 额外配置
iOS ASWebAuthenticationSession 无
Android 使用 Chrome Custom Tabs androidUseCustomTabs: true Intent 过滤器
Web 端 弹窗或重定向 SPA 重定向 URI

在 Android 上,请启用 Custom Tabs。默认的嵌入式 WebView 无法与 Microsoft Authenticator、passkeys 或 Company Portal 通信,而条件访问(Conditional Access)通常依赖这些功能。

步骤 1:在 Entra ID 中注册应用

  1. 打开 Microsoft Entra 管理中心 并前往 Entra ID > 应用注册 > 新注册.
  2. 输入名称,例如“Example Mobile”。
  3. 选择 支持的账户类型。这将决定您稍后使用的 authority。
  4. 暂时跳过重定向 URI,点击 注册.
  5. 当 概览,复制 应用程序 (client) ID 以及 目录 (tenant) ID.

选择权限机构

注册时的账户类型 URL 中的权限机构段
单租户(仅限您的组织) 您的租户 ID,例如 8f1c...
任意组织目录 organizations
任意组织目录及个人 Microsoft 账户 common
仅限个人 Microsoft 账户 consumers

尽可能使用租户 ID。它能生成可预测的颁发者,并避免来自其他租户的意外登录。

添加重定向 URI

前往 身份验证 > 添加平台:

  • 移动和桌面应用:添加 com.example.app://oauth/azure 作为自定义重定向 URI。添加 com.example.app://oauth/logout ,如果你需要登出后的重定向。
  • 单页应用 (仅当你发布 Web 构建时):添加 https://app.example.com/auth/callback.

请勿在 Web 端下注册移动 URI。Web 平台重定向属于机密客户端,需要在令牌端点提供密钥。

API 权限中

API 权限中 已包含 Microsoft Graph > User.Read。添加 offline_access, openid, profile 以及 email 如果未列出。某些租户会阻止用户同意。在这种情况下,管理员必须点击 授予管理员同意,否则用户会看到 AADSTS65001 或“需要管理员批准”界面。

为您的 API 公开一个 scope(可选)

如果您的后端需要接受该 token:

  1. 打开 公开一个 API 并设置 Application ID URI,通常为 api://<client-id>.
  2. 添加一个名为 access_as_user,谁可以同意:管理员和用户。
  3. 在 清单,设置 "requestedAccessTokenVersion": 2 以便 access token 使用 v2.0 issuer 格式。

步骤 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 token 的 claim。

第 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 scope(例如 Calendars.Read),既要在 API 权限中 ,也要在 scope 字符串中。

第 6 步:获取你自己的 API token

一个 access token 只针对一个资源。要调用你的后端,请添加第二个条目,紧挨着 azure ,放在同一个 oauth2 map 中,也就是你的 initialize 调用时,请求你的 API 权限范围,而不是 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,
    },
  },
});

用户已有 Microsoft 会话,所以第二次登录通常无需提示即可完成。在服务端验证 token:

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 claim 是否在你接受的租户列表中,而不是固定的 issuer。用户标识应基于 oid 加 tid用户 ID,而不是 email。

步骤 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 有时会显示“你已退出账户”页面,而不是重定向到自定义 scheme,因此用户可能需要手动关闭该页面。如果你只需要本地登出,可以 logoutUrl 并传递 prompt: 'select_account' 在下次登录时丢弃。

条件访问和 Intune

基于浏览器的 OIDC 支持 MFA、无密码登录和大多数条件访问策略。以下两种情况需要额外处理:

  • 要求合规或混合加入的设备:Entra 必须能获取设备状态。在 Android 上,安装 Company Portal 或 Authenticator 后,可通过 Custom Tabs 实现。在受管理的 iOS 设备上,由 MDM 部署的 Microsoft Enterprise SSO 插件会提供该状态。请尽早用真实策略测试。
  • Intune 应用保护策略(MAM):这些需要在原生应用内使用 Intune App SDK。仅 OIDC 无法满足“要求应用保护策略”的授权。

故障排除

错误 含义 修复
AADSTS50011 重定向 URI 不匹配 在“移动和桌面应用”下添加确切的 URI
AADSTS7000218 Token 请求需要 secret 重定向注册在 Web 下。请移至“移动和桌面”
AADSTS9002326 仅允许 SPA 进行跨域令牌兑换 Web 构建使用移动端重定向。请在“单页应用程序”下注册 Web URL
AADSTS700016 目录中未找到应用 Authority 中的租户错误,或单租户应用与以下情况一起使用 common
AADSTS50020 来自其他租户的用户账户 使用多租户注册,或邀请用户作为来宾
AADSTS65001 未授予同意 授予管理员同意或允许用户同意
AADSTS53003 被条件访问阻止 检查登录日志,参见上文
AADSTS28000 单次请求中包含多个资源的 Scopes 在单独的登录中分别请求 Graph 和您的 API Scopes
缺少 Refresh Token offline_access 缺失 将其添加到 Scopes 和权限中

Entra 管理中心 登录日志 显示每次尝试的确切失败原因,并且 logsEnabled: true 在插件配置中,会在设备上打印 authorize URL 和 token 响应。

发布更新

Entra 端的策略变更即时生效,但客户端修复仍需推送到设备。诸如 scopes 或错误处理等 TypeScript 变更可通过 Capgo 实时更新发布。原生变更(intent filters、新 schemes)需要新的商店构建,而 Capgo 构建 无需本地 Mac 即可生成。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。