@nativelement/capacitor-msal-entra 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/NativelementCapacitorMsalEntra.podspec +18 -0
- package/README.md +460 -2
- package/android/build.gradle +55 -0
- package/android/src/main/java/io/nativelement/msalentra/MsalAuthPlugin.kt +273 -0
- package/android/src/main/res/raw/msal_config.json +14 -0
- package/dist/esm/definitions.d.ts +93 -0
- package/dist/esm/definitions.js +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/web.d.ts +41 -0
- package/dist/esm/web.js +187 -0
- package/dist/plugin.cjs.js +202 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +204 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/MsalAuthPlugin.swift +204 -0
- package/ios/Plugin/msal_config.json +5 -0
- package/package.json +68 -4
- package/web/msal_config.json +6 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
var msalBrowser = require('@azure/msal-browser');
|
|
5
|
+
|
|
6
|
+
const MsalAuth = core.registerPlugin('MsalAuth', {
|
|
7
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.MsalAuthWeb()),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const CONFIG_PATH = 'assets/msal_config.json';
|
|
11
|
+
/**
|
|
12
|
+
* Web/PWA implementation backed by `@azure/msal-browser`, kept behind the
|
|
13
|
+
* same `MsalAuthPlugin` contract the native iOS/Android implementations use
|
|
14
|
+
* so callers (see `home.page.ts`, `auth.interceptor.ts`) don't need
|
|
15
|
+
* platform-specific branches.
|
|
16
|
+
*
|
|
17
|
+
* Interactive sign-in/sign-out use msal-browser's *redirect* APIs
|
|
18
|
+
* (`loginRedirect`/`logoutRedirect`), not popups. Microsoft's own MSAL.js
|
|
19
|
+
* guidance is that popups are "unreliable in Safari and mobile browsers due
|
|
20
|
+
* to popup blocker and gesture constraints," while redirect is "the most
|
|
21
|
+
* reliable interaction type across browsers, including Safari and mobile
|
|
22
|
+
* browsers" - the deciding factor for a PWA target.
|
|
23
|
+
*
|
|
24
|
+
* The tradeoff: `loginRedirect()`/`logoutRedirect()` navigate the browser
|
|
25
|
+
* away before their promises would otherwise resolve, so `signIn()` and
|
|
26
|
+
* `signOut({ clearBrowserSession: true })` below deliberately never resolve
|
|
27
|
+
* on web - unlike native, where the OS browser overlay returns control to
|
|
28
|
+
* the same JS context. The signed-in result is instead recovered on the
|
|
29
|
+
* *next* page load, once `handleRedirectPromise()` has processed it during
|
|
30
|
+
* initialization (see `createInstance()`). Callers must not block UI on
|
|
31
|
+
* `signIn()`'s promise settling on web; they should instead call
|
|
32
|
+
* `getAccount()` on app startup to pick up a just-completed redirect.
|
|
33
|
+
*/
|
|
34
|
+
class MsalAuthWeb extends core.WebPlugin {
|
|
35
|
+
constructor() {
|
|
36
|
+
super(...arguments);
|
|
37
|
+
this.initPromise = null;
|
|
38
|
+
}
|
|
39
|
+
async signIn(options) {
|
|
40
|
+
var _a;
|
|
41
|
+
const pca = await this.getInstance();
|
|
42
|
+
try {
|
|
43
|
+
await pca.loginRedirect({
|
|
44
|
+
scopes: (_a = options.scopes) !== null && _a !== void 0 ? _a : [],
|
|
45
|
+
domainHint: options.domainHint,
|
|
46
|
+
prompt: options.prompt,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw this.rejection('SIGN_IN_FAILED', this.messageOf(error));
|
|
51
|
+
}
|
|
52
|
+
// Unreachable in practice: loginRedirect() above navigates the page away
|
|
53
|
+
// first. Nothing after this point runs in this JS context.
|
|
54
|
+
return new Promise(() => undefined);
|
|
55
|
+
}
|
|
56
|
+
async acquireTokenSilent(options) {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
const pca = await this.getInstance();
|
|
59
|
+
const account = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0];
|
|
60
|
+
if (!account) {
|
|
61
|
+
throw this.rejection('UI_REQUIRED', 'No cached account - call signIn() first.');
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const result = await pca.acquireTokenSilent({ account, scopes: (_b = options.scopes) !== null && _b !== void 0 ? _b : [] });
|
|
65
|
+
return this.toResult(result);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error instanceof msalBrowser.InteractionRequiredAuthError) {
|
|
69
|
+
throw this.rejection('UI_REQUIRED', error.message);
|
|
70
|
+
}
|
|
71
|
+
throw this.rejection('SILENT_FAILED', this.messageOf(error));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async getAccount() {
|
|
75
|
+
var _a, _b;
|
|
76
|
+
const pca = await this.getInstance();
|
|
77
|
+
const account = (_b = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0]) !== null && _b !== void 0 ? _b : null;
|
|
78
|
+
return { value: account ? this.toAccountInfo(account) : null };
|
|
79
|
+
}
|
|
80
|
+
async signOut(options) {
|
|
81
|
+
var _a, _b;
|
|
82
|
+
const pca = await this.getInstance();
|
|
83
|
+
const account = (_b = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0]) !== null && _b !== void 0 ? _b : undefined;
|
|
84
|
+
try {
|
|
85
|
+
if (options === null || options === void 0 ? void 0 : options.clearBrowserSession) {
|
|
86
|
+
// Ends the Entra browser SSO session too, not just the local cache -
|
|
87
|
+
// like signIn() above, this navigates away and never returns here.
|
|
88
|
+
await pca.logoutRedirect({ account });
|
|
89
|
+
return new Promise(() => undefined);
|
|
90
|
+
}
|
|
91
|
+
// Local-only sign-out: clears this account's cached tokens without any
|
|
92
|
+
// network call or navigation, matching native signOut()'s default
|
|
93
|
+
// "local cache only" behavior.
|
|
94
|
+
await pca.clearCache({ account });
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
throw this.rejection('SIGN_OUT_FAILED', this.messageOf(error));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async getInstance() {
|
|
101
|
+
if (!this.initPromise) {
|
|
102
|
+
// Memoize per page load, but let a failed attempt be retried instead
|
|
103
|
+
// of permanently poisoning every later call with the same rejection.
|
|
104
|
+
this.initPromise = this.createInstance().catch((error) => {
|
|
105
|
+
this.initPromise = null;
|
|
106
|
+
throw error;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return this.initPromise;
|
|
110
|
+
}
|
|
111
|
+
async createInstance() {
|
|
112
|
+
var _a, _b, _c;
|
|
113
|
+
const config = await this.loadConfig();
|
|
114
|
+
const redirectUri = (_a = config.redirect_uri) !== null && _a !== void 0 ? _a : window.location.origin;
|
|
115
|
+
const pca = await msalBrowser.createStandardPublicClientApplication({
|
|
116
|
+
auth: {
|
|
117
|
+
clientId: config.client_id,
|
|
118
|
+
authority: config.authority_url,
|
|
119
|
+
knownAuthorities: (_b = config.known_authorities) !== null && _b !== void 0 ? _b : [],
|
|
120
|
+
redirectUri,
|
|
121
|
+
postLogoutRedirectUri: (_c = config.post_logout_redirect_uri) !== null && _c !== void 0 ? _c : redirectUri,
|
|
122
|
+
},
|
|
123
|
+
cache: {
|
|
124
|
+
// localStorage (not the sessionStorage default) so a signed-in user
|
|
125
|
+
// stays signed in across app relaunches/tab closes, matching native
|
|
126
|
+
// MSAL's persistent Keychain/EncryptedSharedPreferences cache - the
|
|
127
|
+
// PWA equivalent of "not native" would otherwise re-prompt every
|
|
128
|
+
// fresh tab, unlike iOS/Android.
|
|
129
|
+
cacheLocation: msalBrowser.BrowserCacheLocation.LocalStorage,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
// Must run exactly once per page load, before any other account-reading
|
|
133
|
+
// call - this is what completes the loginRedirect()/logoutRedirect()
|
|
134
|
+
// round trip from the previous page load, if one is in flight. A failure
|
|
135
|
+
// here (e.g. the user denied consent on the Entra page) shouldn't break
|
|
136
|
+
// the whole plugin instance - it's swallowed so getAccount() simply comes
|
|
137
|
+
// back null and the app can prompt sign-in again, mirroring how a
|
|
138
|
+
// rejected native signIn() call leaves the app just as unauthenticated.
|
|
139
|
+
try {
|
|
140
|
+
const redirectResult = await pca.handleRedirectPromise();
|
|
141
|
+
if (redirectResult === null || redirectResult === void 0 ? void 0 : redirectResult.account) {
|
|
142
|
+
pca.setActiveAccount(redirectResult.account);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.warn('[MsalAuthWeb] handleRedirectPromise() failed', error);
|
|
147
|
+
}
|
|
148
|
+
return pca;
|
|
149
|
+
}
|
|
150
|
+
async loadConfig() {
|
|
151
|
+
const help = `Add a web msal_config.json at "${CONFIG_PATH}" (e.g. src/assets/msal_config.json in an Ionic/Angular app) - see the plugin README's "Web / PWA setup" section.`;
|
|
152
|
+
let response;
|
|
153
|
+
try {
|
|
154
|
+
response = await fetch(CONFIG_PATH);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
throw this.rejection('NOT_INITIALIZED', `Could not load ${CONFIG_PATH}: ${this.messageOf(error)}. ${help}`);
|
|
158
|
+
}
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
throw this.rejection('NOT_INITIALIZED', `${CONFIG_PATH} returned HTTP ${response.status}. ${help}`);
|
|
161
|
+
}
|
|
162
|
+
const config = (await response.json());
|
|
163
|
+
if (!config.client_id || !config.authority_url) {
|
|
164
|
+
throw this.rejection('NOT_INITIALIZED', `${CONFIG_PATH} is missing client_id or authority_url. ${help}`);
|
|
165
|
+
}
|
|
166
|
+
return config;
|
|
167
|
+
}
|
|
168
|
+
toResult(result) {
|
|
169
|
+
var _a, _b;
|
|
170
|
+
return {
|
|
171
|
+
accessToken: result.accessToken,
|
|
172
|
+
idToken: result.idToken,
|
|
173
|
+
expiresOn: result.expiresOn ? result.expiresOn.getTime() : Date.now(),
|
|
174
|
+
// Local MSAL account identifier, not the raw `oid` claim - matches the
|
|
175
|
+
// native implementations' accountId semantics (see definitions.ts).
|
|
176
|
+
accountId: (_b = (_a = result.account) === null || _a === void 0 ? void 0 : _a.homeAccountId) !== null && _b !== void 0 ? _b : '',
|
|
177
|
+
scopes: result.scopes,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
toAccountInfo(account) {
|
|
181
|
+
var _a, _b;
|
|
182
|
+
return {
|
|
183
|
+
accountId: account.homeAccountId,
|
|
184
|
+
username: (_a = account.username) !== null && _a !== void 0 ? _a : null,
|
|
185
|
+
idTokenClaims: (_b = account.idTokenClaims) !== null && _b !== void 0 ? _b : null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
rejection(code, message) {
|
|
189
|
+
return Object.assign(new Error(message), { code });
|
|
190
|
+
}
|
|
191
|
+
messageOf(error) {
|
|
192
|
+
return error instanceof Error ? error.message : String(error);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
197
|
+
__proto__: null,
|
|
198
|
+
MsalAuthWeb: MsalAuthWeb
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
exports.MsalAuth = MsalAuth;
|
|
202
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst MsalAuth = registerPlugin('MsalAuth', {\n web: () => import('./web').then((m) => new m.MsalAuthWeb()),\n});\nexport * from './definitions';\nexport { MsalAuth };\n","import { WebPlugin } from '@capacitor/core';\nimport { BrowserCacheLocation, InteractionRequiredAuthError, createStandardPublicClientApplication, } from '@azure/msal-browser';\nconst CONFIG_PATH = 'assets/msal_config.json';\n/**\n * Web/PWA implementation backed by `@azure/msal-browser`, kept behind the\n * same `MsalAuthPlugin` contract the native iOS/Android implementations use\n * so callers (see `home.page.ts`, `auth.interceptor.ts`) don't need\n * platform-specific branches.\n *\n * Interactive sign-in/sign-out use msal-browser's *redirect* APIs\n * (`loginRedirect`/`logoutRedirect`), not popups. Microsoft's own MSAL.js\n * guidance is that popups are \"unreliable in Safari and mobile browsers due\n * to popup blocker and gesture constraints,\" while redirect is \"the most\n * reliable interaction type across browsers, including Safari and mobile\n * browsers\" - the deciding factor for a PWA target.\n *\n * The tradeoff: `loginRedirect()`/`logoutRedirect()` navigate the browser\n * away before their promises would otherwise resolve, so `signIn()` and\n * `signOut({ clearBrowserSession: true })` below deliberately never resolve\n * on web - unlike native, where the OS browser overlay returns control to\n * the same JS context. The signed-in result is instead recovered on the\n * *next* page load, once `handleRedirectPromise()` has processed it during\n * initialization (see `createInstance()`). Callers must not block UI on\n * `signIn()`'s promise settling on web; they should instead call\n * `getAccount()` on app startup to pick up a just-completed redirect.\n */\nexport class MsalAuthWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.initPromise = null;\n }\n async signIn(options) {\n var _a;\n const pca = await this.getInstance();\n try {\n await pca.loginRedirect({\n scopes: (_a = options.scopes) !== null && _a !== void 0 ? _a : [],\n domainHint: options.domainHint,\n prompt: options.prompt,\n });\n }\n catch (error) {\n throw this.rejection('SIGN_IN_FAILED', this.messageOf(error));\n }\n // Unreachable in practice: loginRedirect() above navigates the page away\n // first. Nothing after this point runs in this JS context.\n return new Promise(() => undefined);\n }\n async acquireTokenSilent(options) {\n var _a, _b;\n const pca = await this.getInstance();\n const account = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0];\n if (!account) {\n throw this.rejection('UI_REQUIRED', 'No cached account - call signIn() first.');\n }\n try {\n const result = await pca.acquireTokenSilent({ account, scopes: (_b = options.scopes) !== null && _b !== void 0 ? _b : [] });\n return this.toResult(result);\n }\n catch (error) {\n if (error instanceof InteractionRequiredAuthError) {\n throw this.rejection('UI_REQUIRED', error.message);\n }\n throw this.rejection('SILENT_FAILED', this.messageOf(error));\n }\n }\n async getAccount() {\n var _a, _b;\n const pca = await this.getInstance();\n const account = (_b = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0]) !== null && _b !== void 0 ? _b : null;\n return { value: account ? this.toAccountInfo(account) : null };\n }\n async signOut(options) {\n var _a, _b;\n const pca = await this.getInstance();\n const account = (_b = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0]) !== null && _b !== void 0 ? _b : undefined;\n try {\n if (options === null || options === void 0 ? void 0 : options.clearBrowserSession) {\n // Ends the Entra browser SSO session too, not just the local cache -\n // like signIn() above, this navigates away and never returns here.\n await pca.logoutRedirect({ account });\n return new Promise(() => undefined);\n }\n // Local-only sign-out: clears this account's cached tokens without any\n // network call or navigation, matching native signOut()'s default\n // \"local cache only\" behavior.\n await pca.clearCache({ account });\n }\n catch (error) {\n throw this.rejection('SIGN_OUT_FAILED', this.messageOf(error));\n }\n }\n async getInstance() {\n if (!this.initPromise) {\n // Memoize per page load, but let a failed attempt be retried instead\n // of permanently poisoning every later call with the same rejection.\n this.initPromise = this.createInstance().catch((error) => {\n this.initPromise = null;\n throw error;\n });\n }\n return this.initPromise;\n }\n async createInstance() {\n var _a, _b, _c;\n const config = await this.loadConfig();\n const redirectUri = (_a = config.redirect_uri) !== null && _a !== void 0 ? _a : window.location.origin;\n const pca = await createStandardPublicClientApplication({\n auth: {\n clientId: config.client_id,\n authority: config.authority_url,\n knownAuthorities: (_b = config.known_authorities) !== null && _b !== void 0 ? _b : [],\n redirectUri,\n postLogoutRedirectUri: (_c = config.post_logout_redirect_uri) !== null && _c !== void 0 ? _c : redirectUri,\n },\n cache: {\n // localStorage (not the sessionStorage default) so a signed-in user\n // stays signed in across app relaunches/tab closes, matching native\n // MSAL's persistent Keychain/EncryptedSharedPreferences cache - the\n // PWA equivalent of \"not native\" would otherwise re-prompt every\n // fresh tab, unlike iOS/Android.\n cacheLocation: BrowserCacheLocation.LocalStorage,\n },\n });\n // Must run exactly once per page load, before any other account-reading\n // call - this is what completes the loginRedirect()/logoutRedirect()\n // round trip from the previous page load, if one is in flight. A failure\n // here (e.g. the user denied consent on the Entra page) shouldn't break\n // the whole plugin instance - it's swallowed so getAccount() simply comes\n // back null and the app can prompt sign-in again, mirroring how a\n // rejected native signIn() call leaves the app just as unauthenticated.\n try {\n const redirectResult = await pca.handleRedirectPromise();\n if (redirectResult === null || redirectResult === void 0 ? void 0 : redirectResult.account) {\n pca.setActiveAccount(redirectResult.account);\n }\n }\n catch (error) {\n console.warn('[MsalAuthWeb] handleRedirectPromise() failed', error);\n }\n return pca;\n }\n async loadConfig() {\n const help = `Add a web msal_config.json at \"${CONFIG_PATH}\" (e.g. src/assets/msal_config.json in an Ionic/Angular app) - see the plugin README's \"Web / PWA setup\" section.`;\n let response;\n try {\n response = await fetch(CONFIG_PATH);\n }\n catch (error) {\n throw this.rejection('NOT_INITIALIZED', `Could not load ${CONFIG_PATH}: ${this.messageOf(error)}. ${help}`);\n }\n if (!response.ok) {\n throw this.rejection('NOT_INITIALIZED', `${CONFIG_PATH} returned HTTP ${response.status}. ${help}`);\n }\n const config = (await response.json());\n if (!config.client_id || !config.authority_url) {\n throw this.rejection('NOT_INITIALIZED', `${CONFIG_PATH} is missing client_id or authority_url. ${help}`);\n }\n return config;\n }\n toResult(result) {\n var _a, _b;\n return {\n accessToken: result.accessToken,\n idToken: result.idToken,\n expiresOn: result.expiresOn ? result.expiresOn.getTime() : Date.now(),\n // Local MSAL account identifier, not the raw `oid` claim - matches the\n // native implementations' accountId semantics (see definitions.ts).\n accountId: (_b = (_a = result.account) === null || _a === void 0 ? void 0 : _a.homeAccountId) !== null && _b !== void 0 ? _b : '',\n scopes: result.scopes,\n };\n }\n toAccountInfo(account) {\n var _a, _b;\n return {\n accountId: account.homeAccountId,\n username: (_a = account.username) !== null && _a !== void 0 ? _a : null,\n idTokenClaims: (_b = account.idTokenClaims) !== null && _b !== void 0 ? _b : null,\n };\n }\n rejection(code, message) {\n return Object.assign(new Error(message), { code });\n }\n messageOf(error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n"],"names":["registerPlugin","WebPlugin","InteractionRequiredAuthError","createStandardPublicClientApplication","BrowserCacheLocation"],"mappings":"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"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
var capacitorMsalEntraPlugin = (function (exports, core, msalBrowser) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const MsalAuth = core.registerPlugin('MsalAuth', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.MsalAuthWeb()),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const CONFIG_PATH = 'assets/msal_config.json';
|
|
9
|
+
/**
|
|
10
|
+
* Web/PWA implementation backed by `@azure/msal-browser`, kept behind the
|
|
11
|
+
* same `MsalAuthPlugin` contract the native iOS/Android implementations use
|
|
12
|
+
* so callers (see `home.page.ts`, `auth.interceptor.ts`) don't need
|
|
13
|
+
* platform-specific branches.
|
|
14
|
+
*
|
|
15
|
+
* Interactive sign-in/sign-out use msal-browser's *redirect* APIs
|
|
16
|
+
* (`loginRedirect`/`logoutRedirect`), not popups. Microsoft's own MSAL.js
|
|
17
|
+
* guidance is that popups are "unreliable in Safari and mobile browsers due
|
|
18
|
+
* to popup blocker and gesture constraints," while redirect is "the most
|
|
19
|
+
* reliable interaction type across browsers, including Safari and mobile
|
|
20
|
+
* browsers" - the deciding factor for a PWA target.
|
|
21
|
+
*
|
|
22
|
+
* The tradeoff: `loginRedirect()`/`logoutRedirect()` navigate the browser
|
|
23
|
+
* away before their promises would otherwise resolve, so `signIn()` and
|
|
24
|
+
* `signOut({ clearBrowserSession: true })` below deliberately never resolve
|
|
25
|
+
* on web - unlike native, where the OS browser overlay returns control to
|
|
26
|
+
* the same JS context. The signed-in result is instead recovered on the
|
|
27
|
+
* *next* page load, once `handleRedirectPromise()` has processed it during
|
|
28
|
+
* initialization (see `createInstance()`). Callers must not block UI on
|
|
29
|
+
* `signIn()`'s promise settling on web; they should instead call
|
|
30
|
+
* `getAccount()` on app startup to pick up a just-completed redirect.
|
|
31
|
+
*/
|
|
32
|
+
class MsalAuthWeb extends core.WebPlugin {
|
|
33
|
+
constructor() {
|
|
34
|
+
super(...arguments);
|
|
35
|
+
this.initPromise = null;
|
|
36
|
+
}
|
|
37
|
+
async signIn(options) {
|
|
38
|
+
var _a;
|
|
39
|
+
const pca = await this.getInstance();
|
|
40
|
+
try {
|
|
41
|
+
await pca.loginRedirect({
|
|
42
|
+
scopes: (_a = options.scopes) !== null && _a !== void 0 ? _a : [],
|
|
43
|
+
domainHint: options.domainHint,
|
|
44
|
+
prompt: options.prompt,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw this.rejection('SIGN_IN_FAILED', this.messageOf(error));
|
|
49
|
+
}
|
|
50
|
+
// Unreachable in practice: loginRedirect() above navigates the page away
|
|
51
|
+
// first. Nothing after this point runs in this JS context.
|
|
52
|
+
return new Promise(() => undefined);
|
|
53
|
+
}
|
|
54
|
+
async acquireTokenSilent(options) {
|
|
55
|
+
var _a, _b;
|
|
56
|
+
const pca = await this.getInstance();
|
|
57
|
+
const account = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0];
|
|
58
|
+
if (!account) {
|
|
59
|
+
throw this.rejection('UI_REQUIRED', 'No cached account - call signIn() first.');
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const result = await pca.acquireTokenSilent({ account, scopes: (_b = options.scopes) !== null && _b !== void 0 ? _b : [] });
|
|
63
|
+
return this.toResult(result);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof msalBrowser.InteractionRequiredAuthError) {
|
|
67
|
+
throw this.rejection('UI_REQUIRED', error.message);
|
|
68
|
+
}
|
|
69
|
+
throw this.rejection('SILENT_FAILED', this.messageOf(error));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async getAccount() {
|
|
73
|
+
var _a, _b;
|
|
74
|
+
const pca = await this.getInstance();
|
|
75
|
+
const account = (_b = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0]) !== null && _b !== void 0 ? _b : null;
|
|
76
|
+
return { value: account ? this.toAccountInfo(account) : null };
|
|
77
|
+
}
|
|
78
|
+
async signOut(options) {
|
|
79
|
+
var _a, _b;
|
|
80
|
+
const pca = await this.getInstance();
|
|
81
|
+
const account = (_b = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0]) !== null && _b !== void 0 ? _b : undefined;
|
|
82
|
+
try {
|
|
83
|
+
if (options === null || options === void 0 ? void 0 : options.clearBrowserSession) {
|
|
84
|
+
// Ends the Entra browser SSO session too, not just the local cache -
|
|
85
|
+
// like signIn() above, this navigates away and never returns here.
|
|
86
|
+
await pca.logoutRedirect({ account });
|
|
87
|
+
return new Promise(() => undefined);
|
|
88
|
+
}
|
|
89
|
+
// Local-only sign-out: clears this account's cached tokens without any
|
|
90
|
+
// network call or navigation, matching native signOut()'s default
|
|
91
|
+
// "local cache only" behavior.
|
|
92
|
+
await pca.clearCache({ account });
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw this.rejection('SIGN_OUT_FAILED', this.messageOf(error));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async getInstance() {
|
|
99
|
+
if (!this.initPromise) {
|
|
100
|
+
// Memoize per page load, but let a failed attempt be retried instead
|
|
101
|
+
// of permanently poisoning every later call with the same rejection.
|
|
102
|
+
this.initPromise = this.createInstance().catch((error) => {
|
|
103
|
+
this.initPromise = null;
|
|
104
|
+
throw error;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return this.initPromise;
|
|
108
|
+
}
|
|
109
|
+
async createInstance() {
|
|
110
|
+
var _a, _b, _c;
|
|
111
|
+
const config = await this.loadConfig();
|
|
112
|
+
const redirectUri = (_a = config.redirect_uri) !== null && _a !== void 0 ? _a : window.location.origin;
|
|
113
|
+
const pca = await msalBrowser.createStandardPublicClientApplication({
|
|
114
|
+
auth: {
|
|
115
|
+
clientId: config.client_id,
|
|
116
|
+
authority: config.authority_url,
|
|
117
|
+
knownAuthorities: (_b = config.known_authorities) !== null && _b !== void 0 ? _b : [],
|
|
118
|
+
redirectUri,
|
|
119
|
+
postLogoutRedirectUri: (_c = config.post_logout_redirect_uri) !== null && _c !== void 0 ? _c : redirectUri,
|
|
120
|
+
},
|
|
121
|
+
cache: {
|
|
122
|
+
// localStorage (not the sessionStorage default) so a signed-in user
|
|
123
|
+
// stays signed in across app relaunches/tab closes, matching native
|
|
124
|
+
// MSAL's persistent Keychain/EncryptedSharedPreferences cache - the
|
|
125
|
+
// PWA equivalent of "not native" would otherwise re-prompt every
|
|
126
|
+
// fresh tab, unlike iOS/Android.
|
|
127
|
+
cacheLocation: msalBrowser.BrowserCacheLocation.LocalStorage,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
// Must run exactly once per page load, before any other account-reading
|
|
131
|
+
// call - this is what completes the loginRedirect()/logoutRedirect()
|
|
132
|
+
// round trip from the previous page load, if one is in flight. A failure
|
|
133
|
+
// here (e.g. the user denied consent on the Entra page) shouldn't break
|
|
134
|
+
// the whole plugin instance - it's swallowed so getAccount() simply comes
|
|
135
|
+
// back null and the app can prompt sign-in again, mirroring how a
|
|
136
|
+
// rejected native signIn() call leaves the app just as unauthenticated.
|
|
137
|
+
try {
|
|
138
|
+
const redirectResult = await pca.handleRedirectPromise();
|
|
139
|
+
if (redirectResult === null || redirectResult === void 0 ? void 0 : redirectResult.account) {
|
|
140
|
+
pca.setActiveAccount(redirectResult.account);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.warn('[MsalAuthWeb] handleRedirectPromise() failed', error);
|
|
145
|
+
}
|
|
146
|
+
return pca;
|
|
147
|
+
}
|
|
148
|
+
async loadConfig() {
|
|
149
|
+
const help = `Add a web msal_config.json at "${CONFIG_PATH}" (e.g. src/assets/msal_config.json in an Ionic/Angular app) - see the plugin README's "Web / PWA setup" section.`;
|
|
150
|
+
let response;
|
|
151
|
+
try {
|
|
152
|
+
response = await fetch(CONFIG_PATH);
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw this.rejection('NOT_INITIALIZED', `Could not load ${CONFIG_PATH}: ${this.messageOf(error)}. ${help}`);
|
|
156
|
+
}
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
throw this.rejection('NOT_INITIALIZED', `${CONFIG_PATH} returned HTTP ${response.status}. ${help}`);
|
|
159
|
+
}
|
|
160
|
+
const config = (await response.json());
|
|
161
|
+
if (!config.client_id || !config.authority_url) {
|
|
162
|
+
throw this.rejection('NOT_INITIALIZED', `${CONFIG_PATH} is missing client_id or authority_url. ${help}`);
|
|
163
|
+
}
|
|
164
|
+
return config;
|
|
165
|
+
}
|
|
166
|
+
toResult(result) {
|
|
167
|
+
var _a, _b;
|
|
168
|
+
return {
|
|
169
|
+
accessToken: result.accessToken,
|
|
170
|
+
idToken: result.idToken,
|
|
171
|
+
expiresOn: result.expiresOn ? result.expiresOn.getTime() : Date.now(),
|
|
172
|
+
// Local MSAL account identifier, not the raw `oid` claim - matches the
|
|
173
|
+
// native implementations' accountId semantics (see definitions.ts).
|
|
174
|
+
accountId: (_b = (_a = result.account) === null || _a === void 0 ? void 0 : _a.homeAccountId) !== null && _b !== void 0 ? _b : '',
|
|
175
|
+
scopes: result.scopes,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
toAccountInfo(account) {
|
|
179
|
+
var _a, _b;
|
|
180
|
+
return {
|
|
181
|
+
accountId: account.homeAccountId,
|
|
182
|
+
username: (_a = account.username) !== null && _a !== void 0 ? _a : null,
|
|
183
|
+
idTokenClaims: (_b = account.idTokenClaims) !== null && _b !== void 0 ? _b : null,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
rejection(code, message) {
|
|
187
|
+
return Object.assign(new Error(message), { code });
|
|
188
|
+
}
|
|
189
|
+
messageOf(error) {
|
|
190
|
+
return error instanceof Error ? error.message : String(error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
195
|
+
__proto__: null,
|
|
196
|
+
MsalAuthWeb: MsalAuthWeb
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
exports.MsalAuth = MsalAuth;
|
|
200
|
+
|
|
201
|
+
return exports;
|
|
202
|
+
|
|
203
|
+
})({}, capacitorExports, msal);
|
|
204
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst MsalAuth = registerPlugin('MsalAuth', {\n web: () => import('./web').then((m) => new m.MsalAuthWeb()),\n});\nexport * from './definitions';\nexport { MsalAuth };\n","import { WebPlugin } from '@capacitor/core';\nimport { BrowserCacheLocation, InteractionRequiredAuthError, createStandardPublicClientApplication, } from '@azure/msal-browser';\nconst CONFIG_PATH = 'assets/msal_config.json';\n/**\n * Web/PWA implementation backed by `@azure/msal-browser`, kept behind the\n * same `MsalAuthPlugin` contract the native iOS/Android implementations use\n * so callers (see `home.page.ts`, `auth.interceptor.ts`) don't need\n * platform-specific branches.\n *\n * Interactive sign-in/sign-out use msal-browser's *redirect* APIs\n * (`loginRedirect`/`logoutRedirect`), not popups. Microsoft's own MSAL.js\n * guidance is that popups are \"unreliable in Safari and mobile browsers due\n * to popup blocker and gesture constraints,\" while redirect is \"the most\n * reliable interaction type across browsers, including Safari and mobile\n * browsers\" - the deciding factor for a PWA target.\n *\n * The tradeoff: `loginRedirect()`/`logoutRedirect()` navigate the browser\n * away before their promises would otherwise resolve, so `signIn()` and\n * `signOut({ clearBrowserSession: true })` below deliberately never resolve\n * on web - unlike native, where the OS browser overlay returns control to\n * the same JS context. The signed-in result is instead recovered on the\n * *next* page load, once `handleRedirectPromise()` has processed it during\n * initialization (see `createInstance()`). Callers must not block UI on\n * `signIn()`'s promise settling on web; they should instead call\n * `getAccount()` on app startup to pick up a just-completed redirect.\n */\nexport class MsalAuthWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.initPromise = null;\n }\n async signIn(options) {\n var _a;\n const pca = await this.getInstance();\n try {\n await pca.loginRedirect({\n scopes: (_a = options.scopes) !== null && _a !== void 0 ? _a : [],\n domainHint: options.domainHint,\n prompt: options.prompt,\n });\n }\n catch (error) {\n throw this.rejection('SIGN_IN_FAILED', this.messageOf(error));\n }\n // Unreachable in practice: loginRedirect() above navigates the page away\n // first. Nothing after this point runs in this JS context.\n return new Promise(() => undefined);\n }\n async acquireTokenSilent(options) {\n var _a, _b;\n const pca = await this.getInstance();\n const account = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0];\n if (!account) {\n throw this.rejection('UI_REQUIRED', 'No cached account - call signIn() first.');\n }\n try {\n const result = await pca.acquireTokenSilent({ account, scopes: (_b = options.scopes) !== null && _b !== void 0 ? _b : [] });\n return this.toResult(result);\n }\n catch (error) {\n if (error instanceof InteractionRequiredAuthError) {\n throw this.rejection('UI_REQUIRED', error.message);\n }\n throw this.rejection('SILENT_FAILED', this.messageOf(error));\n }\n }\n async getAccount() {\n var _a, _b;\n const pca = await this.getInstance();\n const account = (_b = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0]) !== null && _b !== void 0 ? _b : null;\n return { value: account ? this.toAccountInfo(account) : null };\n }\n async signOut(options) {\n var _a, _b;\n const pca = await this.getInstance();\n const account = (_b = (_a = pca.getActiveAccount()) !== null && _a !== void 0 ? _a : pca.getAllAccounts()[0]) !== null && _b !== void 0 ? _b : undefined;\n try {\n if (options === null || options === void 0 ? void 0 : options.clearBrowserSession) {\n // Ends the Entra browser SSO session too, not just the local cache -\n // like signIn() above, this navigates away and never returns here.\n await pca.logoutRedirect({ account });\n return new Promise(() => undefined);\n }\n // Local-only sign-out: clears this account's cached tokens without any\n // network call or navigation, matching native signOut()'s default\n // \"local cache only\" behavior.\n await pca.clearCache({ account });\n }\n catch (error) {\n throw this.rejection('SIGN_OUT_FAILED', this.messageOf(error));\n }\n }\n async getInstance() {\n if (!this.initPromise) {\n // Memoize per page load, but let a failed attempt be retried instead\n // of permanently poisoning every later call with the same rejection.\n this.initPromise = this.createInstance().catch((error) => {\n this.initPromise = null;\n throw error;\n });\n }\n return this.initPromise;\n }\n async createInstance() {\n var _a, _b, _c;\n const config = await this.loadConfig();\n const redirectUri = (_a = config.redirect_uri) !== null && _a !== void 0 ? _a : window.location.origin;\n const pca = await createStandardPublicClientApplication({\n auth: {\n clientId: config.client_id,\n authority: config.authority_url,\n knownAuthorities: (_b = config.known_authorities) !== null && _b !== void 0 ? _b : [],\n redirectUri,\n postLogoutRedirectUri: (_c = config.post_logout_redirect_uri) !== null && _c !== void 0 ? _c : redirectUri,\n },\n cache: {\n // localStorage (not the sessionStorage default) so a signed-in user\n // stays signed in across app relaunches/tab closes, matching native\n // MSAL's persistent Keychain/EncryptedSharedPreferences cache - the\n // PWA equivalent of \"not native\" would otherwise re-prompt every\n // fresh tab, unlike iOS/Android.\n cacheLocation: BrowserCacheLocation.LocalStorage,\n },\n });\n // Must run exactly once per page load, before any other account-reading\n // call - this is what completes the loginRedirect()/logoutRedirect()\n // round trip from the previous page load, if one is in flight. A failure\n // here (e.g. the user denied consent on the Entra page) shouldn't break\n // the whole plugin instance - it's swallowed so getAccount() simply comes\n // back null and the app can prompt sign-in again, mirroring how a\n // rejected native signIn() call leaves the app just as unauthenticated.\n try {\n const redirectResult = await pca.handleRedirectPromise();\n if (redirectResult === null || redirectResult === void 0 ? void 0 : redirectResult.account) {\n pca.setActiveAccount(redirectResult.account);\n }\n }\n catch (error) {\n console.warn('[MsalAuthWeb] handleRedirectPromise() failed', error);\n }\n return pca;\n }\n async loadConfig() {\n const help = `Add a web msal_config.json at \"${CONFIG_PATH}\" (e.g. src/assets/msal_config.json in an Ionic/Angular app) - see the plugin README's \"Web / PWA setup\" section.`;\n let response;\n try {\n response = await fetch(CONFIG_PATH);\n }\n catch (error) {\n throw this.rejection('NOT_INITIALIZED', `Could not load ${CONFIG_PATH}: ${this.messageOf(error)}. ${help}`);\n }\n if (!response.ok) {\n throw this.rejection('NOT_INITIALIZED', `${CONFIG_PATH} returned HTTP ${response.status}. ${help}`);\n }\n const config = (await response.json());\n if (!config.client_id || !config.authority_url) {\n throw this.rejection('NOT_INITIALIZED', `${CONFIG_PATH} is missing client_id or authority_url. ${help}`);\n }\n return config;\n }\n toResult(result) {\n var _a, _b;\n return {\n accessToken: result.accessToken,\n idToken: result.idToken,\n expiresOn: result.expiresOn ? result.expiresOn.getTime() : Date.now(),\n // Local MSAL account identifier, not the raw `oid` claim - matches the\n // native implementations' accountId semantics (see definitions.ts).\n accountId: (_b = (_a = result.account) === null || _a === void 0 ? void 0 : _a.homeAccountId) !== null && _b !== void 0 ? _b : '',\n scopes: result.scopes,\n };\n }\n toAccountInfo(account) {\n var _a, _b;\n return {\n accountId: account.homeAccountId,\n username: (_a = account.username) !== null && _a !== void 0 ? _a : null,\n idTokenClaims: (_b = account.idTokenClaims) !== null && _b !== void 0 ? _b : null,\n };\n }\n rejection(code, message) {\n return Object.assign(new Error(message), { code });\n }\n messageOf(error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n"],"names":["registerPlugin","WebPlugin","InteractionRequiredAuthError","createStandardPublicClientApplication","BrowserCacheLocation"],"mappings":"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"}
|