@axa-fr/react-oidc 7.29.3 → 7.29.5
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/README.md +268 -643
- package/dist/FetchToken.d.ts +1 -1
- package/dist/FetchToken.d.ts.map +1 -1
- package/dist/OidcSecure.d.ts +1 -1
- package/dist/OidcSecure.d.ts.map +1 -1
- package/dist/ReactOidc.d.ts.map +1 -1
- package/dist/core/default-component/SilentLogin.component.d.ts.map +1 -1
- package/dist/core/routes/OidcRoutes.d.ts.map +1 -1
- package/dist/index.js +164 -169
- package/dist/index.umd.cjs +1 -1
- package/package.json +18 -18
- package/src/FetchToken.spec.tsx +135 -0
- package/src/FetchToken.tsx +4 -17
- package/src/OidcSecure.spec.tsx +84 -0
- package/src/ReactOidc.spec.tsx +222 -0
- package/src/ReactOidc.tsx +12 -22
- package/src/core/default-component/SilentLogin.component.spec.tsx +71 -0
- package/src/core/default-component/SilentLogin.component.tsx +1 -3
- package/src/core/routes/OidcRoutes.behavior.spec.tsx +123 -0
- package/src/core/routes/OidcRoutes.tsx +15 -21
package/README.md
CHANGED
|
@@ -1,749 +1,374 @@
|
|
|
1
1
|
# @axa-fr/react-oidc
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://www.npmjs.com/package/@axa-fr/react-oidc)
|
|
3
|
+
React components and hooks for OpenID Connect (OIDC), built on [`@axa-fr/oidc-client`](../oidc-client/README.md). The provider handles browser authentication callbacks, session restoration, and token renewal; your components use hooks to sign in, read user information, and call protected APIs.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
**@axa-fr/oidc-client** is a pure javascript library. It works with any JavaScript framework or library.
|
|
5
|
+
The library supports Authorization Code Flow with PKCE, multiple named configurations, optional service-worker token isolation, PAR, and DPoP. It is a browser-side authentication library, not a server-side session or authorization system.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
- [Quick start](#quick-start)
|
|
8
|
+
- [Protect components](#protect-components)
|
|
9
|
+
- [Call protected APIs](#call-protected-apis)
|
|
10
|
+
- [User information and token hooks](#user-information-and-token-hooks)
|
|
11
|
+
- [Service worker](#service-worker)
|
|
12
|
+
- [Configuration, renewal, PAR, and DPoP](#configuration-renewal-par-and-dpop)
|
|
13
|
+
- [Custom components and provider options](#custom-components-and-provider-options)
|
|
14
|
+
- [Routing and Next.js](#routing-and-nextjs)
|
|
15
|
+
- [Named configurations](#named-configurations)
|
|
16
|
+
- [Errors and missing providers](#errors-and-missing-providers)
|
|
17
|
+
- [Examples and further reading](#examples-and-further-reading)
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
- Try the pure javascript demo at https://icy-glacier-004ab4303.2.azurestaticapps.net/
|
|
19
|
+
<a id="getting-started"></a>
|
|
13
20
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- [About](#about)
|
|
17
|
-
- [Getting Started](#getting-started)
|
|
18
|
-
- [Run The Demo](#run-the-demo)
|
|
19
|
-
- [Examples](#examples)
|
|
20
|
-
- [How It Works](#how-it-works)
|
|
21
|
-
- [NextJS](#NextJS)
|
|
22
|
-
- [Hash route](#Hash-route)
|
|
23
|
-
- [Service Worker Support](#service-worker-support)
|
|
24
|
-
|
|
25
|
-
## About
|
|
26
|
-
|
|
27
|
-
@axa-fr/react is:
|
|
28
|
-
|
|
29
|
-
- **Secure** :
|
|
30
|
-
- With Demonstrating Proof of Possession (DPoP), your access_token and refresh_token are not usable outside your browser context (big protection)
|
|
31
|
-
- With the use of Service Worker, your tokens (refresh_token and/or access_token) are not accessible to the JavaScript client code (if you follow good practices from [`FAQ`](https://github.com/AxaFrance/oidc-client/blob/main/FAQ.md) section)
|
|
32
|
-
- OIDC using client side Code Credential Grant with pkce only
|
|
33
|
-
- **Lightweight** : Unpacked Size on npm is **274 kB**
|
|
34
|
-
- **Simple**
|
|
35
|
-
- refresh_token and access_token are auto refreshed in background
|
|
36
|
-
- with the use of the Service Worker, you do not need to inject the access_token in every fetch, you have only to configure OidcTrustedDomains.js file
|
|
37
|
-
- **Multiple Authentication** :
|
|
38
|
-
- You can authenticate many times to the same provider with different scope (for example you can acquire a new 'payment' scope for a payment)
|
|
39
|
-
- You can authenticate to multiple different providers inside the same SPA (single page application) website
|
|
40
|
-
- **Flexible** :
|
|
41
|
-
- Work with Service Worker (more secure) and without for older browser (less secure).
|
|
42
|
-
- You can disable Service Worker if you want (but less secure) and just use SessionStorage or LocalStorage mode.
|
|
43
|
-
|
|
44
|
-

|
|
45
|
-
|
|
46
|
-
The service worker catch **access_token** and **refresh_token** that will never be accessible to the client.
|
|
47
|
-
|
|
48
|
-
## Getting Started
|
|
49
|
-
|
|
50
|
-
```sh
|
|
51
|
-
npm install @axa-fr/react-oidc --save
|
|
52
|
-
|
|
53
|
-
# To install or update OidcServiceWorker.js file, you can run
|
|
54
|
-
node ./node_modules/@axa-fr/react-oidc/bin/copy-service-worker-files.mjs public
|
|
55
|
-
|
|
56
|
-
# If you have a "public" folder, the 2 files will be created :
|
|
57
|
-
# ./public/OidcServiceWorker.js <-- will be updated at each "npm install"
|
|
58
|
-
# ./public/OidcTrustedDomains.js <-- won't be updated if already exist
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
WARNING : If you use Service Worker mode, the OidcServiceWorker.js file should always be up to date with the version of the library. You may setup a postinstall script in your package.json file to update it at each npm install. For example :
|
|
21
|
+
## Quick start
|
|
62
22
|
|
|
63
23
|
```sh
|
|
64
|
-
|
|
65
|
-
...
|
|
66
|
-
"postinstall": "node ./node_modules/@axa-fr/react-oidc/bin/copy-service-worker-files.mjs public"
|
|
67
|
-
},
|
|
24
|
+
npm install @axa-fr/react-oidc
|
|
68
25
|
```
|
|
69
26
|
|
|
70
|
-
|
|
71
|
-
The only file you should edit is "OidcTrustedDomains.js".
|
|
72
|
-
|
|
73
|
-
```javascript
|
|
74
|
-
// OidcTrustedDomains.js
|
|
27
|
+
Register a public browser client with your identity provider using Authorization Code Flow with PKCE. Register the exact callback URL and post-logout URL, and allow the application's origin through CORS. Never embed a client secret in a browser application.
|
|
75
28
|
|
|
76
|
-
|
|
77
|
-
// trusted domain can also be a path like https://www.myapi.com/users,
|
|
78
|
-
// then all subroute like https://www.myapi.com/useers/1 will be authorized to send access_token to.
|
|
29
|
+
Replace the issuer and client ID below with your own settings. This example assumes a client-rendered React application with an HTML element named `root`. Your web server must serve the application on `/authentication/callback` as well as `/`.
|
|
79
30
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
accessTokenDomains: ['https://www.myapi.com/users'],
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// Service worker will continue to give access token to the JavaScript client
|
|
89
|
-
// Ideal to hide refresh token from client JavaScript, but to retrieve access_token for some
|
|
90
|
-
// scenarios which require it. For example, to send it via websocket connection.
|
|
91
|
-
trustedDomains.config_show_access_token = {
|
|
92
|
-
oidcDomains: ['https://demo.duendesoftware.com'],
|
|
93
|
-
accessTokenDomains: ['https://www.myapi.com/users'],
|
|
94
|
-
showAccessToken: true,
|
|
95
|
-
// convertAllRequestsToCorsExceptNavigate: false, // default value is false
|
|
96
|
-
// setAccessTokenToNavigateRequests: true, // default value is true
|
|
97
|
-
// bypassAllNonOidcRequests: false, // default value is false; when true, requests outside OIDC and accessTokenDomains are handled by the browser
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// DPoP (Demonstrating Proof of Possession) will be activated for the following domains
|
|
101
|
-
trustedDomains.config_with_dpop = {
|
|
102
|
-
domains: ['https://demo.duendesoftware.com'],
|
|
103
|
-
demonstratingProofOfPossession: true,
|
|
104
|
-
demonstratingProofOfPossessionOnlyWhenDpopHeaderPresent: true, // default value is false, inject DPOP token only when DPOP header is present
|
|
105
|
-
// Optional, more details bellow
|
|
106
|
-
/*demonstratingProofOfPossessionConfiguration: {
|
|
107
|
-
importKeyAlgorithm: {
|
|
108
|
-
name: 'ECDSA',
|
|
109
|
-
namedCurve: 'P-256',
|
|
110
|
-
hash: {name: 'ES256'}
|
|
111
|
-
},
|
|
112
|
-
signAlgorithm: {name: 'ECDSA', hash: {name: 'SHA-256'}},
|
|
113
|
-
generateKeyAlgorithm: {
|
|
114
|
-
name: 'ECDSA',
|
|
115
|
-
namedCurve: 'P-256'
|
|
116
|
-
},
|
|
117
|
-
digestAlgorithm: { name: 'SHA-256' },
|
|
118
|
-
jwtHeaderAlgorithm : 'ES256'
|
|
119
|
-
}*/
|
|
120
|
-
};
|
|
31
|
+
```tsx
|
|
32
|
+
import { useState } from 'react';
|
|
33
|
+
import { createRoot } from 'react-dom/client';
|
|
34
|
+
import { OidcProvider, useOidc, type OidcConfiguration } from '@axa-fr/react-oidc';
|
|
121
35
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// Using a plain fetch or axios without the OIDC fetch wrapper will result in requests being sent
|
|
128
|
-
// without an Authorization header (401 errors), because the service worker cannot determine which
|
|
129
|
-
// tab's token to inject without the placeholder.
|
|
130
|
-
// Example with axios: configure it to use the OIDC fetch as its adapter or use the OIDC fetch directly.
|
|
131
|
-
trustedDomains.config_multi_tab_login = {
|
|
132
|
-
domains: ['https://demo.duendesoftware.com'],
|
|
133
|
-
allowMultiTabLogin: true,
|
|
36
|
+
const configuration: OidcConfiguration = {
|
|
37
|
+
client_id: 'your-public-client',
|
|
38
|
+
authority: 'https://issuer.example.com',
|
|
39
|
+
redirect_uri: `${window.location.origin}/authentication/callback`,
|
|
40
|
+
scope: 'openid profile',
|
|
134
41
|
};
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## Run The Demo
|
|
138
42
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
43
|
+
function Account(): React.JSX.Element {
|
|
44
|
+
const { login, logout, isAuthenticated } = useOidc();
|
|
45
|
+
const [hasError, setHasError] = useState(false);
|
|
46
|
+
|
|
47
|
+
const changeSession = async (): Promise<void> => {
|
|
48
|
+
setHasError(false);
|
|
49
|
+
try {
|
|
50
|
+
if (isAuthenticated) {
|
|
51
|
+
await logout('/');
|
|
52
|
+
} else {
|
|
53
|
+
await login('/');
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
setHasError(true);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
154
59
|
|
|
155
|
-
|
|
60
|
+
return (
|
|
61
|
+
<main>
|
|
62
|
+
<p>{isAuthenticated ? 'Signed in' : 'Not signed in'}</p>
|
|
63
|
+
<button type="button" onClick={changeSession}>
|
|
64
|
+
{isAuthenticated ? 'Sign out' : 'Sign in'}
|
|
65
|
+
</button>
|
|
66
|
+
{hasError && <p role="alert">Authentication failed. Please try again.</p>}
|
|
67
|
+
</main>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
156
70
|
|
|
157
|
-
|
|
71
|
+
const root = document.getElementById('root');
|
|
72
|
+
if (!root) throw new Error('Missing root element');
|
|
158
73
|
|
|
159
|
-
|
|
160
|
-
import React from 'react';
|
|
161
|
-
import { render } from 'react-dom';
|
|
162
|
-
import { BrowserRouter as Router } from 'react-router-dom';
|
|
163
|
-
import { OidcProvider } from '@axa-fr/react-oidc';
|
|
164
|
-
import Header from './Layout/Header';
|
|
165
|
-
import Routes from './Router';
|
|
166
|
-
|
|
167
|
-
// This configuration use hybrid mode
|
|
168
|
-
// ServiceWorker are used if available (more secure) else tokens are given to the client
|
|
169
|
-
// You need to give inside your code the "access_token" when using fetch
|
|
170
|
-
const configuration = {
|
|
171
|
-
client_id: 'interactive.public.short',
|
|
172
|
-
redirect_uri: window.location.origin + '/authentication/callback',
|
|
173
|
-
silent_redirect_uri: window.location.origin + '/authentication/silent-callback',
|
|
174
|
-
scope: 'openid profile email api offline_access', // offline_access scope allow your client to retrieve the refresh_token
|
|
175
|
-
authority: 'https://demo.duendesoftware.com',
|
|
176
|
-
par: 'auto',
|
|
177
|
-
service_worker_relative_url: '/OidcServiceWorker.js', // just comment that line to disable service worker mode
|
|
178
|
-
service_worker_only: false,
|
|
179
|
-
demonstrating_proof_of_possession: false,
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const App = () => (
|
|
74
|
+
createRoot(root).render(
|
|
183
75
|
<OidcProvider configuration={configuration}>
|
|
184
|
-
<
|
|
185
|
-
|
|
186
|
-
<Routes />
|
|
187
|
-
</Router>
|
|
188
|
-
</OidcProvider>
|
|
76
|
+
<Account />
|
|
77
|
+
</OidcProvider>,
|
|
189
78
|
);
|
|
190
|
-
|
|
191
|
-
render(<App />, document.getElementById('root'));
|
|
192
79
|
```
|
|
193
80
|
|
|
194
|
-
|
|
195
|
-
> If you have both `redirect_uri` and `silent_redirect_uri` configured, their value must be different.
|
|
196
|
-
|
|
197
|
-
```javascript
|
|
198
|
-
const configuration = {
|
|
199
|
-
loadingComponent: ReactComponent, // you can inject your own loading component
|
|
200
|
-
sessionLostComponent: ReactComponent, // you can inject your own session lost component
|
|
201
|
-
authenticating: ReactComponent, // you can inject your own authenticating component
|
|
202
|
-
authenticatingErrorComponent: ReactComponent,
|
|
203
|
-
callbackSuccessComponent: ReactComponent, // you can inject your own call back success component
|
|
204
|
-
serviceWorkerNotSupportedComponent: ReactComponent, // you can inject your page that explains you require a more modern browser
|
|
205
|
-
onSessionLost: Function, // If set, "sessionLostComponent" is not displayed, and onSessionLost callback is called instead
|
|
206
|
-
configuration: {
|
|
207
|
-
client_id: String.isRequired, // oidc client id
|
|
208
|
-
redirect_uri: String.isRequired, // oidc redirect url
|
|
209
|
-
silent_redirect_uri: String, // Optional activate silent-signin that use cookies between OIDC server and client javascript to restore sessions
|
|
210
|
-
silent_login_uri: String, // Optional, route that triggers the signin
|
|
211
|
-
silent_login_timeout: Number, // Optional, default is 12000 milliseconds
|
|
212
|
-
scope: String.isRequired, // oidc scope (you need to set "offline_access")
|
|
213
|
-
authority: String.isRequired,
|
|
214
|
-
storage: Storage, // Default sessionStorage, you can set localStorage, but it is not secure
|
|
215
|
-
login_state_storage: Storage, // Optional. Storage used only for authorization flow state (state, code_verifier, nonce, login params). Defaults to the value of `storage`. Set to sessionStorage when using storage: localStorage to prevent race conditions when multiple tabs start the login flow simultaneously.
|
|
216
|
-
authority_configuration: {
|
|
217
|
-
// Optional for providers that do not implement OIDC server auto-discovery via a .wellknown URL
|
|
218
|
-
authorization_endpoint: String,
|
|
219
|
-
token_endpoint: String,
|
|
220
|
-
userinfo_endpoint: String,
|
|
221
|
-
end_session_endpoint: String,
|
|
222
|
-
revocation_endpoint: String,
|
|
223
|
-
pushed_authorization_request_endpoint: String,
|
|
224
|
-
require_pushed_authorization_requests: Boolean,
|
|
225
|
-
check_session_iframe: String,
|
|
226
|
-
issuer: String,
|
|
227
|
-
},
|
|
228
|
-
refresh_time_before_tokens_expiration_in_second: Number, // default is 120 seconds
|
|
229
|
-
service_worker_relative_url: String,
|
|
230
|
-
service_worker_keep_alive_path: String, // default is "/"
|
|
231
|
-
service_worker_only: Boolean, // default false
|
|
232
|
-
service_worker_activate: () => boolean, // you can take the control of the service worker default activation which use user agent string
|
|
233
|
-
service_worker_register: (url: string) => Promise<ServiceWorkerRegistration>, // Optional, you can take the control of the service worker registration
|
|
234
|
-
extras: StringMap | undefined, // ex: {'prompt': 'consent', 'access_type': 'offline'} list of key/value that is sent to the OIDC server (more info: https://github.com/openid/AppAuth-JS)
|
|
235
|
-
token_request_extras: StringMap | undefined, // ex: {'prompt': 'consent', 'access_type': 'offline'} list of key/value that is sent to the OIDC server during token request (more info: https://github.com/openid/AppAuth-JS)
|
|
236
|
-
withCustomHistory: Function, // Override history modification, return an instance with replaceState(url, stateHistory) implemented (like History.replaceState())
|
|
237
|
-
authority_time_cache_wellknowurl_in_second: 60 * 60, // Time to cache in seconds of the openid well-known URL, default is 1 hour
|
|
238
|
-
authority_timeout_wellknowurl_in_millisecond: 10000, // Timeout in milliseconds of the openid well-known URL, default is 10 seconds, then an error is thrown
|
|
239
|
-
par: 'disabled' | 'auto' | 'required', // Pushed Authorization Requests mode, default is 'disabled'
|
|
240
|
-
par_request_timeout: Number, // PAR endpoint timeout in milliseconds, default is 10000
|
|
241
|
-
monitor_session: Boolean, // Add OpenID monitor session, default is false (more information https://openid.net/specs/openid-connect-session-1_0.html), if you need to set it to true consider https://infi.nl/nieuws/spa-necromancy/
|
|
242
|
-
onLogoutFromAnotherTab: Function, // Optional, can be set to override the default behavior, this function is triggered when a user with the same subject is logged out from another tab when session_monitor is active
|
|
243
|
-
onLogoutFromSameTab: Function, // Optional, can be set to override the default behavior, this function is triggered when a user is logged out from the same tab when session_monitor is active
|
|
244
|
-
token_renew_mode: String, // Optional, update tokens based on the selected token(s) lifetime: "access_token_or_id_token_invalid" (default), "access_token_invalid", "id_token_invalid"
|
|
245
|
-
token_automatic_renew_mode: TokenAutomaticRenewMode.AutomaticOnlyWhenFetchExecuted, // Optional, default is TokenAutomaticRenewMode.AutomaticBeforeTokensExpiration
|
|
246
|
-
// TokenAutomaticRenewMode.AutomaticBeforeTokensExpiration: renew tokens automatically before they expire
|
|
247
|
-
// TokenAutomaticRenewMode.AutomaticOnlyWhenFetchExecuted: renew tokens automatically only when fetch is executed
|
|
248
|
-
// It requires you to use fetch given by hook useOidcFetch(fetch) or HOC withOidcFetch(fetch)(Component)
|
|
249
|
-
logout_tokens_to_invalidate: Array<string>, // Optional tokens to invalidate during logout, default: ['access_token', 'refresh_token']
|
|
250
|
-
location: ILOidcLocation, // Optional, default is window.location, you can inject your own location object respecting the ILOidcLocation interface
|
|
251
|
-
demonstrating_proof_of_possession: Boolean, // Optional, default is false, if true, the the Demonstrating Proof of Possession will be activated //https://www.rfc-editor.org/rfc/rfc9449.html#name-protected-resource-access
|
|
252
|
-
demonstrating_proof_of_possession_configuration: DemonstratingProofOfPossessionConfiguration // Optional, more details bellow
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
demonstrating_proof_of_possession_configuration: DemonstratingProofOfPossessionConfiguration // Optional, more details bellow
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
interface DemonstratingProofOfPossessionConfiguration
|
|
260
|
-
{
|
|
261
|
-
generateKeyAlgorithm: RsaHashedKeyGenParams | EcKeyGenParams,
|
|
262
|
-
digestAlgorithm: AlgorithmIdentifier,
|
|
263
|
-
importKeyAlgorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
|
|
264
|
-
signAlgorithm: AlgorithmIdentifier | RsaPssParams | EcdsaParams,
|
|
265
|
-
jwtHeaderAlgorithm: string
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
// default value of demonstrating_proof_of_possession_configuration
|
|
269
|
-
const defaultDemonstratingProofOfPossessionConfiguration: DemonstratingProofOfPossessionConfiguration ={
|
|
270
|
-
importKeyAlgorithm: {
|
|
271
|
-
name: 'ECDSA',
|
|
272
|
-
namedCurve: 'P-256',
|
|
273
|
-
hash: {name: 'ES256'}
|
|
274
|
-
},
|
|
275
|
-
signAlgorithm: {name: 'ECDSA', hash: {name: 'SHA-256'}},
|
|
276
|
-
generateKeyAlgorithm: {
|
|
277
|
-
name: 'ECDSA',
|
|
278
|
-
namedCurve: 'P-256'
|
|
279
|
-
},
|
|
280
|
-
digestAlgorithm: { name: 'SHA-256' },
|
|
281
|
-
jwtHeaderAlgorithm : 'ES256'
|
|
282
|
-
};
|
|
81
|
+
`OidcProvider` processes the callback route; do not call `loginCallbackAsync()` yourself inside this React setup. `login('/')` selects the application destination after authentication, not the registered callback URL.
|
|
283
82
|
|
|
83
|
+
This example uses browser storage. For optional token isolation, follow the [service-worker setup](#service-worker).
|
|
284
84
|
|
|
85
|
+
```mermaid
|
|
86
|
+
flowchart LR
|
|
87
|
+
UI["React components and hooks"] --> Provider["OidcProvider"]
|
|
88
|
+
Provider --> Client["@axa-fr/oidc-client"]
|
|
89
|
+
Client --> IdP["OIDC identity provider"]
|
|
90
|
+
Client --> SW["Optional service worker"]
|
|
91
|
+
SW --> API["Trusted API"]
|
|
92
|
+
Client --> Fetch["OIDC fetch without worker"]
|
|
93
|
+
Fetch --> API
|
|
285
94
|
```
|
|
286
95
|
|
|
287
|
-
|
|
96
|
+
## Protect components
|
|
288
97
|
|
|
289
|
-
|
|
98
|
+
`OidcSecure` starts login when there is no authenticated session and renders its children only after authentication:
|
|
290
99
|
|
|
291
100
|
```tsx
|
|
292
|
-
|
|
293
|
-
client_id: 'spa-client',
|
|
294
|
-
redirect_uri: `${window.location.origin}/authentication/callback`,
|
|
295
|
-
scope: 'openid profile',
|
|
296
|
-
authority: 'https://issuer.example.com',
|
|
297
|
-
par: 'auto', // 'disabled' (default), 'auto', or 'required'
|
|
298
|
-
};
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
`auto` uses the discovered `pushed_authorization_request_endpoint` when it is
|
|
302
|
-
available. `required` fails before navigation if no endpoint is available.
|
|
303
|
-
Once PAR is selected, a PAR endpoint error is surfaced and never silently
|
|
304
|
-
downgraded. Browser deployments require the issuer's PAR endpoint to allow
|
|
305
|
-
CORS from the application origin. See the
|
|
306
|
-
[`@axa-fr/oidc-client` PAR documentation](../oidc-client/README.md#pushed-authorization-requests-par)
|
|
307
|
-
for complete mode semantics, custom authority metadata, error handling, and
|
|
308
|
-
security guidance.
|
|
309
|
-
|
|
310
|
-
## How to consume
|
|
311
|
-
|
|
312
|
-
> **Note (issue #1679):** `useOidc`, `useOidcUser`, `useOidcAccessToken` and
|
|
313
|
-
> `useOidcIdToken` are safe to call **outside** of an `<OidcProvider>` (e.g.
|
|
314
|
-
> in unit tests or Storybook stories). When no provider is mounted, they
|
|
315
|
-
> emit a single `console.warn` per configuration name and return stable
|
|
316
|
-
> default values (`isAuthenticated: false`, `oidcUser: null`,
|
|
317
|
-
> `accessToken: null`, `idToken: null`, with no-op `login`/`logout`/
|
|
318
|
-
> `renewTokens`/`reloadOidcUser`). Use `OidcClient.getOrThrow(name)` if you
|
|
319
|
-
> prefer the previous fail-fast behaviour.
|
|
320
|
-
|
|
321
|
-
"useOidc" returns all props from the Hook :
|
|
322
|
-
|
|
323
|
-
```javascript
|
|
324
|
-
import React from 'react';
|
|
325
|
-
import { useOidc } from './oidc';
|
|
326
|
-
|
|
327
|
-
export const Home = () => {
|
|
328
|
-
const { login, logout, renewTokens, isAuthenticated } = useOidc();
|
|
101
|
+
import { OidcSecure } from '@axa-fr/react-oidc';
|
|
329
102
|
|
|
103
|
+
export function PrivatePage(): React.JSX.Element {
|
|
330
104
|
return (
|
|
331
|
-
<
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
<h5 className="card-title">Welcome !!!</h5>
|
|
335
|
-
<p className="card-text">React Demo Application protected by OpenId Connect</p>
|
|
336
|
-
{!isAuthenticated && (
|
|
337
|
-
<button type="button" className="btn btn-primary" onClick={() => login('/profile')}>
|
|
338
|
-
Login
|
|
339
|
-
</button>
|
|
340
|
-
)}
|
|
341
|
-
{isAuthenticated && (
|
|
342
|
-
<button type="button" className="btn btn-primary" onClick={() => logout()}>
|
|
343
|
-
logout
|
|
344
|
-
</button>
|
|
345
|
-
)}
|
|
346
|
-
{isAuthenticated && (
|
|
347
|
-
<button type="button" className="btn btn-primary" onClick={() => renewTokens()}>
|
|
348
|
-
renewTokens
|
|
349
|
-
</button>
|
|
350
|
-
)}
|
|
351
|
-
</div>
|
|
352
|
-
</div>
|
|
353
|
-
</div>
|
|
105
|
+
<OidcSecure callbackPath="/account">
|
|
106
|
+
<h1>Account</h1>
|
|
107
|
+
</OidcSecure>
|
|
354
108
|
);
|
|
355
|
-
}
|
|
109
|
+
}
|
|
356
110
|
```
|
|
357
111
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
- isAuthenticated : if the user is logged in or not
|
|
361
|
-
- logout: logout function (return a promise)
|
|
362
|
-
- login: login function 'return a promise'
|
|
363
|
-
- renewTokens: renew tokens function 'return a promise'
|
|
364
|
-
|
|
365
|
-
## How to secure a component
|
|
112
|
+
Place it beneath `OidcProvider`. You can protect the whole application, a route element, or a smaller component. Optional props are `callbackPath`, authorization `extras`, and `configurationName`.
|
|
366
113
|
|
|
367
|
-
|
|
114
|
+
The higher-order component (HOC) equivalent is:
|
|
368
115
|
|
|
369
|
-
```
|
|
370
|
-
import
|
|
371
|
-
import { OidcSecure } from '@axa-fr/react-oidc';
|
|
116
|
+
```tsx
|
|
117
|
+
import { withOidcSecure } from '@axa-fr/react-oidc';
|
|
372
118
|
|
|
373
|
-
|
|
374
|
-
<
|
|
375
|
-
|
|
376
|
-
</OidcSecure>
|
|
377
|
-
);
|
|
119
|
+
function AccountDetails(): React.JSX.Element {
|
|
120
|
+
return <h1>Account details</h1>;
|
|
121
|
+
}
|
|
378
122
|
|
|
379
|
-
|
|
380
|
-
export default AdminSecure;
|
|
123
|
+
export const ProtectedAccountDetails = withOidcSecure(AccountDetails, '/account');
|
|
381
124
|
```
|
|
382
125
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
`withOidcSecure` will act the same as `OidcSecure`,it will also trigger authentication in case the user is not authenticated.
|
|
386
|
-
|
|
387
|
-
```javascript
|
|
388
|
-
import React from 'react';
|
|
389
|
-
import { Switch, Route } from 'react-router-dom';
|
|
390
|
-
import { withOidcSecure } from '@axa-fr/react-oidc';
|
|
391
|
-
import Home from '../Pages/Home';
|
|
392
|
-
import Dashboard from '../Pages/Dashboard';
|
|
393
|
-
import Admin from '../Pages/Admin';
|
|
394
|
-
|
|
395
|
-
const Routes = () => (
|
|
396
|
-
<Switch>
|
|
397
|
-
<Route exact path="/" component={Home} />
|
|
398
|
-
<Route path="/dashboard" component={withOidcSecure(Dashboard)} />
|
|
399
|
-
<Route path="/admin" component={Admin} />
|
|
400
|
-
<Route path="/home" component={Home} />
|
|
401
|
-
</Switch>
|
|
402
|
-
);
|
|
126
|
+
Its signature is `withOidcSecure(Component, callbackPath?, extras?, configurationName?)`.
|
|
403
127
|
|
|
404
|
-
|
|
405
|
-
```
|
|
128
|
+
These components gate the UI; your APIs must independently validate tokens and enforce authorization.
|
|
406
129
|
|
|
407
|
-
##
|
|
130
|
+
## Call protected APIs
|
|
408
131
|
|
|
409
|
-
|
|
410
|
-
import { useOidcAccessToken } from '@axa-fr/react-oidc';
|
|
132
|
+
`useOidcFetch()` returns a fetch wrapper that attaches the access token (or a service-worker placeholder) and integrates with renewal. Use it only for trusted API URLs, not arbitrary user-supplied destinations.
|
|
411
133
|
|
|
412
|
-
|
|
413
|
-
|
|
134
|
+
```tsx
|
|
135
|
+
import { useState } from 'react';
|
|
136
|
+
import { useOidcFetch } from '@axa-fr/react-oidc';
|
|
137
|
+
|
|
138
|
+
export function ProfileRequest(): React.JSX.Element {
|
|
139
|
+
const { fetch: oidcFetch } = useOidcFetch();
|
|
140
|
+
const [status, setStatus] = useState('Ready');
|
|
141
|
+
|
|
142
|
+
const loadProfile = async (): Promise<void> => {
|
|
143
|
+
setStatus('Loading…');
|
|
144
|
+
try {
|
|
145
|
+
const response = await oidcFetch('https://api.example.com/profile');
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
throw new Error(`API request failed with status ${response.status}`);
|
|
148
|
+
}
|
|
149
|
+
setStatus('Profile request succeeded');
|
|
150
|
+
} catch {
|
|
151
|
+
setStatus('Could not load the profile');
|
|
152
|
+
}
|
|
153
|
+
};
|
|
414
154
|
|
|
415
|
-
if (!accessToken) {
|
|
416
|
-
return <p>you are not authentified</p>;
|
|
417
|
-
}
|
|
418
155
|
return (
|
|
419
|
-
|
|
420
|
-
<
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
client side javascript.
|
|
426
|
-
</p>
|
|
427
|
-
{<p className="card-text">{JSON.stringify(accessToken)}</p>}
|
|
428
|
-
{accessTokenPayload != null && (
|
|
429
|
-
<p className="card-text">{JSON.stringify(accessTokenPayload)}</p>
|
|
430
|
-
)}
|
|
431
|
-
</div>
|
|
432
|
-
</div>
|
|
156
|
+
<>
|
|
157
|
+
<button type="button" onClick={loadProfile}>
|
|
158
|
+
Load profile
|
|
159
|
+
</button>
|
|
160
|
+
<p role="status">{status}</p>
|
|
161
|
+
</>
|
|
433
162
|
);
|
|
434
|
-
}
|
|
163
|
+
}
|
|
435
164
|
```
|
|
436
165
|
|
|
437
|
-
|
|
166
|
+
Render protected API consumers beneath `OidcSecure`, or wait until `useOidc().isAuthenticated` is true. Check `response.ok`: ordinary HTTP error responses do not automatically reject.
|
|
438
167
|
|
|
439
|
-
|
|
440
|
-
|
|
168
|
+
| API | Arguments / result |
|
|
169
|
+
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
170
|
+
| `useOidcFetch(fetch?, configurationName?, demonstratingProofOfPossession?)` | Returns `{ fetch }`; defaults to browser fetch and configuration `'default'`. |
|
|
171
|
+
| `withOidcFetch(fetch?, configurationName?, demonstratingProofOfPossession?)(Component)` | Injects a `fetch` prop into a component. |
|
|
441
172
|
|
|
442
|
-
|
|
443
|
-
const { idToken, idTokenPayload } = useOidcIdToken();
|
|
173
|
+
For example, `withOidcFetch()(ProfileComponent)` supplies the same wrapper through props instead of a hook.
|
|
444
174
|
|
|
445
|
-
|
|
446
|
-
return <p>you are not authentified</p>;
|
|
447
|
-
}
|
|
175
|
+
## User information and token hooks
|
|
448
176
|
|
|
449
|
-
|
|
450
|
-
<div className="card text-white bg-info mb-3">
|
|
451
|
-
<div className="card-body">
|
|
452
|
-
<h5 className="card-title">ID Token</h5>
|
|
453
|
-
{<p className="card-text">{JSON.stringify(idToken)}</p>}
|
|
454
|
-
{idTokenPayload != null && <p className="card-text">{JSON.stringify(idTokenPayload)}</p>}
|
|
455
|
-
</div>
|
|
456
|
-
</div>
|
|
457
|
-
);
|
|
458
|
-
};
|
|
459
|
-
```
|
|
177
|
+
### User information
|
|
460
178
|
|
|
461
|
-
|
|
179
|
+
`useOidcUser()` reads the provider's user-info endpoint and exposes loading state. The exported enum is **`OidcUserStatus`**:
|
|
462
180
|
|
|
463
|
-
```
|
|
464
|
-
import {
|
|
181
|
+
```tsx
|
|
182
|
+
import { OidcUserStatus, useOidcUser } from '@axa-fr/react-oidc';
|
|
465
183
|
|
|
466
|
-
|
|
184
|
+
export function UserGreeting(): React.JSX.Element {
|
|
467
185
|
const { oidcUser, oidcUserLoadingState } = useOidcUser();
|
|
468
186
|
|
|
469
187
|
switch (oidcUserLoadingState) {
|
|
470
|
-
case
|
|
471
|
-
return <p>
|
|
472
|
-
case
|
|
473
|
-
return <p>
|
|
474
|
-
case
|
|
475
|
-
return <p>
|
|
188
|
+
case OidcUserStatus.Loading:
|
|
189
|
+
return <p>Loading profile…</p>;
|
|
190
|
+
case OidcUserStatus.Unauthenticated:
|
|
191
|
+
return <p>Please sign in.</p>;
|
|
192
|
+
case OidcUserStatus.LoadingError:
|
|
193
|
+
return <p>Could not load your profile.</p>;
|
|
476
194
|
default:
|
|
477
|
-
return
|
|
478
|
-
<div className="card text-white bg-success mb-3">
|
|
479
|
-
<div className="card-body">
|
|
480
|
-
<h5 className="card-title">User information</h5>
|
|
481
|
-
<p className="card-text">{JSON.stringify(oidcUser)}</p>
|
|
482
|
-
</div>
|
|
483
|
-
</div>
|
|
484
|
-
);
|
|
195
|
+
return <p>Hello, {oidcUser?.name ?? 'there'}.</p>;
|
|
485
196
|
}
|
|
486
|
-
}
|
|
197
|
+
}
|
|
487
198
|
```
|
|
488
199
|
|
|
489
|
-
|
|
200
|
+
The hook also returns `reloadOidcUser()`. Use `useOidcUser<MyUserInfo>(configurationName?, demonstratingProofOfPossession?)` for custom claims, where `MyUserInfo` extends `OidcUserInfo`.
|
|
490
201
|
|
|
491
|
-
|
|
492
|
-
This hook will give you a wrapped fetch that adds the access token for you.
|
|
202
|
+
### Hook reference
|
|
493
203
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
204
|
+
| Hook | Return values |
|
|
205
|
+
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
206
|
+
| `useOidc(configurationName?)` | `isAuthenticated`, `login`, `logout`, `renewTokens`. |
|
|
207
|
+
| `useOidcUser<T>(configurationName?, demonstratingProofOfPossession?)` | `oidcUser`, `oidcUserLoadingState`, `reloadOidcUser`. |
|
|
208
|
+
| `useOidcAccessToken(configurationName?)` | `accessToken`, `accessTokenPayload`, and, when enabled, `generateDemonstrationOfProofOfPossessionAsync`. |
|
|
209
|
+
| `useOidcIdToken(configurationName?)` | `idToken`, `idTokenPayload`. |
|
|
497
210
|
|
|
498
|
-
|
|
499
|
-
const [oidcUser, setOidcUser] = useState(null);
|
|
500
|
-
const [isLoading, setLoading] = useState(true);
|
|
211
|
+
`login(callbackPath?, extras?, silentLoginOnly?, scope?)`, `logout(callbackPath?, extras?)`, and `renewTokens(extras?)` return promises.
|
|
501
212
|
|
|
502
|
-
|
|
503
|
-
const fetchUserInfoAsync = async () => {
|
|
504
|
-
const res = await fetch('https://demo.duendesoftware.com/connect/userinfo');
|
|
505
|
-
if (res.status != 200) {
|
|
506
|
-
return null;
|
|
507
|
-
}
|
|
508
|
-
return res.json();
|
|
509
|
-
};
|
|
510
|
-
let isMounted = true;
|
|
511
|
-
fetchUserInfoAsync().then(userInfo => {
|
|
512
|
-
if (isMounted) {
|
|
513
|
-
setLoading(false);
|
|
514
|
-
setOidcUser(userInfo);
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
return () => {
|
|
518
|
-
isMounted = false;
|
|
519
|
-
};
|
|
520
|
-
}, []);
|
|
521
|
-
|
|
522
|
-
if (isLoading) {
|
|
523
|
-
return <>Loading</>;
|
|
524
|
-
}
|
|
213
|
+
Prefer `useOidcFetch` over manually building authorization headers. When worker token hiding is enabled, `accessToken` is a placeholder rather than the real token. Do not render or log raw tokens, and treat user claims as personal data. An ID token identifies the user to the client; it is not a replacement for an API access token.
|
|
525
214
|
|
|
526
|
-
|
|
527
|
-
<div className="container mt-3">
|
|
528
|
-
<div className="card text-white bg-success mb-3">
|
|
529
|
-
<div className="card-body">
|
|
530
|
-
<h5 className="card-title">User information</h5>
|
|
531
|
-
{oidcUser != null && <p className="card-text">{JSON.stringify(oidcUser)}</p>}
|
|
532
|
-
</div>
|
|
533
|
-
</div>
|
|
534
|
-
</div>
|
|
535
|
-
);
|
|
536
|
-
};
|
|
215
|
+
## Service worker
|
|
537
216
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
<DisplayUserInfo fetch={fetch} />
|
|
543
|
-
</OidcSecure>
|
|
544
|
-
);
|
|
545
|
-
};
|
|
217
|
+
Ensure your application's static-assets directory exists (`public` below), then install the worker assets:
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
node ./node_modules/@axa-fr/react-oidc/bin/copy-service-worker-files.mjs public
|
|
546
221
|
```
|
|
547
222
|
|
|
548
|
-
|
|
223
|
+
Keep the generated `OidcServiceWorker.js` synchronized with package updates. For example, merge this into your application's `package.json`:
|
|
549
224
|
|
|
550
|
-
|
|
551
|
-
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"scripts": {
|
|
228
|
+
"postinstall": "node ./node_modules/@axa-fr/react-oidc/bin/copy-service-worker-files.mjs public"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
552
232
|
|
|
553
|
-
|
|
554
|
-
import React, { useEffect, useState } from 'react';
|
|
555
|
-
import { useOidcFetch, OidcSecure } from '@axa-fr/react-oidc';
|
|
233
|
+
Then:
|
|
556
234
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
235
|
+
1. Configure `public/OidcTrustedDomains.js` with your provider and API destinations.
|
|
236
|
+
2. Add `service_worker_relative_url: '/OidcServiceWorker.js'` to the OIDC configuration.
|
|
237
|
+
3. Set `service_worker_only: true` if login must not fall back to browser token storage.
|
|
238
|
+
4. Serve the worker over HTTPS (or localhost) with a scope that covers the application.
|
|
560
239
|
|
|
561
|
-
|
|
562
|
-
const fetchUserInfoAsync = async () => {
|
|
563
|
-
const res = await fetch('https://demo.duendesoftware.com/connect/userinfo');
|
|
564
|
-
if (res.status != 200) {
|
|
565
|
-
return null;
|
|
566
|
-
}
|
|
567
|
-
return res.json();
|
|
568
|
-
};
|
|
569
|
-
let isMounted = true;
|
|
570
|
-
fetchUserInfoAsync().then(userInfo => {
|
|
571
|
-
if (isMounted) {
|
|
572
|
-
setLoading(false);
|
|
573
|
-
setOidcUser(userInfo);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
return () => {
|
|
577
|
-
isMounted = false;
|
|
578
|
-
};
|
|
579
|
-
}, []);
|
|
580
|
-
|
|
581
|
-
if (isLoading) {
|
|
582
|
-
return <>Loading</>;
|
|
583
|
-
}
|
|
240
|
+
Follow the [core service-worker guide](../oidc-client/README.md#service-worker) for a trusted-domain example, all options, token-exposure choices, and fallback behavior.
|
|
584
241
|
|
|
585
|
-
|
|
586
|
-
<div className="container mt-3">
|
|
587
|
-
<div className="card text-white bg-success mb-3">
|
|
588
|
-
<div className="card-body">
|
|
589
|
-
<h5 className="card-title">User information</h5>
|
|
590
|
-
{oidcUser != null && <p className="card-text">{JSON.stringify(oidcUser)}</p>}
|
|
591
|
-
</div>
|
|
592
|
-
</div>
|
|
593
|
-
</div>
|
|
594
|
-
);
|
|
595
|
-
};
|
|
242
|
+
**Multi-tab login:** when `allowMultiTabLogin: true` is set in a trusted-domain entry, use `useOidcFetch()` or `withOidcFetch()` for protected API calls. Their tab-specific placeholder identifies the session to the worker. Plain fetch or a default Axios request cannot supply that marker and may result in HTTP 401.
|
|
596
243
|
|
|
597
|
-
|
|
598
|
-
export const FetchUserHoc = () => (
|
|
599
|
-
<OidcSecure>
|
|
600
|
-
<UserInfoWithFetchHoc />
|
|
601
|
-
</OidcSecure>
|
|
602
|
-
);
|
|
603
|
-
```
|
|
244
|
+
The worker can hide access and refresh tokens from application JavaScript, but it does **not** prevent XSS or stop injected code from making requests through your application. It does not hide all identity information. Keep normal XSS defenses and avoid broad trusted-domain rules.
|
|
604
245
|
|
|
605
|
-
##
|
|
246
|
+
## Configuration, renewal, PAR, and DPoP
|
|
606
247
|
|
|
607
|
-
|
|
608
|
-
All components definition receive props `configurationName`. Please checkout the demo for more complete example.
|
|
248
|
+
`OidcProvider` accepts the same `OidcConfiguration` as the vanilla client. Keep provider props, such as custom components and routing callbacks, outside that configuration object.
|
|
609
249
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
import Header from './Layout/Header';
|
|
616
|
-
import Routes from './Router';
|
|
617
|
-
|
|
618
|
-
// This configuration use hybrid mode
|
|
619
|
-
// ServiceWorker are used if available (more secure) else tokens are given to the client
|
|
620
|
-
// You need to give inside your code the "access_token" when using fetch
|
|
621
|
-
const configuration = {
|
|
622
|
-
client_id: 'interactive.public.short',
|
|
623
|
-
redirect_uri: 'http://localhost:4200/authentication/callback',
|
|
624
|
-
silent_redirect_uri: 'http://localhost:4200/authentication/silent-callback',
|
|
625
|
-
scope: 'openid profile email api offline_access',
|
|
626
|
-
authority: 'https://demo.identityserver.io',
|
|
627
|
-
service_worker_relative_url: '/OidcServiceWorker.js',
|
|
628
|
-
service_worker_only: false,
|
|
629
|
-
};
|
|
250
|
+
- **Configuration:** see the [core configuration reference](../oidc-client/README.md#configuration) for required fields, storage, discovery, timeouts, logout, and session monitoring.
|
|
251
|
+
- **Renewal:** automatic renewal is enabled by default. For `TokenAutomaticRenewMode.AutomaticOnlyWhenFetchExecuted`, use `useOidcFetch`/`withOidcFetch` so requests trigger renewal. See [renewal behavior and strict renewal](../oidc-client/README.md#token-renewal).
|
|
252
|
+
- **Silent login:** add a distinct `silent_redirect_uri` if required. The provider handles its silent-login routes. Provider and browser cookie policies can prevent iframe login.
|
|
253
|
+
- **PAR:** set `par: 'auto'` or `'required'` in `configuration`; the default is `'disabled'`. `auto` uses PAR when an endpoint is advertised; missing required endpoints fail before navigation. Once selected, PAR errors never silently downgrade. See [full PAR semantics, errors, and CORS requirements](../oidc-client/README.md#pushed-authorization-requests-par).
|
|
254
|
+
- **DPoP:** enable `demonstrating_proof_of_possession` and use `useOidcFetch(undefined, 'default', true)` or `withOidcFetch(undefined, 'default', true)`. For user info, use `useOidcUser('default', true)`. See [DPoP and worker-specific settings](../oidc-client/README.md#dpop). Both the identity provider and resource server must support it.
|
|
630
255
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
//const onSessionLost = ()=>{
|
|
641
|
-
// setIsSessionLost(true);
|
|
642
|
-
//}
|
|
643
|
-
|
|
644
|
-
const App = () => (
|
|
645
|
-
<OidcProvider
|
|
646
|
-
configuration={configuration}
|
|
647
|
-
loadingComponent={Loading}
|
|
648
|
-
authenticatingErrorComponent={AuthenticatingError}
|
|
649
|
-
authenticatingComponent={Authenticating}
|
|
650
|
-
sessionLostComponent={SessionLost}
|
|
651
|
-
//onSessionLost={onSessionLost} // If set "sessionLostComponent" is not displayed and onSessionLost callback is called instead
|
|
652
|
-
serviceWorkerNotSupportedComponent={ServiceWorkerNotSupported}
|
|
653
|
-
callbackSuccessComponent={CallBackSuccess}
|
|
654
|
-
>
|
|
655
|
-
{/* isSessionLost && <SessionLost />*/}
|
|
656
|
-
<Router>
|
|
657
|
-
<Header />
|
|
658
|
-
<Routes />
|
|
659
|
-
</Router>
|
|
660
|
-
</OidcProvider>
|
|
256
|
+
## Custom components and provider options
|
|
257
|
+
|
|
258
|
+
Replace built-in status screens with your own components. These are **provider props**, not fields inside `configuration`:
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
const Loading = (): React.JSX.Element => <p>Restoring your session…</p>;
|
|
262
|
+
const AuthenticationError = (): React.JSX.Element => (
|
|
263
|
+
<p role="alert">Sign-in failed. Please return to the sign-in page and try again.</p>
|
|
661
264
|
);
|
|
662
265
|
|
|
663
|
-
|
|
266
|
+
<OidcProvider
|
|
267
|
+
configuration={configuration}
|
|
268
|
+
loadingComponent={Loading}
|
|
269
|
+
authenticatingErrorComponent={AuthenticationError}
|
|
270
|
+
>
|
|
271
|
+
<App />
|
|
272
|
+
</OidcProvider>;
|
|
664
273
|
```
|
|
665
274
|
|
|
666
|
-
|
|
275
|
+
| Provider prop | Purpose |
|
|
276
|
+
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
277
|
+
| `configuration`, `configurationName` | Client configuration and its name (`'default'` by default). |
|
|
278
|
+
| `loadingComponent` | Session restoration/loading screen. |
|
|
279
|
+
| `loadingTimeoutComponent` | Screen shown when the loading watchdog expires. |
|
|
280
|
+
| `authenticatingComponent` | Screen shown while starting login. |
|
|
281
|
+
| `authenticatingErrorComponent` | Login or callback failure screen. |
|
|
282
|
+
| `callbackSuccessComponent` | Screen shown after a successful callback. |
|
|
283
|
+
| `sessionLostComponent` | Session-loss screen. |
|
|
284
|
+
| `serviceWorkerNotSupportedComponent` | Unavailable-worker screen when worker-only mode is required. |
|
|
285
|
+
| `onSessionLost` | Handles session loss instead of displaying the built-in session-loss flow. |
|
|
286
|
+
| `onLogoutFromAnotherTab`, `onLogoutFromSameTab` | React to logout events. |
|
|
287
|
+
| `onEvent(configurationName, eventName, data)` | Observe client events; avoid logging complete payloads. |
|
|
288
|
+
| `withCustomHistory` | Returns a history adapter with `replaceState(url, stateHistory?)`. |
|
|
289
|
+
| `navigateAfterCallback(callbackPath)` | Async callback to perform post-login navigation; takes precedence over the history adapter for this navigation. |
|
|
290
|
+
| `getFetch` | Supplies a custom fetch implementation for the underlying client. |
|
|
291
|
+
| `location` | Custom `ILOidcLocation` adapter. |
|
|
667
292
|
|
|
668
|
-
|
|
669
|
-
Internally, native History API is used to be router library agnostic.
|
|
293
|
+
Custom status components receive `configurationName`. Set `configuration.loading_timeout_ms` to change the default 30-second loading watchdog, or a nonpositive value to disable it. See [`OidcProviderProps`](./src/OidcProvider.tsx) for the full type.
|
|
670
294
|
|
|
671
|
-
|
|
295
|
+
## Routing and Next.js
|
|
672
296
|
|
|
673
|
-
-
|
|
674
|
-
- [English : Increase the security and simplicity of your information system with openid connect](https://medium.com/just-tech-it-now/increase-the-security-and-simplicity-of-your-information-system-with-openid-connect-fa8c26b99d6d)
|
|
675
|
-
- [English: youtube OIDC](https://www.youtube.com/watch?v=frIJfavZkUE&list=PL8EMdIH6Mzxy2kHtsVOEWqNz-OaM_D_fB&index=1)
|
|
676
|
-
- [French: youtube OIDC](https://www.youtube.com/watch?v=H-mLMGzQ_y0&list=PL8EMdIH6Mzxy2kHtsVOEWqNz-OaM_D_fB&index=2)
|
|
297
|
+
### Client-side routers
|
|
677
298
|
|
|
678
|
-
|
|
299
|
+
The default navigation uses the browser History API and dispatches `popstate`. Keep `OidcProvider` mounted on callback URLs. Protect route elements with `OidcSecure`; no particular router package is required.
|
|
679
300
|
|
|
680
|
-
|
|
301
|
+
For a router-specific integration, provide `navigateAfterCallback` to perform and await the router's navigation, or `withCustomHistory` to replace the default history adapter. These options belong on `OidcProvider`.
|
|
681
302
|
|
|
682
|
-
|
|
303
|
+
The library retains hash-route callback matching for legacy integrations, but OAuth redirect URIs must not contain a fragment. Prefer path-based callback URLs for new deployments, even if the rest of the application uses a hash router. Existing hash-callback setups depend on provider-specific behavior. Interactive and silent callback URLs must be different.
|
|
683
304
|
|
|
684
|
-
|
|
685
|
-
import { OidcProvider } from '@axa-fr/react-oidc';
|
|
686
|
-
import { useRouter } from 'next/router';
|
|
305
|
+
### Next.js
|
|
687
306
|
|
|
688
|
-
|
|
689
|
-
client_id: 'interactive.public.short',
|
|
690
|
-
redirect_uri: 'http://localhost:3001/#authentication/callback',
|
|
691
|
-
silent_redirect_uri: 'http://localhost:3001/#authentication/silent-callback', // Optional activate silent-login that use cookies between OIDC server and client javascript to restore the session
|
|
692
|
-
scope: 'openid profile email api offline_access',
|
|
693
|
-
authority: 'https://demo.duendesoftware.com',
|
|
694
|
-
par: 'auto',
|
|
695
|
-
};
|
|
307
|
+
Keep this browser library behind a client-only boundary: do not access `window` or initialize the OIDC client while rendering on the server. In the App Router, `'use client'` alone does not disable prerendering; use an appropriate client-only mounting or dynamic-import strategy.
|
|
696
308
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
309
|
+
The repository's [Next.js demo](../../examples/nextjs-demo/README.md) uses the **Pages Router** and a custom history adapter. For an initialized client-only provider using `next/router`, a post-login navigation adapter can look like this:
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
import { useRouter } from 'next/router';
|
|
313
|
+
import { OidcProvider } from '@axa-fr/react-oidc';
|
|
700
314
|
|
|
701
|
-
|
|
315
|
+
function ClientAuth({ children }: React.PropsWithChildren): React.JSX.Element {
|
|
702
316
|
const router = useRouter();
|
|
703
|
-
const withCustomHistory = () => {
|
|
704
|
-
return {
|
|
705
|
-
replaceState: url => {
|
|
706
|
-
router
|
|
707
|
-
.replace({
|
|
708
|
-
pathname: url,
|
|
709
|
-
})
|
|
710
|
-
.then(() => {
|
|
711
|
-
window.dispatchEvent(new Event('popstate'));
|
|
712
|
-
});
|
|
713
|
-
},
|
|
714
|
-
};
|
|
715
|
-
};
|
|
716
317
|
|
|
717
318
|
return (
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
319
|
+
<OidcProvider
|
|
320
|
+
configuration={configuration}
|
|
321
|
+
navigateAfterCallback={async (path: string): Promise<void> => {
|
|
322
|
+
await router.replace(path);
|
|
323
|
+
window.dispatchEvent(new Event('popstate'));
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
{children}
|
|
327
|
+
</OidcProvider>
|
|
727
328
|
);
|
|
728
329
|
}
|
|
729
330
|
```
|
|
730
331
|
|
|
731
|
-
|
|
332
|
+
Here `configuration` is your browser-side OIDC configuration. Do not copy `next/router` into an App Router application; adapt to that router's APIs and navigation lifecycle. This package does not provide server-side route protection or a server session.
|
|
732
333
|
|
|
733
|
-
##
|
|
334
|
+
## Named configurations
|
|
734
335
|
|
|
735
|
-
|
|
336
|
+
Use names to separate identity providers or sessions with different scopes. Give each configuration distinct callback URLs and, for worker mode, a matching trusted-domain entry.
|
|
736
337
|
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
};
|
|
338
|
+
```tsx
|
|
339
|
+
<OidcProvider configuration={accountConfiguration}>
|
|
340
|
+
<OidcProvider configuration={paymentsConfiguration} configurationName="payments">
|
|
341
|
+
<App />
|
|
342
|
+
</OidcProvider>
|
|
343
|
+
</OidcProvider>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Select the name explicitly; nesting does not change the hooks' default name:
|
|
347
|
+
|
|
348
|
+
```tsx
|
|
349
|
+
const { login, isAuthenticated } = useOidc('payments');
|
|
350
|
+
const { fetch: paymentsFetch } = useOidcFetch(undefined, 'payments');
|
|
749
351
|
```
|
|
352
|
+
|
|
353
|
+
Use `<OidcSecure configurationName="payments">` for that session's protected UI. Token and user hooks also accept a configuration name.
|
|
354
|
+
|
|
355
|
+
## Errors and missing providers
|
|
356
|
+
|
|
357
|
+
`OidcError`, `OidcErrorCode`, `isOidcError`, `OidcStateError`, `OidcStateErrorCode`, `isOidcStateError`, and the PAR error classes/guards are re-exported from this package. Use stable codes and phases, not string matching, for error handling.
|
|
358
|
+
|
|
359
|
+
See the [core errors and events guide](../oidc-client/README.md#errors-and-events) for renewal errors, missing/mismatched state, missing nonces, retryability, and recovery. The provider's custom error/session-loss screens and `onEvent` callback are the React integration points. For strict renewal outside a hook, use `OidcClient.getOrThrow(name).renewTokensOrThrowAsync()`.
|
|
360
|
+
|
|
361
|
+
When no client has been initialized for a name, `useOidc`, `useOidcUser`, `useOidcAccessToken`, and `useOidcIdToken` warn once per name and return unauthenticated/null defaults. This is useful in tests and Storybook, but does not initialize authentication.
|
|
362
|
+
|
|
363
|
+
`OidcClient.get(name)` returns `null` for a missing client; `getOrThrow(name)` fails explicitly. `OidcSecure` and requests made through `useOidcFetch` require initialization and remain fail-fast. Mount a matching provider before using them.
|
|
364
|
+
|
|
365
|
+
## Examples and further reading
|
|
366
|
+
|
|
367
|
+
- [React demo](../../examples/react-oidc-demo/README.md) — routes, hooks, worker options, and named configurations.
|
|
368
|
+
- [Next.js demo](../../examples/nextjs-demo/README.md) — Pages Router integration.
|
|
369
|
+
- [Core client guide and API](../oidc-client/README.md).
|
|
370
|
+
- [Service-worker protocol](../oidc-client-service-worker/PROTOCOL.md).
|
|
371
|
+
- [Service-worker package guide](../oidc-client-service-worker/README.md).
|
|
372
|
+
- [FAQ and deployment guidance](../../FAQ.md).
|
|
373
|
+
|
|
374
|
+
The demos use the repository's pnpm workspace. Follow their READMEs for setup rather than installing each package separately.
|