@equinor/fusion-framework-react-app 7.0.1 → 8.0.0-next.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 +41 -0
- package/README.md +33 -19
- package/dist/esm/msal/useCurrentAccount.js +1 -1
- package/dist/esm/msal/useCurrentAccount.js.map +1 -1
- package/dist/esm/msal/useToken.js +6 -3
- package/dist/esm/msal/useToken.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/docs/msal.md +291 -0
- package/package.json +16 -16
- package/src/msal/useCurrentAccount.ts +1 -1
- package/src/msal/useToken.ts +6 -3
- package/src/version.ts +1 -1
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "
|
|
1
|
+
export declare const version = "8.0.0-next.1";
|
package/docs/msal.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# MSAL Authentication
|
|
2
|
+
|
|
3
|
+
This package includes React hooks for Microsoft authentication using MSAL v4.
|
|
4
|
+
|
|
5
|
+
> [!CAUTION]
|
|
6
|
+
> **Applications should NOT configure the MSAL module themselves.**
|
|
7
|
+
>
|
|
8
|
+
> The MSAL module **must be configured by the host/portal application**, not by individual apps. This is required for module hoisting, which allows sharing authentication state across all applications in a portal.
|
|
9
|
+
>
|
|
10
|
+
> - ✅ **Host/Portal:** Configure MSAL using `enableMSAL()` in the portal's configuration
|
|
11
|
+
> - ❌ **App:** Do NOT call `enableMSAL()` or configure the auth module
|
|
12
|
+
> - ✅ **App:** Just use the hooks to access the already-configured MSAL module
|
|
13
|
+
|
|
14
|
+
> [!IMPORTANT]
|
|
15
|
+
> `@equinor/fusion-framework-module-msal` must be installed to make MSAL hooks available
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
The MSAL authentication hooks provide a simple way to integrate Microsoft authentication into your React applications. These hooks are built on top of `@equinor/fusion-framework-module-msal` and provide React-friendly access to authentication state and token acquisition.
|
|
20
|
+
|
|
21
|
+
Since the MSAL module is configured by your host application, you don't need to worry about configuration—just import and use the hooks!
|
|
22
|
+
|
|
23
|
+
The hooks use a simplified API that internally handles the MSAL v4 format, so you can use the simple `{ scopes: string[] }` format without worrying about the v4 nested request structure.
|
|
24
|
+
|
|
25
|
+
## Available Hooks
|
|
26
|
+
|
|
27
|
+
### useCurrentAccount
|
|
28
|
+
|
|
29
|
+
Returns the current authenticated user account information.
|
|
30
|
+
|
|
31
|
+
**Signature:**
|
|
32
|
+
```typescript
|
|
33
|
+
useCurrentAccount(): AccountInfo | undefined
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Example:**
|
|
37
|
+
```tsx
|
|
38
|
+
import { useCurrentAccount } from '@equinor/fusion-framework-react-app/msal';
|
|
39
|
+
|
|
40
|
+
const UserProfile = () => {
|
|
41
|
+
const account = useCurrentAccount();
|
|
42
|
+
|
|
43
|
+
if (!account) {
|
|
44
|
+
return <span>Not authenticated</span>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div>
|
|
49
|
+
<p>Username: {account.username}</p>
|
|
50
|
+
<p>Name: {account.name}</p>
|
|
51
|
+
<p>Tenant ID: {account.tenantId}</p>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Returns:** `AccountInfo | undefined`
|
|
58
|
+
|
|
59
|
+
### useAccessToken
|
|
60
|
+
|
|
61
|
+
Returns just the access token string for making authenticated API calls.
|
|
62
|
+
|
|
63
|
+
**Signature:**
|
|
64
|
+
```typescript
|
|
65
|
+
useAccessToken(req: { scopes: string[] }): { token?: string; pending: boolean; error: unknown }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Example:**
|
|
69
|
+
```tsx
|
|
70
|
+
import { useAccessToken } from '@equinor/fusion-framework-react-app/msal';
|
|
71
|
+
import { useMemo } from 'react';
|
|
72
|
+
|
|
73
|
+
const ProtectedComponent = () => {
|
|
74
|
+
const { token, pending, error } = useAccessToken(
|
|
75
|
+
useMemo(() => ({ scopes: ['User.Read'] }), [])
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (pending) return <span>Loading token...</span>;
|
|
79
|
+
if (error) return <span>Error: {String(error)}</span>;
|
|
80
|
+
|
|
81
|
+
return token ? <span>Token acquired</span> : <span>No token</span>;
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Parameters:**
|
|
86
|
+
- `req`: Object with `scopes` property - Array of OAuth scopes to request
|
|
87
|
+
|
|
88
|
+
**Returns:**
|
|
89
|
+
- `token?: string` - The access token string if available
|
|
90
|
+
- `pending: boolean` - Whether the token request is in progress
|
|
91
|
+
- `error: unknown` - Any error that occurred during token acquisition
|
|
92
|
+
|
|
93
|
+
### useToken
|
|
94
|
+
|
|
95
|
+
Returns the full authentication result object with complete token information.
|
|
96
|
+
|
|
97
|
+
**Signature:**
|
|
98
|
+
```typescript
|
|
99
|
+
useToken(req: { scopes: string[] }): { token?: AuthenticationResult; pending: boolean; error: unknown }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Example:**
|
|
103
|
+
```tsx
|
|
104
|
+
import { useToken } from '@equinor/fusion-framework-react-app/msal';
|
|
105
|
+
import { useMemo } from 'react';
|
|
106
|
+
|
|
107
|
+
const TokenInfo = () => {
|
|
108
|
+
const { token, pending, error } = useToken(
|
|
109
|
+
useMemo(() => ({ scopes: ['User.Read', 'api.read'] }), [])
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (pending) return <span>Loading...</span>;
|
|
113
|
+
if (error) return <span>Error: {String(error)}</span>;
|
|
114
|
+
if (!token) return <span>No token</span>;
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div>
|
|
118
|
+
<p>Access Token: {token.accessToken.substring(0, 20)}...</p>
|
|
119
|
+
<p>Expires: {new Date(token.expiresOn).toLocaleString()}</p>
|
|
120
|
+
<p>Token Type: {token.tokenType}</p>
|
|
121
|
+
<p>Scope: {token.scopes.join(', ')}</p>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Parameters:**
|
|
128
|
+
- `req`: Object with `scopes` property - Array of OAuth scopes to request
|
|
129
|
+
|
|
130
|
+
**Returns:**
|
|
131
|
+
- `token?: AuthenticationResult` - Full authentication result containing accessToken, account info, expiresOn, etc.
|
|
132
|
+
- `pending: boolean` - Whether the token request is in progress
|
|
133
|
+
- `error: unknown` - Any error that occurred during token acquisition
|
|
134
|
+
|
|
135
|
+
**Note:** The `AuthenticationResult` type includes:
|
|
136
|
+
- `accessToken: string`
|
|
137
|
+
- `account: AccountInfo`
|
|
138
|
+
- `expiresOn: Date`
|
|
139
|
+
- `tokenType: string`
|
|
140
|
+
- `scopes: string[]`
|
|
141
|
+
- And more...
|
|
142
|
+
|
|
143
|
+
## How It Works
|
|
144
|
+
|
|
145
|
+
Internally, these hooks call the MSAL provider's methods which support both legacy and modern formats:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// The hook receives simple format:
|
|
149
|
+
useToken({ scopes: ['User.Read'] })
|
|
150
|
+
|
|
151
|
+
// Internally converts to MSAL v4 format:
|
|
152
|
+
msalProvider.acquireToken({ request: { scopes: ['User.Read'] } })
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This means you can use the simpler API without dealing with the v4 nested structure, while still benefiting from MSAL v4 features under the hood.
|
|
156
|
+
|
|
157
|
+
## Complete Example
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { useCurrentAccount, useAccessToken } from '@equinor/fusion-framework-react-app/msal';
|
|
161
|
+
import { useMemo, useState } from 'react';
|
|
162
|
+
|
|
163
|
+
const App = () => {
|
|
164
|
+
const account = useCurrentAccount();
|
|
165
|
+
|
|
166
|
+
// Get scopes from somewhere (e.g., service discovery)
|
|
167
|
+
const [scopes] = useState(['User.Read', 'api.read']);
|
|
168
|
+
|
|
169
|
+
const { token, pending, error } = useAccessToken(
|
|
170
|
+
useMemo(() => ({ scopes }), [scopes])
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<div>
|
|
175
|
+
{!account && <p>Please log in</p>}
|
|
176
|
+
|
|
177
|
+
{account && (
|
|
178
|
+
<>
|
|
179
|
+
<h1>Welcome, {account.name}!</h1>
|
|
180
|
+
<p>Username: {account.username}</p>
|
|
181
|
+
|
|
182
|
+
{pending && <p>Loading token...</p>}
|
|
183
|
+
{error && <p>Error: {String(error)}</p>}
|
|
184
|
+
{token && (
|
|
185
|
+
<div>
|
|
186
|
+
<p>Token acquired successfully</p>
|
|
187
|
+
<pre>{token.substring(0, 50)}...</pre>
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
</>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Advanced Usage
|
|
198
|
+
|
|
199
|
+
### Dynamic Scopes
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
const Component = () => {
|
|
203
|
+
const [scopes, setScopes] = useState(['User.Read']);
|
|
204
|
+
|
|
205
|
+
const { token, pending } = useAccessToken(
|
|
206
|
+
useMemo(() => ({ scopes }), [scopes])
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<div>
|
|
211
|
+
<button onClick={() => setScopes(['User.Read', 'api.write'])}>
|
|
212
|
+
Request More Permissions
|
|
213
|
+
</button>
|
|
214
|
+
{token && <p>Token ready</p>}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Error Handling
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
const Component = () => {
|
|
224
|
+
const { token, error, pending } = useAccessToken(
|
|
225
|
+
useMemo(() => ({ scopes: ['User.Read'] }), [])
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (error) {
|
|
230
|
+
console.error('Token acquisition failed:', error);
|
|
231
|
+
// Handle error appropriately
|
|
232
|
+
}
|
|
233
|
+
}, [error]);
|
|
234
|
+
|
|
235
|
+
return pending ? <Loading /> : token ? <Content /> : <Error />;
|
|
236
|
+
};
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Performance Considerations
|
|
240
|
+
|
|
241
|
+
1. **Memoize request objects**: Always wrap the scopes object in `useMemo` to prevent unnecessary re-renders
|
|
242
|
+
```tsx
|
|
243
|
+
// Bad - creates new object on every render
|
|
244
|
+
const { token } = useAccessToken({ scopes: ['User.Read'] });
|
|
245
|
+
|
|
246
|
+
// Good - stable reference
|
|
247
|
+
const { token } = useAccessToken(
|
|
248
|
+
useMemo(() => ({ scopes: ['User.Read'] }), [])
|
|
249
|
+
);
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
2. **Dependent scopes**: Include scopes in the dependency array
|
|
253
|
+
```tsx
|
|
254
|
+
const { token } = useAccessToken(
|
|
255
|
+
useMemo(() => ({ scopes }), [scopes])
|
|
256
|
+
);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## MSAL v4 Under the Hood
|
|
260
|
+
|
|
261
|
+
While the hooks use a simple API, they internally use MSAL v4:
|
|
262
|
+
|
|
263
|
+
- Hooks accept `{ scopes: string[] }` for simplicity
|
|
264
|
+
- Internally converts to `{ request: { scopes: string[] } }` format
|
|
265
|
+
- The provider supports both legacy and modern formats
|
|
266
|
+
- All benefits of MSAL v4 are available (better security, performance, etc.)
|
|
267
|
+
|
|
268
|
+
## Troubleshooting
|
|
269
|
+
|
|
270
|
+
### Hook Returns No Account
|
|
271
|
+
|
|
272
|
+
- Ensure user is logged in
|
|
273
|
+
- Check that MSAL module is properly configured
|
|
274
|
+
- Verify `useCurrentAccount()` hook is wrapped in a component within the framework provider
|
|
275
|
+
|
|
276
|
+
### Token Acquisition Fails
|
|
277
|
+
|
|
278
|
+
- Verify scopes are properly configured in Azure AD
|
|
279
|
+
- Check user has necessary permissions for requested scopes
|
|
280
|
+
- Review browser console for MSAL errors
|
|
281
|
+
|
|
282
|
+
### Performance Issues
|
|
283
|
+
|
|
284
|
+
- Ensure scopes object is memoized with `useMemo`
|
|
285
|
+
- Check for unnecessary re-renders of parent components
|
|
286
|
+
|
|
287
|
+
## Related Documentation
|
|
288
|
+
|
|
289
|
+
- [MSAL Module Documentation](https://equinor.github.io/fusion-framework/modules/auth/msal/) - Core MSAL provider
|
|
290
|
+
- [Microsoft MSAL Browser Docs](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser) - Official MSAL documentation
|
|
291
|
+
- [Azure AD App Registration Guide](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) - App registration guide
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-react-app",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0-next.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/esm/index.js",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -106,13 +106,13 @@
|
|
|
106
106
|
"directory": "packages/react"
|
|
107
107
|
},
|
|
108
108
|
"dependencies": {
|
|
109
|
-
"@equinor/fusion-framework-app": "^10.1.1",
|
|
110
|
-
"@equinor/fusion-framework-module": "^
|
|
111
|
-
"@equinor/fusion-framework-
|
|
112
|
-
"@equinor/fusion-framework-module
|
|
113
|
-
"@equinor/fusion-framework-
|
|
114
|
-
"@equinor/fusion-framework-react-module": "^3.1.
|
|
115
|
-
"@equinor/fusion-framework-react-module-http": "^10.0.
|
|
109
|
+
"@equinor/fusion-framework-app": "^10.1.2-next.1",
|
|
110
|
+
"@equinor/fusion-framework-module-app": "^7.0.4-next.0",
|
|
111
|
+
"@equinor/fusion-framework-react": "^7.4.19-next.1",
|
|
112
|
+
"@equinor/fusion-framework-module": "^5.0.6-next.0",
|
|
113
|
+
"@equinor/fusion-framework-module-navigation": "^6.0.1-next.0",
|
|
114
|
+
"@equinor/fusion-framework-react-module": "^3.1.14-next.0",
|
|
115
|
+
"@equinor/fusion-framework-react-module-http": "^10.0.1-next.1"
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@types/react": "^18.2.50",
|
|
@@ -121,20 +121,20 @@
|
|
|
121
121
|
"react-dom": "^18.2.0",
|
|
122
122
|
"rxjs": "^7.8.1",
|
|
123
123
|
"typescript": "^5.8.2",
|
|
124
|
-
"@equinor/fusion-framework-module-ag-grid": "^34.2.
|
|
125
|
-
"@equinor/fusion-framework-module-event": "^4.4.0",
|
|
126
|
-
"@equinor/fusion-framework-module-feature-flag": "^1.1.
|
|
127
|
-
"@equinor/fusion-framework-module-msal": "^
|
|
128
|
-
"@equinor/fusion-framework-react-module-bookmark": "^5.0.1",
|
|
129
|
-
"@equinor/fusion-framework-react-module-context": "^6.2.
|
|
130
|
-
"@equinor/fusion-observable": "^8.5.
|
|
124
|
+
"@equinor/fusion-framework-module-ag-grid": "^34.2.3-next.0",
|
|
125
|
+
"@equinor/fusion-framework-module-event": "^4.4.1-next.0",
|
|
126
|
+
"@equinor/fusion-framework-module-feature-flag": "^1.1.27-next.0",
|
|
127
|
+
"@equinor/fusion-framework-module-msal": "^6.0.0-next.1",
|
|
128
|
+
"@equinor/fusion-framework-react-module-bookmark": "^5.0.2-next.1",
|
|
129
|
+
"@equinor/fusion-framework-react-module-context": "^6.2.34-next.0",
|
|
130
|
+
"@equinor/fusion-observable": "^8.5.7-next.0"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
133
|
"@types/react": "^17.0.0 || ^18.0.0",
|
|
134
134
|
"react": "^17.0.0 || ^18.0.0",
|
|
135
135
|
"react-dom": "^17.0.0 || ^18.0.0",
|
|
136
136
|
"rxjs": "^7.8.1",
|
|
137
|
-
"@equinor/fusion-framework-module-msal": "^
|
|
137
|
+
"@equinor/fusion-framework-module-msal": "^6.0.0-next.1"
|
|
138
138
|
},
|
|
139
139
|
"peerDependenciesMeta": {
|
|
140
140
|
"@equinor/fusion-framework-react-module-ag-grid": {
|
package/src/msal/useToken.ts
CHANGED
|
@@ -20,9 +20,12 @@ export const useToken = (req: {
|
|
|
20
20
|
setPending(true);
|
|
21
21
|
setToken(undefined);
|
|
22
22
|
msalProvider
|
|
23
|
-
.acquireToken(req)
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
.acquireToken({ request: req })
|
|
24
|
+
.then((result) => {
|
|
25
|
+
if (result) {
|
|
26
|
+
setToken(result);
|
|
27
|
+
}
|
|
28
|
+
})
|
|
26
29
|
.catch(setError)
|
|
27
30
|
.finally(() => setPending(false));
|
|
28
31
|
}, [msalProvider, req]);
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '
|
|
2
|
+
export const version = '8.0.0-next.1';
|