@authu/react 1.0.64 → 1.0.66

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 CHANGED
@@ -128,6 +128,55 @@ function ApiComponent() {
128
128
  }
129
129
  ```
130
130
 
131
+ ## Token Storage
132
+
133
+ **Tokens never touch `localStorage`, `sessionStorage` or any other persistent
134
+ store.** The access token and the refresh token live in memory for the lifetime
135
+ of the page: an XSS on the host application can still act as the user while the
136
+ page is open, but it can no longer walk away with a 30-day refresh token and
137
+ replay it from anywhere, later.
138
+
139
+ The trade-off is that a reload loses the tokens. The session itself is not
140
+ lost, though: it lives in AuthU's own cookie, and the SDK re-derives the tokens
141
+ from it on every page load with one silent round trip.
142
+
143
+ ### Silent renewal (`prompt=none`)
144
+
145
+ On mount, when no code is coming back in the URL, the SDK redirects to
146
+ `/authorize?prompt=none`. AuthU answers without ever showing a page:
147
+
148
+ - session cookie still valid → it redirects straight back with a fresh
149
+ authorization code, which the SDK exchanges for tokens. The visitor sees a
150
+ single redirect and stays signed in.
151
+ - no session → it redirects back with `error=login_required`. The SDK reads
152
+ that as "anonymous", renders accordingly, and records the attempt in
153
+ `sessionStorage` so the tab never loops through AuthU again.
154
+
155
+ The URL the visitor asked for is saved before the round trip and restored
156
+ afterwards, so landing on `redirectUri` is invisible.
157
+
158
+ Set `silentAuth: false` to opt out. The session then does not survive a reload
159
+ at all: every load starts signed out until `login()` is called.
160
+
161
+ ### Logging out
162
+
163
+ `logout()` now navigates to AuthU's `/logout` rather than only clearing memory.
164
+ It has to: the session lives in the AuthU cookie, so a purely local logout
165
+ would let the next silent renewal sign the visitor right back in. The browser
166
+ returns to `redirectUri`, or to `options.returnTo` when given - which must be a
167
+ URI registered for the tenant, or on the tenant's home origin, otherwise AuthU
168
+ falls back to its own host.
169
+
170
+ ### Upgrading from 1.0.x
171
+
172
+ Two behaviour changes to expect in an app that upgrades:
173
+
174
+ - any token left in `localStorage` under `authu_tokens` by a previous version
175
+ is deleted on the first load;
176
+ - `logout()` performs a full navigation. Code that expected it to be
177
+ synchronous and stay on the page must move whatever it did afterwards into
178
+ `options.returnTo`.
179
+
131
180
  ## API Reference
132
181
 
133
182
  ### AuthUProvider Props
@@ -139,6 +188,7 @@ function ApiComponent() {
139
188
  | `redirectUri` | `string` | Yes | Callback URL after login |
140
189
  | `scope` | `string` | No | OAuth2 scopes (default: `openid profile email`) |
141
190
  | `audience` | `string` | No | API audience for access tokens |
191
+ | `silentAuth` | `boolean` | No | Silent session renewal (default: `true`, see below) |
142
192
 
143
193
  ### useAuthU Returns
144
194
 
@@ -1 +1 @@
1
- {"version":3,"file":"AuthUProvider.d.ts","sourceRoot":"","sources":["../src/AuthUProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAIf,MAAM,OAAO,CAAC;AAef,OAAO,KAAK,EAEV,WAAW,EACX,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,YAAY,mDAAgD,CAAC;AAO1E,UAAU,kBAAkB;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAID,wBAAgB,aAAa,CAAC,EAAC,MAAM,EAAE,QAAQ,EAAC,EAAE,kBAAkB,2CAqcnE"}
1
+ {"version":3,"file":"AuthUProvider.d.ts","sourceRoot":"","sources":["../src/AuthUProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AAef,OAAO,KAAK,EAEV,WAAW,EACX,iBAAiB,EAIlB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,YAAY,mDAAgD,CAAC;AAO1E,UAAU,kBAAkB;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAuDD,wBAAgB,aAAa,CAAC,EAAC,MAAM,EAAE,QAAQ,EAAC,EAAE,kBAAkB,2CAwYnE"}
@@ -1,23 +1,77 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useCallback, useEffect, useState } from 'react';
2
+ import { createContext, useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { clearCodeVerifier, clearNonce, clearState, generateCodeChallenge, generateCodeVerifier, generateNonce, generateState, getCodeVerifier, getStoredState, storeCodeVerifier, storeNonce, storeState } from './pkce.js';
4
4
  export const AuthUContext = createContext(null);
5
5
  function buildBaseUrl(domain) {
6
6
  const protocol = domain.startsWith('localhost') ? 'http' : 'https';
7
7
  return `${protocol}://${domain}`;
8
8
  }
9
- const TOKEN_STORAGE_KEY = 'authu_tokens';
9
+ /**
10
+ * Tokens are never written to disk. Up to 1.0.x they lived in localStorage
11
+ * under this key, where any XSS on the host app could read them and replay a
12
+ * 30-day refresh token from anywhere; the key is only kept here so an app
13
+ * upgrading to this version wipes what the previous one left behind.
14
+ */
15
+ const LEGACY_TOKEN_STORAGE_KEY = 'authu_tokens';
16
+ /**
17
+ * Holding the tokens in memory means a reload loses them, so the session is
18
+ * re-derived from the AuthU session cookie instead: one `prompt=none` round
19
+ * trip through /authorize, which returns a fresh code without showing a page
20
+ * when the cookie is still valid, and `error=login_required` when it is not.
21
+ * The attempt is recorded per tab so a signed-out visitor bounces once and
22
+ * then renders anonymously rather than looping through AuthU forever.
23
+ */
24
+ const SILENT_ATTEMPT_KEY = 'authu_silent_attempted';
25
+ const RETURN_TO_KEY = 'authu_return_to';
26
+ const EMPTY_STATE = {
27
+ isLoading: false,
28
+ isAuthenticated: false,
29
+ user: null,
30
+ accessToken: null,
31
+ refreshToken: null,
32
+ expiresAt: null,
33
+ error: null
34
+ };
35
+ function currentLocation() {
36
+ return `${window.location.pathname}${window.location.search}${window.location.hash}`;
37
+ }
38
+ /**
39
+ * Only a same-origin path is ever restored: `//evil.example` is a valid
40
+ * relative URL for the browser but a different origin, so a poisoned entry
41
+ * cannot turn the silent round trip into an open redirect.
42
+ */
43
+ function readReturnTo() {
44
+ const stored = sessionStorage.getItem(RETURN_TO_KEY);
45
+ sessionStorage.removeItem(RETURN_TO_KEY);
46
+ if (!stored || !stored.startsWith('/') || stored.startsWith('//')) {
47
+ return null;
48
+ }
49
+ return stored;
50
+ }
10
51
  export function AuthUProvider({ config, children }) {
11
52
  const [state, setState] = useState({
12
- isLoading: true,
13
- isAuthenticated: false,
14
- user: null,
15
- accessToken: null,
16
- refreshToken: null,
17
- expiresAt: null,
18
- error: null
53
+ ...EMPTY_STATE,
54
+ isLoading: true
19
55
  });
20
- const getAuthorizationUrl = useCallback(async (options) => {
56
+ // The tokens also live in a ref so getAccessToken() and refreshTokens() read
57
+ // the current pair rather than the one captured when the callback was built:
58
+ // two concurrent API calls racing on an expiring token would otherwise both
59
+ // rotate the refresh token, and the loser is now treated as a replay.
60
+ const tokensRef = useRef(null);
61
+ const refreshInFlight = useRef(null);
62
+ const setTokens = useCallback((tokens, user) => {
63
+ tokensRef.current = tokens;
64
+ setState(prev => ({
65
+ isLoading: false,
66
+ isAuthenticated: tokens !== null,
67
+ user: tokens === null ? null : user !== undefined ? user : prev.user,
68
+ accessToken: tokens?.accessToken ?? null,
69
+ refreshToken: tokens?.refreshToken ?? null,
70
+ expiresAt: tokens?.expiresAt ?? null,
71
+ error: null
72
+ }));
73
+ }, []);
74
+ const buildAuthorizationUrl = useCallback(async (options) => {
21
75
  const codeVerifier = generateCodeVerifier();
22
76
  const codeChallenge = await generateCodeChallenge(codeVerifier);
23
77
  const stateValue = generateState();
@@ -44,6 +98,9 @@ export function AuthUProvider({ config, children }) {
44
98
  if (options?.screenHint) {
45
99
  params.set('screen_hint', options.screenHint);
46
100
  }
101
+ if (options?.prompt) {
102
+ params.set('prompt', options.prompt);
103
+ }
47
104
  return `${buildBaseUrl(config.domain)}/authorize?${params.toString()}`;
48
105
  }, [config]);
49
106
  const exchangeCodeForTokensWithVerifier = useCallback(async (code, codeVerifier) => {
@@ -79,22 +136,31 @@ export function AuthUProvider({ config, children }) {
79
136
  };
80
137
  }, []);
81
138
  const login = useCallback(async (options) => {
82
- const url = await getAuthorizationUrl(options);
83
- console.log('[AuthU SDK] login() called, redirecting to:', url);
84
- window.location.href = url;
85
- }, [getAuthorizationUrl]);
86
- const logout = useCallback(() => {
87
- localStorage.removeItem(TOKEN_STORAGE_KEY);
88
- setState({
89
- isLoading: false,
90
- isAuthenticated: false,
91
- user: null,
92
- accessToken: null,
93
- refreshToken: null,
94
- expiresAt: null,
95
- error: null
139
+ sessionStorage.removeItem(SILENT_ATTEMPT_KEY);
140
+ if (options?.returnTo) {
141
+ sessionStorage.setItem(RETURN_TO_KEY, options.returnTo);
142
+ }
143
+ window.location.href = await buildAuthorizationUrl(options);
144
+ }, [buildAuthorizationUrl]);
145
+ /**
146
+ * A local logout is no longer enough: the session now lives in AuthU's own
147
+ * cookie, so clearing memory alone would let the next silent renewal sign
148
+ * the visitor straight back in. The browser is sent to AuthU /logout, which
149
+ * kills the session and bounces back to the app.
150
+ */
151
+ const logout = useCallback((options) => {
152
+ tokensRef.current = null;
153
+ sessionStorage.setItem(SILENT_ATTEMPT_KEY, '1');
154
+ setState(EMPTY_STATE);
155
+ // The destination has to survive AuthU's own check, which only accepts a
156
+ // URI the tenant registered or its home origin: the configured
157
+ // redirectUri is registered by construction, so it is the safe default.
158
+ const params = new URLSearchParams({
159
+ client_id: config.clientId,
160
+ redirect_uri: options?.returnTo || config.redirectUri
96
161
  });
97
- }, []);
162
+ window.location.href = `${buildBaseUrl(config.domain)}/logout?${params.toString()}`;
163
+ }, [config.domain, config.clientId, config.redirectUri]);
98
164
  const fetchUserInfo = useCallback(async (accessToken) => {
99
165
  try {
100
166
  const response = await fetch(`${buildBaseUrl(config.domain)}/oauth/userinfo`, {
@@ -116,206 +182,151 @@ export function AuthUProvider({ config, children }) {
116
182
  }
117
183
  }, [config.domain]);
118
184
  const refreshTokens = useCallback(async () => {
119
- if (!state.refreshToken)
185
+ const refreshToken = tokensRef.current?.refreshToken;
186
+ if (!refreshToken)
120
187
  return false;
121
- try {
122
- const response = await fetch(`${buildBaseUrl(config.domain)}/oauth/token`, {
123
- method: 'POST',
124
- headers: { 'Content-Type': 'application/json' },
125
- body: JSON.stringify({
126
- grant_type: 'refresh_token',
127
- refresh_token: state.refreshToken,
128
- client_id: config.clientId
129
- })
130
- });
131
- if (!response.ok) {
132
- logout();
188
+ if (refreshInFlight.current)
189
+ return refreshInFlight.current;
190
+ const run = async () => {
191
+ try {
192
+ const response = await fetch(`${buildBaseUrl(config.domain)}/oauth/token`, {
193
+ method: 'POST',
194
+ headers: { 'Content-Type': 'application/json' },
195
+ body: JSON.stringify({
196
+ grant_type: 'refresh_token',
197
+ refresh_token: refreshToken,
198
+ client_id: config.clientId
199
+ })
200
+ });
201
+ if (!response.ok) {
202
+ tokensRef.current = null;
203
+ setState(EMPTY_STATE);
204
+ return false;
205
+ }
206
+ const tokens = await response.json();
207
+ setTokens({
208
+ accessToken: tokens.access_token,
209
+ refreshToken: tokens.refresh_token ?? null,
210
+ expiresAt: Date.now() + tokens.expires_in * 1000
211
+ }, tokens.id_token ? parseIdToken(tokens.id_token) : undefined);
212
+ return true;
213
+ }
214
+ catch {
215
+ tokensRef.current = null;
216
+ setState(EMPTY_STATE);
133
217
  return false;
134
218
  }
135
- const tokens = await response.json();
136
- const expiresAt = Date.now() + tokens.expires_in * 1000;
137
- const user = tokens.id_token ? parseIdToken(tokens.id_token) : state.user;
138
- localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify({
139
- accessToken: tokens.access_token,
140
- refreshToken: tokens.refresh_token,
141
- expiresAt
142
- }));
143
- setState(prev => ({
144
- ...prev,
145
- accessToken: tokens.access_token,
146
- refreshToken: tokens.refresh_token,
147
- expiresAt,
148
- user: user || prev.user
149
- }));
150
- return true;
151
- }
152
- catch {
153
- logout();
154
- return false;
155
- }
156
- }, [
157
- state.refreshToken,
158
- state.user,
159
- config.domain,
160
- config.clientId,
161
- logout,
162
- parseIdToken
163
- ]);
219
+ };
220
+ const pending = run().finally(() => {
221
+ refreshInFlight.current = null;
222
+ });
223
+ refreshInFlight.current = pending;
224
+ return pending;
225
+ }, [config.domain, config.clientId, parseIdToken, setTokens]);
164
226
  const getAccessToken = useCallback(async () => {
165
- if (!state.accessToken)
227
+ const tokens = tokensRef.current;
228
+ if (!tokens)
166
229
  return null;
167
- if (state.expiresAt && Date.now() >= state.expiresAt - 60000) {
230
+ if (Date.now() >= tokens.expiresAt - 60000) {
168
231
  const refreshed = await refreshTokens();
169
232
  if (!refreshed)
170
233
  return null;
171
234
  }
172
- return state.accessToken;
173
- }, [state.accessToken, state.expiresAt, refreshTokens]);
235
+ return tokensRef.current?.accessToken ?? null;
236
+ }, [refreshTokens]);
237
+ const configRef = useRef(config);
238
+ configRef.current = config;
239
+ const bootstrapped = useRef(false);
174
240
  useEffect(() => {
175
- const handleCallback = async () => {
176
- console.log('[AuthU SDK] handleCallback starting, URL:', window.location.href);
241
+ if (bootstrapped.current)
242
+ return;
243
+ bootstrapped.current = true;
244
+ // Silent renewal lands on redirectUri, which for most apps is a bare
245
+ // /callback route: the URL is rewritten back to the page the visitor
246
+ // actually asked for. replaceState alone would leave a client-side router
247
+ // still rendering /callback, so a popstate is dispatched to make it
248
+ // re-read the location.
249
+ const restoreUrl = () => {
250
+ const returnTo = readReturnTo();
251
+ window.history.replaceState({}, document.title, returnTo || window.location.pathname);
252
+ if (returnTo) {
253
+ window.dispatchEvent(new PopStateEvent('popstate'));
254
+ }
255
+ };
256
+ const bootstrap = async () => {
257
+ localStorage.removeItem(LEGACY_TOKEN_STORAGE_KEY);
177
258
  const params = new URLSearchParams(window.location.search);
178
259
  const code = params.get('code');
179
260
  const returnedState = params.get('state');
180
261
  const error = params.get('error');
181
- console.log('[AuthU SDK] handleCallback params - code:', !!code, 'state:', returnedState, 'error:', error);
182
262
  if (error) {
183
- setState(prev => ({
184
- ...prev,
185
- isLoading: false,
186
- error: new Error(params.get('error_description') || error)
187
- }));
263
+ const description = params.get('error_description') ||
264
+ params.get('errorDescription') ||
265
+ error;
266
+ restoreUrl();
267
+ // A refused silent renewal is the expected answer for a visitor who is
268
+ // simply not signed in: it is an anonymous render, not an error.
269
+ if (error === 'login_required' || error === 'interaction_required') {
270
+ sessionStorage.setItem(SILENT_ATTEMPT_KEY, '1');
271
+ setState(EMPTY_STATE);
272
+ return;
273
+ }
274
+ setState({ ...EMPTY_STATE, error: new Error(description) });
188
275
  return;
189
276
  }
190
277
  if (code) {
191
278
  const storedState = getStoredState();
192
279
  if (returnedState !== storedState) {
193
- setState(prev => ({
194
- ...prev,
195
- isLoading: false,
280
+ setState({
281
+ ...EMPTY_STATE,
196
282
  error: new Error('Invalid state parameter')
197
- }));
283
+ });
198
284
  return;
199
285
  }
200
- // Get and immediately clear code verifier to prevent duplicate exchanges
201
- // (React StrictMode double-mount protection)
286
+ // Read and clear the verifier in one go: React StrictMode mounts this
287
+ // effect twice, and the second pass must not replay the same code.
202
288
  const codeVerifier = getCodeVerifier();
203
- if (!codeVerifier) {
204
- console.log('[AuthU SDK] No code verifier found, skipping duplicate callback');
289
+ if (!codeVerifier)
205
290
  return;
206
- }
207
291
  clearCodeVerifier();
208
292
  clearState();
209
293
  clearNonce();
210
- // Clear URL immediately to prevent issues on refresh
211
- window.history.replaceState({}, document.title, window.location.pathname);
294
+ restoreUrl();
212
295
  try {
213
296
  const tokens = await exchangeCodeForTokensWithVerifier(code, codeVerifier);
214
- const user = parseIdToken(tokens.id_token);
215
- const expiresAt = Date.now() + tokens.expires_in * 1000;
216
- localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify({
217
- accessToken: tokens.access_token,
218
- refreshToken: tokens.refresh_token,
219
- expiresAt
220
- }));
221
- setState({
222
- isLoading: false,
223
- isAuthenticated: true,
224
- user,
297
+ sessionStorage.removeItem(SILENT_ATTEMPT_KEY);
298
+ setTokens({
225
299
  accessToken: tokens.access_token,
226
- refreshToken: tokens.refresh_token,
227
- expiresAt,
228
- error: null
229
- });
300
+ refreshToken: tokens.refresh_token ?? null,
301
+ expiresAt: Date.now() + tokens.expires_in * 1000
302
+ }, tokens.id_token
303
+ ? parseIdToken(tokens.id_token)
304
+ : await fetchUserInfo(tokens.access_token));
230
305
  }
231
306
  catch (err) {
232
- setState(prev => ({
233
- ...prev,
234
- isLoading: false,
307
+ setState({
308
+ ...EMPTY_STATE,
235
309
  error: err instanceof Error ? err : new Error('Authentication failed')
236
- }));
310
+ });
237
311
  }
238
312
  return;
239
313
  }
240
- const stored = localStorage.getItem(TOKEN_STORAGE_KEY);
241
- console.log('[AuthU SDK] Checking localStorage for tokens:', !!stored);
242
- if (stored) {
243
- try {
244
- const tokens = JSON.parse(stored);
245
- console.log('[AuthU SDK] Found tokens, expiresAt:', tokens.expiresAt, 'now:', Date.now(), 'valid:', tokens.expiresAt > Date.now());
246
- if (tokens.expiresAt > Date.now()) {
247
- const user = await fetchUserInfo(tokens.accessToken);
248
- console.log('[AuthU SDK] Tokens valid, user:', user);
249
- setState({
250
- isLoading: false,
251
- isAuthenticated: true,
252
- user,
253
- accessToken: tokens.accessToken,
254
- refreshToken: tokens.refreshToken,
255
- expiresAt: tokens.expiresAt,
256
- error: null
257
- });
258
- return;
259
- }
260
- if (tokens.refreshToken) {
261
- console.log('[AuthU SDK] Access token expired, attempting refresh...');
262
- try {
263
- const response = await fetch(`${buildBaseUrl(config.domain)}/oauth/token`, {
264
- method: 'POST',
265
- headers: { 'Content-Type': 'application/json' },
266
- body: JSON.stringify({
267
- grant_type: 'refresh_token',
268
- refresh_token: tokens.refreshToken,
269
- client_id: config.clientId
270
- })
271
- });
272
- if (response.ok) {
273
- const newTokens = await response.json();
274
- const expiresAt = Date.now() + newTokens.expires_in * 1000;
275
- const user = newTokens.id_token
276
- ? parseIdToken(newTokens.id_token)
277
- : await fetchUserInfo(newTokens.access_token);
278
- localStorage.setItem(TOKEN_STORAGE_KEY, JSON.stringify({
279
- accessToken: newTokens.access_token,
280
- refreshToken: newTokens.refresh_token,
281
- expiresAt
282
- }));
283
- console.log('[AuthU SDK] Token refresh successful, user:', user);
284
- setState({
285
- isLoading: false,
286
- isAuthenticated: true,
287
- user,
288
- accessToken: newTokens.access_token,
289
- refreshToken: newTokens.refresh_token,
290
- expiresAt,
291
- error: null
292
- });
293
- return;
294
- }
295
- console.log('[AuthU SDK] Token refresh failed, clearing tokens');
296
- localStorage.removeItem(TOKEN_STORAGE_KEY);
297
- }
298
- catch {
299
- console.log('[AuthU SDK] Token refresh error, clearing tokens');
300
- localStorage.removeItem(TOKEN_STORAGE_KEY);
301
- }
302
- }
303
- }
304
- catch {
305
- console.log('[AuthU SDK] Error parsing stored tokens, removing');
306
- localStorage.removeItem(TOKEN_STORAGE_KEY);
307
- }
314
+ if (configRef.current.silentAuth === false ||
315
+ sessionStorage.getItem(SILENT_ATTEMPT_KEY)) {
316
+ setState(EMPTY_STATE);
317
+ return;
308
318
  }
309
- console.log('[AuthU SDK] No valid tokens found, setting isLoading: false');
310
- setState(prev => ({ ...prev, isLoading: false }));
319
+ sessionStorage.setItem(SILENT_ATTEMPT_KEY, '1');
320
+ sessionStorage.setItem(RETURN_TO_KEY, currentLocation());
321
+ window.location.replace(await buildAuthorizationUrl({ prompt: 'none' }));
311
322
  };
312
- handleCallback();
323
+ bootstrap();
313
324
  }, [
325
+ buildAuthorizationUrl,
314
326
  exchangeCodeForTokensWithVerifier,
315
327
  fetchUserInfo,
316
- config.clientId,
317
- config.domain,
318
- parseIdToken
328
+ parseIdToken,
329
+ setTokens
319
330
  ]);
320
331
  const checkEmailExists = useCallback(async (email) => {
321
332
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AA2BA,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG7E;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAMD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../src/pkce.ts"],"names":[],"mappings":"AA2BA,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG7E;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAcD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAExD;AAED,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAE/C;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC"}
package/dist/pkce.js CHANGED
@@ -34,6 +34,14 @@ export function generateState() {
34
34
  export function generateNonce() {
35
35
  return generateRandomString(32);
36
36
  }
37
+ /**
38
+ * The verifier, the state and the nonce have to outlive a full navigation to
39
+ * AuthU and back. They stay in localStorage rather than sessionStorage on
40
+ * purpose: 1.0.10 moved them there after sessionStorage lost them across the
41
+ * cross-domain round trip. They are single-use, consumed within seconds, and
42
+ * cleared as soon as the callback is handled - unlike the tokens, which never
43
+ * touch any storage at all.
44
+ */
37
45
  const STORAGE_KEY_VERIFIER = 'authu_code_verifier';
38
46
  const STORAGE_KEY_STATE = 'authu_state';
39
47
  const STORAGE_KEY_NONCE = 'authu_nonce';
package/dist/types.d.ts CHANGED
@@ -4,6 +4,13 @@ export interface AuthUConfig {
4
4
  redirectUri: string;
5
5
  scope?: string;
6
6
  audience?: string;
7
+ /**
8
+ * Silent session renewal through `/authorize?prompt=none`, on by default.
9
+ * Tokens are held in memory only, so this round trip is what carries a
10
+ * session across a page reload. Turning it off means every reload starts
11
+ * signed out.
12
+ */
13
+ silentAuth?: boolean;
7
14
  }
8
15
  export interface AuthUUser {
9
16
  sub: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,aAAa,KAAK,IAAI,CAAC;IAC1C,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authu/react",
3
- "version": "1.0.64",
3
+ "version": "1.0.66",
4
4
  "description": "React SDK for AuthU - Centralized Multi-Tenant Authentication Service",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",