@shardev/capacitor-microsoft-auth
v1.0.1
Published
Capacitor plugin for native Microsoft sign-in (Microsoft Entra ID / Azure AD) using MSAL on iOS and Android
Downloads
110
Maintainers
Readme
@shardev/capacitor-microsoft-auth
Capacitor plugin for native Microsoft sign-in on iOS and Android. It wraps the official MSAL (Microsoft Authentication Library) and supports Microsoft Entra ID (Azure AD) work/school accounts, Microsoft accounts, and multi-tenant sign-in.
- Interactive sign-in with account picker
- Silent token acquisition and refresh
- Multi-account management
- Sign-out
Supported platforms
| Platform | Library | Minimum version |
| -------- | ------- | --------------- |
| iOS | MSAL iOS 2.x (CocoaPods MSAL) | iOS 13+ |
| Android | MSAL Android 8.4.x (Maven com.microsoft.identity.client:msal) | Android 7.0 (API 24) |
Web/PWA is not supported because Microsoft native sign-in requires MSAL; calling any
method other than initialize() on the web rejects with NOT_IMPLEMENTED.
Installation
npm install @shardev/capacitor-microsoft-auth
npx cap syncPrerequisites
1. Register an application in Microsoft Entra ID
- Go to the Azure portal → Microsoft Entra ID → App registrations → New registration.
- Give it a name and choose a supported account type (work/school, personal, or both).
- Note the Application (client) ID.
2. Register the redirect URIs
For each platform, add a redirect URI of type Public client/native (mobile & desktop).
iOS
msauth.<BUNDLE_ID>://authWhere <BUNDLE_ID> is your app's bundle identifier, e.g. msauth.com.example.app://auth.
Android
msauth.<package-name>://<base64-signature-hash>Where <package-name> is your Android application ID and <base64-signature-hash> is the
Base64-encoded SHA-256 fingerprint of the certificate signing the app. Generate it with:
keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64For a release build, replace the debug keystore with your signing keystore and adjust the alias.
3. Grant API permissions
Add the delegated permissions your app needs (e.g. User.Read for Microsoft Graph).
offline_access, openid, profile and email are granted by default by MSAL and do
not need to be added in the portal.
Platform setup
iOS
Podfile
npx cap sync adds the pod automatically. Verify your ios/App/Podfile contains:
pod 'ShardevCapacitorMicrosoftAuth', :path => '../node_modules/@shardev/capacitor-microsoft-auth'The MSAL pod is pulled in automatically.
Info.plist
Register the redirect URL scheme so the browser can return to your app. In
ios/App/App/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>msauth.$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</array>Keychain sharing
Enable the Keychain Sharing capability in Xcode and add the group
com.microsoft.adalcache. This lets MSAL persist tokens securely across app launches
and is required for SSO between apps that share the group.
No AppDelegate changes are required. The plugin observes Capacitor's URL-open notification and forwards the MSAL response internally.
Android
Manifest
Add the BrowserTabActivity to your app's android/app/src/main/AndroidManifest.xml,
inside <application>:
<activity
android:name="com.microsoft.identity.client.BrowserTabActivity"
android:exported="true">
<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="msauth"
android:host="<package-name>"
android:path="/<base64-signature-hash>" />
</intent-filter>
</activity>The scheme/host/path must match the Android redirect URI registered in Azure, and the redirect URI must be passed to
initialize().
Minimum SDK
MSAL requires minSdkVersion 24. Ensure android/app/build.gradle (or your
variables.gradle) sets minSdkVersion to at least 24:
minSdkVersion 24Usage
import { MicrosoftAuth } from '@shardev/capacitor-microsoft-auth';
async function signIn() {
// 1. Initialize once at app startup (or before the first sign-in).
await MicrosoftAuth.initialize({
clientId: '00000000-0000-0000-0000-000000000000',
tenant: 'common', // or a tenant ID/domain, 'organizations', 'consumers'
// Android only: the redirect URI registered in Azure.
redirectUri: 'msauth.com.example.app://xxxxxxxxxxxxxxxxxxxxxxxxxxx',
// Optional defaults (openid/profile/email are used when omitted).
scopes: ['User.Read'],
});
// 2. Restore the session silently.
const { account } = await MicrosoftAuth.getCurrentAccount();
// 3. Sign in interactively if there is no account.
let result = account
? await MicrosoftAuth.getToken({ scopes: ['User.Read'] })
: await MicrosoftAuth.login({
scopes: ['User.Read'],
loginHint: '[email protected]',
});
console.log(result.accessToken, result.account.username);
// 4. Call your backend with the token.
// Authorization: Bearer <result.accessToken>
// 5. Sign out.
await MicrosoftAuth.logout();
}Getting a token for a specific account
const accounts = await MicrosoftAuth.getAccounts();
for (const account of accounts.accounts) {
const result = await MicrosoftAuth.getToken({ scopes: ['User.Read'], accountId: account.id });
}API
initialize(options)
Creates the MSAL client. Call it once before any other method.
| Option | Type | Default | Description |
| --------------- | ---------- | ------------------------------------------ | ---------------------------------------------------------------------- |
| clientId | string | – (required) | Azure AD application (client) ID. |
| tenant | string | 'common' | common, organizations, consumers, or a tenant ID/domain. |
| authorityUrl | string | https://login.microsoftonline.com/<tenant> | Full authority URL; overrides tenant. |
| scopes | string[] | ['openid', 'profile', 'email'] | Default scopes for login/getToken when none are passed. |
| redirectUri | string | platform default (iOS) / required (Android) | Override the redirect URI (advanced). |
| keychainGroup | string | MSAL default | iOS only: keychain sharing group for the token cache. |
On Android redirectUri is required (it is embedded in the BrowserTabActivity
registered in your manifest). On iOS it defaults to msauth.<bundle-id>://auth.
login(options?)
Interactive sign-in with the system account picker.
| Option | Type | Description |
| ------------ | ---------------------------------------- | ------------------------------------------------- |
| scopes | string[] | Overrides the default scopes. |
| loginHint | string | Pre-fills the account (usually the email). |
| domainHint | string | Hints the tenant, e.g. contoso.com. |
| prompt | 'select_account' \| 'login' \| 'consent' \| 'create' \| 'when_required' | Prompt behavior. Defaults to select_account. |
Returns Promise<LoginResult>.
getToken(options?)
Silently acquires a token for a previously signed-in account. Fails with
INTERACTION_REQUIRED when the user must sign in again (call login() then).
| Option | Type | Default | Description |
| --------------- | ---------- | ------- | ----------------------------------------------------------------- |
| scopes | string[] | default | Overrides the default scopes. |
| forceRefresh | boolean | false | Ignores the cached token and acquires a new one. |
| accountId | string | first account | Account to use, matched by id or username. |
getAccounts()
Returns Promise<{ accounts: MicrosoftAccount[] }> with every account known to the app.
getCurrentAccount()
Returns Promise<{ account: MicrosoftAccount | null }>, the first known account or null.
logout()
Signs the current account out and removes it from the local token cache.
Types
interface LoginResult {
accessToken: string;
idToken?: string;
expiresOn?: number; // UNIX timestamp in milliseconds
scopes: string[];
tenantId?: string;
account: MicrosoftAccount;
}
interface MicrosoftAccount {
id: string;
username: string;
environment: string;
homeAccountId: string;
objectId: string;
tenantId: string;
name?: string;
email?: string;
claims?: Record<string, unknown>;
}Error codes
| Code | Description |
| ----------------------- | --------------------------------------------------------------------------- |
| NOT_INITIALIZED | A method other than initialize() was called first. |
| INVALID_ARGUMENT | Missing or invalid options (e.g. clientId or Android redirectUri). |
| USER_CANCELLED | The user cancelled the sign-in flow. |
| INTERACTION_REQUIRED | Interactive sign-in is required (e.g. expired refresh token). Call login(). |
| NO_ACCOUNT | No signed-in account available for a silent request. |
| NO_VIEW_CONTROLLER | iOS: no view controller was available to present the flow. |
| NO_ACTIVITY | Android: no foreground activity was available to present the flow. |
| INVALID_AUTHORITY | The authority URL could not be parsed. |
| NOT_IMPLEMENTED | The method is not available on the web. |
| UNEXPECTED | The native flow completed without a result. |
| MSAL error code / AUTH_ERROR | Any other failure; the native error code and message are forwarded. |
How it works
- Android:
PublicClientApplication.create(...)is used programmatically withIMultipleAccountPublicClientApplication. Interactive requests useAcquireTokenParameters, silent requests useAcquireTokenSilentParameters. - iOS:
MSALPublicClientApplicationis created from aMSALPublicClientApplicationConfig. The MSAL response URL is handled by observing Capacitor's.capacitorOpenURLnotification, so no AppDelegate changes are needed.
Contributing
npm install
npm run verify # typechecks and builds the TypeScript/rollup outputLicense
MIT
