@equinor/fusion-framework-module-msal 0.1.0-beta.4 → 0.1.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 +26 -0
- package/client/package.json +7 -0
- package/dist/esm/client/behavior.js +2 -0
- package/dist/esm/client/behavior.js.map +1 -0
- package/dist/esm/client/client.js +63 -0
- package/dist/esm/client/client.js.map +1 -0
- package/dist/esm/client/create-auth-client.js +16 -0
- package/dist/esm/client/create-auth-client.js.map +1 -0
- package/dist/esm/client/index.js +5 -0
- package/dist/esm/client/index.js.map +1 -0
- package/dist/esm/client/log/console.js +27 -0
- package/dist/esm/client/log/console.js.map +1 -0
- package/dist/esm/client/request.js +2 -0
- package/dist/esm/client/request.js.map +1 -0
- package/dist/esm/client/util/browser.js +5 -0
- package/dist/esm/client/util/browser.js.map +1 -0
- package/dist/esm/client/util/url.js +10 -0
- package/dist/esm/client/util/url.js.map +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/module.js +3 -1
- package/dist/esm/module.js.map +1 -1
- package/dist/esm/provider.js +27 -8
- package/dist/esm/provider.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/client/behavior.d.ts +2 -0
- package/dist/types/client/client.d.ts +13 -0
- package/dist/types/client/create-auth-client.d.ts +7 -0
- package/dist/types/client/index.d.ts +4 -0
- package/dist/types/client/log/console.d.ts +8 -0
- package/dist/types/client/request.d.ts +2 -0
- package/dist/types/client/util/browser.d.ts +1 -0
- package/dist/types/client/util/url.d.ts +1 -0
- package/dist/types/configurator.d.ts +1 -1
- package/dist/types/provider.d.ts +12 -12
- package/package.json +5 -5
- package/src/client/behavior.ts +14 -0
- package/src/client/client.ts +158 -0
- package/src/client/create-auth-client.ts +48 -0
- package/src/client/index.ts +6 -0
- package/src/client/log/console.ts +55 -0
- package/src/client/request.ts +66 -0
- package/src/client/util/browser.ts +14 -0
- package/src/client/util/url.ts +24 -0
- package/src/configurator.ts +1 -1
- package/src/index.ts +1 -0
- package/src/module.ts +3 -1
- package/src/provider.ts +56 -15
package/dist/types/provider.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { AuthClient } from '
|
|
2
|
-
import { AuthRequest } from '@equinor/fusion-web-msal/dist/request';
|
|
1
|
+
import { AuthClient, AuthRequest } from './client';
|
|
3
2
|
import { IAuthConfigurator, AuthClientOptions } from './configurator';
|
|
4
3
|
export declare type AccountInfo = {
|
|
5
4
|
homeAccountId: string;
|
|
@@ -10,26 +9,27 @@ export declare type AccountInfo = {
|
|
|
10
9
|
name?: string;
|
|
11
10
|
};
|
|
12
11
|
export interface IAuthProvider {
|
|
13
|
-
readonly
|
|
12
|
+
readonly defaultClient: AuthClient;
|
|
14
13
|
readonly defaultConfig: AuthClientOptions | undefined;
|
|
15
|
-
readonly
|
|
14
|
+
readonly defaultAccount: AccountInfo | undefined;
|
|
15
|
+
getClient(name: string): AuthClient;
|
|
16
16
|
createClient(name?: string): AuthClient;
|
|
17
|
-
acquireToken(req: AuthRequest):
|
|
18
|
-
accessToken: string;
|
|
19
|
-
} | void>;
|
|
17
|
+
acquireToken(req: AuthRequest): ReturnType<AuthClient['acquireToken']>;
|
|
20
18
|
acquireAccessToken(req: AuthRequest): Promise<string | undefined>;
|
|
21
19
|
login(): Promise<void>;
|
|
20
|
+
handleRedirect(): ReturnType<AuthClient['handleRedirectPromise']>;
|
|
22
21
|
}
|
|
23
22
|
export declare class AuthProvider {
|
|
24
23
|
protected _config: IAuthConfigurator;
|
|
25
|
-
|
|
26
|
-
get
|
|
24
|
+
protected _clients: Record<string, AuthClient>;
|
|
25
|
+
get defaultClient(): AuthClient;
|
|
26
|
+
get defaultAccount(): AccountInfo | undefined;
|
|
27
27
|
get defaultConfig(): AuthClientOptions | undefined;
|
|
28
28
|
constructor(_config: IAuthConfigurator);
|
|
29
|
+
getClient(name: string): AuthClient;
|
|
29
30
|
createClient(name?: string): AuthClient;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} | void>;
|
|
31
|
+
handleRedirect(): ReturnType<AuthClient['handleRedirectPromise']>;
|
|
32
|
+
acquireToken(req: AuthRequest): ReturnType<AuthClient['acquireToken']>;
|
|
33
33
|
acquireAccessToken(req: AuthRequest): Promise<string | undefined>;
|
|
34
34
|
login(): Promise<void>;
|
|
35
35
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-module-msal",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/esm/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
"directory": "packages/module-msal"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@
|
|
25
|
-
"@equinor/fusion-framework-module
|
|
26
|
-
"@equinor/fusion-
|
|
24
|
+
"@azure/msal-browser": "^2.21.0",
|
|
25
|
+
"@equinor/fusion-framework-module": "^0.1.1",
|
|
26
|
+
"@equinor/fusion-framework-module-http": "^0.1.1"
|
|
27
27
|
},
|
|
28
28
|
"types": "index.d.ts",
|
|
29
29
|
"typesVersions": {
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
]
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "ca79b01fcc32c2c4e4aeea2f7c251fe9d91a2d04"
|
|
37
37
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* - **Popup:**
|
|
3
|
+
* Use when initiating the process via opening a popup window in the user's browser
|
|
4
|
+
*
|
|
5
|
+
* - **Redirect:**
|
|
6
|
+
* Use when initiating the login process by redirecting the user's browser to the authorization endpoint.
|
|
7
|
+
* This function redirects the page, so any code that follows this function will not execute.
|
|
8
|
+
*/
|
|
9
|
+
export type AuthBehavior = 'popup' | 'redirect';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default behavior for login and acquisition of token
|
|
13
|
+
*/
|
|
14
|
+
export const defaultBehavior: AuthBehavior = 'redirect';
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PublicClientApplication,
|
|
3
|
+
Configuration,
|
|
4
|
+
AuthenticationResult,
|
|
5
|
+
SsoSilentRequest,
|
|
6
|
+
PopupRequest,
|
|
7
|
+
RedirectRequest,
|
|
8
|
+
AccountInfo,
|
|
9
|
+
} from '@azure/msal-browser';
|
|
10
|
+
|
|
11
|
+
import { AuthBehavior, defaultBehavior } from './behavior';
|
|
12
|
+
import { AuthRequest } from './request';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ### Simple extension of Microsoft`s authentication client.
|
|
16
|
+
*
|
|
17
|
+
* When using this client tenant is **required** since common login is deprecated after all.
|
|
18
|
+
* By providing tenant the user account can simple be extracted from current session *if any*.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const tenantId = '224123a0d-7990-4ba1-aff3-1dss9569af32';
|
|
23
|
+
* const authPath = '/my-app/auth';
|
|
24
|
+
* const client = new AuthClient(tenantId, {
|
|
25
|
+
* auth: {
|
|
26
|
+
* clientId: '6dab35d4-59ff-4dcc-3356-24479e6fc888',
|
|
27
|
+
* authority: `https://login.microsoftonline.com/${tenantId}`,
|
|
28
|
+
* redirectUri: window.location.origin + '/my-app/auth'
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
* document.getElementById('login-btn').addEventListener('click', () =>
|
|
32
|
+
* client.login({ scopes: ['data.read'] })
|
|
33
|
+
* .then(console.log)
|
|
34
|
+
* .catch(console.error)
|
|
35
|
+
* );
|
|
36
|
+
* (async() => {
|
|
37
|
+
* if(window.location.path === authPath) {
|
|
38
|
+
* await client.handleRedirectPromise()
|
|
39
|
+
* }
|
|
40
|
+
* )();
|
|
41
|
+
* ```
|
|
42
|
+
* @see [Microsoft Authentication Library](https://github.com/AzureAD/microsoft-authentication-library-for-js)
|
|
43
|
+
* @see [Microsoft identity platform](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow)
|
|
44
|
+
*/
|
|
45
|
+
export class AuthClient extends PublicClientApplication {
|
|
46
|
+
/**
|
|
47
|
+
* @returns
|
|
48
|
+
* Returns account for client tenant that MSAL currently has data for.
|
|
49
|
+
* (the account object is created at the time of successful login)
|
|
50
|
+
*/
|
|
51
|
+
get account(): AccountInfo | undefined {
|
|
52
|
+
const accounts = this.getAllAccounts();
|
|
53
|
+
return accounts.find((a) => (a.tenantId = this.tenantId));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @returns - Configured client id
|
|
58
|
+
*/
|
|
59
|
+
get clientId(): string | undefined {
|
|
60
|
+
return this.config.auth?.clientId;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get requestOrigin(): string | null {
|
|
64
|
+
return this.browserStorage.getTemporaryCache('request.origin', true);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param tenantId - tenant id for client domain
|
|
69
|
+
* @param config - required [Configuration](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/src/config/Configuration.ts)
|
|
70
|
+
*/
|
|
71
|
+
constructor(readonly tenantId: string, config: Configuration) {
|
|
72
|
+
super(config);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param silent
|
|
77
|
+
* Attempt to use a hidden iframe to fetch an authorization code from the eSTS if {@link AuthClient.account} or login hint.
|
|
78
|
+
* Provided {@link AuthBehavior} is used as fallback.
|
|
79
|
+
* There are cases where this may not work:
|
|
80
|
+
* - Any browser using a form of Intelligent Tracking Prevention
|
|
81
|
+
* - If there is not an established session with the service
|
|
82
|
+
*
|
|
83
|
+
* @returns
|
|
84
|
+
* Promise that is fulfilled when this function has completed, or rejected if an error was raised.
|
|
85
|
+
*/
|
|
86
|
+
async login(
|
|
87
|
+
options?: AuthRequest,
|
|
88
|
+
behavior: AuthBehavior = defaultBehavior,
|
|
89
|
+
silent = true
|
|
90
|
+
): Promise<AuthenticationResult | void> {
|
|
91
|
+
const loginHint = options?.loginHint || this.account?.username;
|
|
92
|
+
const scopes = options?.scopes || [];
|
|
93
|
+
const request = { ...options, loginHint, scopes };
|
|
94
|
+
|
|
95
|
+
if (loginHint && silent) {
|
|
96
|
+
this.logger.verbose('Attempting to login in silently');
|
|
97
|
+
try {
|
|
98
|
+
return this.ssoSilent(request as SsoSilentRequest);
|
|
99
|
+
} catch {
|
|
100
|
+
this.logger.verbose('Silent login attempt failed');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.logger.verbose(`Attempting to login in by [${behavior}]`);
|
|
105
|
+
|
|
106
|
+
switch (behavior) {
|
|
107
|
+
case 'popup':
|
|
108
|
+
return this.loginPopup(request as PopupRequest);
|
|
109
|
+
case 'redirect': {
|
|
110
|
+
return this.loginRedirect(request as RedirectRequest);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Will try to silently acquire an access token for a given set of scopes.
|
|
117
|
+
* Will use cached token if available, otherwise will attempt to acquire a new token from the network via refresh token.
|
|
118
|
+
*
|
|
119
|
+
* @param silent
|
|
120
|
+
* Attempt to use a hidden iframe to fetch an authorization code from the eSTS if {@link AuthClient.account} or login hint.
|
|
121
|
+
* Provided {@link AuthBehavior} is used as fallback.
|
|
122
|
+
* There are cases where this may not work:
|
|
123
|
+
* - Any browser using a form of Intelligent Tracking Prevention
|
|
124
|
+
* - If there is not an established session with the service
|
|
125
|
+
*
|
|
126
|
+
* @returns
|
|
127
|
+
* Promise that is fulfilled when this function has completed, or rejected if an error was raised.
|
|
128
|
+
*/
|
|
129
|
+
public async acquireToken(
|
|
130
|
+
options: AuthRequest = { scopes: [] },
|
|
131
|
+
behavior: AuthBehavior = defaultBehavior,
|
|
132
|
+
silent = true
|
|
133
|
+
): Promise<AuthenticationResult | void> {
|
|
134
|
+
const account = await this.account;
|
|
135
|
+
if (silent && account) {
|
|
136
|
+
this.logger.verbose('Attempting to acquire token in silently');
|
|
137
|
+
try {
|
|
138
|
+
return this.acquireTokenSilent({ account, ...options });
|
|
139
|
+
} catch (err) {
|
|
140
|
+
this.logger.info(
|
|
141
|
+
'Expected to navigate away from the current page but timeout occurred.'
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.logger.verbose(`Attempting to acquire token by [${behavior}]`);
|
|
147
|
+
|
|
148
|
+
switch (behavior) {
|
|
149
|
+
case 'popup':
|
|
150
|
+
return this.acquireTokenPopup(options);
|
|
151
|
+
case 'redirect': {
|
|
152
|
+
return this.acquireTokenRedirect(options);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export default AuthClient;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Configuration, IPublicClientApplication } from '@azure/msal-browser';
|
|
2
|
+
import { AuthClient } from './client';
|
|
3
|
+
import { normalizeUri } from './util/url';
|
|
4
|
+
|
|
5
|
+
export type AuthClientConfig = Configuration & {
|
|
6
|
+
auth: Partial<Configuration['auth']>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates an authentication client with basic config.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const myClient = createClient(
|
|
15
|
+
* '224123a0d-7990-4ba1-aff3-1dss9569af32',
|
|
16
|
+
* '6dab35d4-59ff-4dcc-3356-24479e6fc888',
|
|
17
|
+
* '/my-app/auth'
|
|
18
|
+
* );
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @template T - client type, default to {@link AuthClient}
|
|
22
|
+
*
|
|
23
|
+
* @param tenantId - tenant to for authentication
|
|
24
|
+
* @param clientId - client id for authentication
|
|
25
|
+
* @param redirectUri - callback url for authentication (must match exact configured url in app)
|
|
26
|
+
* @param config - optional [Configuration](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/src/config/Configuration.ts)
|
|
27
|
+
* @param ctor - optional client class
|
|
28
|
+
*/
|
|
29
|
+
export const createAuthClient = <T extends IPublicClientApplication = AuthClient>(
|
|
30
|
+
tenantId: string,
|
|
31
|
+
clientId: string,
|
|
32
|
+
redirectUri?: string,
|
|
33
|
+
config?: AuthClientConfig,
|
|
34
|
+
ctor?: new (tenantId: string, config: Configuration) => T
|
|
35
|
+
): T => {
|
|
36
|
+
const auth: Configuration['auth'] = {
|
|
37
|
+
clientId,
|
|
38
|
+
redirectUri: normalizeUri(redirectUri || ''),
|
|
39
|
+
navigateToLoginRequestUrl: false,
|
|
40
|
+
authority: `https://login.microsoftonline.com/${tenantId}`,
|
|
41
|
+
...config?.auth,
|
|
42
|
+
};
|
|
43
|
+
const cache = { cacheLocation: 'localStorage', ...config?.cache };
|
|
44
|
+
const system = config?.system;
|
|
45
|
+
return new (ctor || AuthClient)(tenantId, { auth, cache, system }) as T;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default createAuthClient;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Logger, LogLevel } from '@azure/msal-browser';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Logger functions of {@link Console}
|
|
5
|
+
*/
|
|
6
|
+
type ConsoleLevel = 'error' | 'warn' | 'info' | 'debug';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* MSAL client logger for development, production should use telemetry
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* client.setLogger(new ConsoleLogger());
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class ConsoleLogger extends Logger {
|
|
17
|
+
/**
|
|
18
|
+
* @param logLevel - 0-1-2-3 (error-warning-info-debug) if not provided all records logged
|
|
19
|
+
*/
|
|
20
|
+
constructor(logLevel?: LogLevel) {
|
|
21
|
+
super({
|
|
22
|
+
logLevel,
|
|
23
|
+
loggerCallback: (...args: [LogLevel, string, boolean]) => this.loggerCallback(...args),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** @inheritdoc */
|
|
28
|
+
protected loggerCallback(lvl: LogLevel, msg: string, _containsPii?: boolean): void {
|
|
29
|
+
console[this.getLogType(lvl)](
|
|
30
|
+
`%c FUSION::MSAL %c %s`,
|
|
31
|
+
'border: 1px solid;',
|
|
32
|
+
'border: none;',
|
|
33
|
+
msg
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Map log level to console log function type
|
|
39
|
+
*/
|
|
40
|
+
protected getLogType = (lvl: LogLevel): ConsoleLevel => {
|
|
41
|
+
switch (lvl) {
|
|
42
|
+
case LogLevel.Error:
|
|
43
|
+
return 'error';
|
|
44
|
+
case LogLevel.Warning:
|
|
45
|
+
return 'warn';
|
|
46
|
+
case LogLevel.Info:
|
|
47
|
+
return 'info';
|
|
48
|
+
case LogLevel.Verbose:
|
|
49
|
+
default:
|
|
50
|
+
return 'debug';
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default ConsoleLogger;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PopupRequest, RedirectRequest } from '@azure/msal-browser';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Request object passed by user to retrieve a Code from the
|
|
5
|
+
* server (first leg of authorization code grant flow).
|
|
6
|
+
*
|
|
7
|
+
* **scopes**\
|
|
8
|
+
* Array of scopes the application is requesting access to.
|
|
9
|
+
*
|
|
10
|
+
* **authority**\
|
|
11
|
+
* Url of the authority which the application acquires tokens from.
|
|
12
|
+
*
|
|
13
|
+
* **correlationId**\
|
|
14
|
+
* Unique GUID set per request to trace a request end-to-end for telemetry purposes.
|
|
15
|
+
*
|
|
16
|
+
* **redirectUri**\
|
|
17
|
+
* The redirect URI where authentication responses can be received by your application. It must exactly match one of the redirect URIs registered in the Azure portal.
|
|
18
|
+
*
|
|
19
|
+
* **extraScopesToConsent**\
|
|
20
|
+
* Scopes for a different resource when the user needs consent upfront.
|
|
21
|
+
*
|
|
22
|
+
* **responseMode**\
|
|
23
|
+
* Specifies the method that should be used to send the authentication result to your app. Fragment is the only valid option for msal-browser.
|
|
24
|
+
*
|
|
25
|
+
* **codeChallenge**\
|
|
26
|
+
* Used to secure authorization code grant via Proof of Key for Code Exchange (PKCE). For more information, see the PKCE RCF:https://tools.ietf.org/html/rfc7636
|
|
27
|
+
*
|
|
28
|
+
* **codeChallengeMethod**\
|
|
29
|
+
* The method used to encode the code verifier for the code challenge parameter. Can be "plain" or "S256". If excluded, code challenge is assumed to be plaintext. For more information, see the PKCE RCF: https://tools.ietf.org/html/rfc7636
|
|
30
|
+
*
|
|
31
|
+
* **state**\
|
|
32
|
+
* A value included in the request that is also returned in the token response. A randomly generated unique value is typically used for preventing cross site request forgery attacks. The state is also used to encode information about the user's state in the app before the authentication request occurred.
|
|
33
|
+
*
|
|
34
|
+
* **prompt**\
|
|
35
|
+
* Indicates the type of user interaction that is required.
|
|
36
|
+
* - login: will force the user to enter their credentials on that request, negating single-sign on
|
|
37
|
+
* - none: will ensure that the user isn't presented with any interactive prompt. if request can't be completed via single-sign on, the endpoint will return an interaction_required error
|
|
38
|
+
* - consent: will the trigger the OAuth consent dialog after the user signs in, asking the user to grant permissions to the app
|
|
39
|
+
* - select_account: will interrupt single sign-=on providing account selection experience listing all the accounts in session or any remembered accounts or an option to choose to use a different account
|
|
40
|
+
*
|
|
41
|
+
* **loginHint**\
|
|
42
|
+
* Can be used to pre-fill the username/email address field of the sign-in page for the user, if you know the username/email address ahead of time. Often apps use this parameter during re-authentication, having already extracted the username from a previous sign-in using the preferred_username claim.
|
|
43
|
+
*
|
|
44
|
+
* **sid**\
|
|
45
|
+
* Session ID, unique identifier for the session. Available as an optional claim on ID tokens.
|
|
46
|
+
*
|
|
47
|
+
* **domainHint**\
|
|
48
|
+
* Provides a hint about the tenant or domain that the user should use to sign in. The value of the domain hint is a registered domain for the tenant.
|
|
49
|
+
*
|
|
50
|
+
* **extraQueryParameters**\
|
|
51
|
+
* String to string map of custom query parameters.
|
|
52
|
+
*
|
|
53
|
+
* **claims**\
|
|
54
|
+
* In cases where Azure AD tenant admin has enabled conditional access policies, and the policy has not been met, exceptions will contain claims that need to be consented to.
|
|
55
|
+
*
|
|
56
|
+
* **nonce**\
|
|
57
|
+
* A value included in the request that is returned in the id token. A randomly generated unique value is typically used to mitigate replay attacks.
|
|
58
|
+
*
|
|
59
|
+
* **redirectStartPage**\
|
|
60
|
+
* The page that should be returned to after loginRedirect or acquireTokenRedirect.
|
|
61
|
+
* This should only be used if this is different from the redirectUri and will default to the page that initiates the request.
|
|
62
|
+
* When the navigateToLoginRequestUrl config option is set to false this parameter will be ignored.
|
|
63
|
+
*
|
|
64
|
+
* @see [microsoft-authentication-library-for-js](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser/src/request)
|
|
65
|
+
*/
|
|
66
|
+
export type AuthRequest = PopupRequest | RedirectRequest;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redirects browser to provided location
|
|
3
|
+
* If not redirected by provided timeout, promise is rejected
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*
|
|
7
|
+
* @param url - endpoint to navigate to
|
|
8
|
+
* @param timeout - max wait before redirect
|
|
9
|
+
* @param history - append navigation to history
|
|
10
|
+
*/
|
|
11
|
+
export const redirect = (url: string, timeout = 3000, history?: boolean): Promise<void> => {
|
|
12
|
+
history ? window.location.assign(url) : window.location.replace(url);
|
|
13
|
+
return new Promise((_, reject) => setTimeout(reject, timeout));
|
|
14
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates and normalizes redirect uri.
|
|
3
|
+
* Strips double and trailing slashes
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*
|
|
7
|
+
* @param uri - relative path or full url
|
|
8
|
+
* @param home - base url for relative urls
|
|
9
|
+
*/
|
|
10
|
+
export const normalizeUri = (uri: string, home: string = window.location.origin): string => {
|
|
11
|
+
uri = uri.match(/^http[s]?/) ? uri : home + uri;
|
|
12
|
+
const { origin, pathname } = new URL(uri);
|
|
13
|
+
return origin + pathname.replace(/((?<=[/])[/]+)|\/$/, '');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compares normalized version of urls
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export const compareOrigin = (a: string, b: string): boolean => {
|
|
22
|
+
const url = { a: normalizeUri(a), b: normalizeUri(b) };
|
|
23
|
+
return url.a === url.b;
|
|
24
|
+
};
|
package/src/configurator.ts
CHANGED
package/src/index.ts
CHANGED
package/src/module.ts
CHANGED
|
@@ -27,7 +27,9 @@ export const module: MsalModule = {
|
|
|
27
27
|
const { scopes = [] } = request;
|
|
28
28
|
if (scopes.length) {
|
|
29
29
|
/** TODO should be try catch, check caller for handling */
|
|
30
|
-
const token = await modules.auth.acquireToken({
|
|
30
|
+
const token = await modules.auth.acquireToken({
|
|
31
|
+
scopes,
|
|
32
|
+
});
|
|
31
33
|
if (token) {
|
|
32
34
|
const headers = new Headers(request.headers);
|
|
33
35
|
headers.set('Authorization', `Bearer ${token.accessToken}`);
|
package/src/provider.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { AuthClient, createAuthClient } from '
|
|
2
|
-
|
|
3
|
-
// TODO
|
|
4
|
-
import { AuthRequest } from '@equinor/fusion-web-msal/dist/request';
|
|
1
|
+
import { AuthClient, createAuthClient, AuthRequest, ConsoleLogger } from './client';
|
|
5
2
|
|
|
6
3
|
import { IAuthConfigurator, AuthClientOptions } from './configurator';
|
|
7
4
|
|
|
@@ -16,22 +13,45 @@ export declare type AccountInfo = {
|
|
|
16
13
|
};
|
|
17
14
|
|
|
18
15
|
export interface IAuthProvider {
|
|
19
|
-
readonly
|
|
16
|
+
readonly defaultClient: AuthClient;
|
|
20
17
|
readonly defaultConfig: AuthClientOptions | undefined;
|
|
21
|
-
readonly
|
|
18
|
+
readonly defaultAccount: AccountInfo | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Get auth client by registered config name
|
|
21
|
+
*/
|
|
22
|
+
getClient(name: string): AuthClient;
|
|
23
|
+
/**
|
|
24
|
+
* Create auth client by registered config name
|
|
25
|
+
*/
|
|
22
26
|
createClient(name?: string): AuthClient;
|
|
23
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Acquire token from default auth client
|
|
29
|
+
*/
|
|
30
|
+
acquireToken(req: AuthRequest): ReturnType<AuthClient['acquireToken']>;
|
|
31
|
+
/**
|
|
32
|
+
* Acquire access token from default auth client
|
|
33
|
+
*/
|
|
24
34
|
acquireAccessToken(req: AuthRequest): Promise<string | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* Login to default auth client
|
|
37
|
+
*/
|
|
25
38
|
login(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Handle default client redirect callback
|
|
41
|
+
*/
|
|
42
|
+
handleRedirect(): ReturnType<AuthClient['handleRedirectPromise']>;
|
|
26
43
|
}
|
|
27
44
|
|
|
45
|
+
const DEFAULT_CLIENT_NAME = 'default';
|
|
46
|
+
|
|
28
47
|
export class AuthProvider {
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
protected _clients: Record<string, AuthClient> = {};
|
|
49
|
+
get defaultClient(): AuthClient {
|
|
50
|
+
return this.getClient(DEFAULT_CLIENT_NAME);
|
|
31
51
|
}
|
|
32
52
|
|
|
33
|
-
get
|
|
34
|
-
return this.
|
|
53
|
+
get defaultAccount(): AccountInfo | undefined {
|
|
54
|
+
return this.defaultClient.account;
|
|
35
55
|
}
|
|
36
56
|
|
|
37
57
|
get defaultConfig(): AuthClientOptions | undefined {
|
|
@@ -40,21 +60,42 @@ export class AuthProvider {
|
|
|
40
60
|
|
|
41
61
|
constructor(protected _config: IAuthConfigurator) {}
|
|
42
62
|
|
|
63
|
+
getClient(name: string): AuthClient {
|
|
64
|
+
if (!this._clients[name]) {
|
|
65
|
+
this._clients[name] = this.createClient(name);
|
|
66
|
+
}
|
|
67
|
+
return this._clients[name];
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
createClient(name?: string): AuthClient {
|
|
44
71
|
const config = name ? this._config.getClientConfig(name) : this._config.defaultConfig;
|
|
45
72
|
if (!config) {
|
|
46
73
|
throw Error('Could not find any config');
|
|
47
74
|
}
|
|
48
|
-
|
|
75
|
+
const client = createAuthClient(
|
|
49
76
|
config.tenantId,
|
|
50
77
|
config.clientId,
|
|
51
78
|
config.redirectUri,
|
|
52
79
|
config.config
|
|
53
80
|
);
|
|
81
|
+
// TODO - fix with log streamer
|
|
82
|
+
client.setLogger(new ConsoleLogger(3));
|
|
83
|
+
|
|
84
|
+
return client;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async handleRedirect(): ReturnType<AuthClient['handleRedirectPromise']> {
|
|
88
|
+
const { redirectUri } = this.defaultConfig || {};
|
|
89
|
+
if (window.location.pathname === redirectUri) {
|
|
90
|
+
const url = this.defaultClient.requestOrigin || '';
|
|
91
|
+
await this.defaultClient.handleRedirectPromise();
|
|
92
|
+
window.location.replace(url);
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
54
95
|
}
|
|
55
96
|
|
|
56
|
-
acquireToken(req: AuthRequest):
|
|
57
|
-
return this.
|
|
97
|
+
acquireToken(req: AuthRequest): ReturnType<AuthClient['acquireToken']> {
|
|
98
|
+
return this.defaultClient.acquireToken(req);
|
|
58
99
|
}
|
|
59
100
|
|
|
60
101
|
async acquireAccessToken(req: AuthRequest): Promise<string | undefined> {
|
|
@@ -63,6 +104,6 @@ export class AuthProvider {
|
|
|
63
104
|
}
|
|
64
105
|
|
|
65
106
|
async login(): Promise<void> {
|
|
66
|
-
await this.
|
|
107
|
+
await this.defaultClient.login();
|
|
67
108
|
}
|
|
68
109
|
}
|