要在 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 中注册应用
- 打开 Microsoft Entra 管理中心 并前往 Entra ID > 应用注册 > 新注册.
- 输入名称,例如“Example Mobile”。
- 选择 支持的账户类型。这将决定您稍后使用的 authority。
- 暂时跳过重定向 URI,点击 注册.
- 当 概览,复制 应用程序 (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:
- 打开 公开一个 API 并设置 Application ID URI,通常为
api://<client-id>. - 添加一个名为
access_as_user,谁可以同意:管理员和用户。 - 在 清单,设置
"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 即可生成。