Skip to content

Commit 83cf1b4

Browse files
authored
[SDK-3784] Update to spa-js v2 (#432)
1 parent ce0082e commit 83cf1b4

File tree

22 files changed

+13008
-546
lines changed

22 files changed

+13008
-546
lines changed

EXAMPLES.md

Lines changed: 18 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@ const Posts = () => {
6262
(async () => {
6363
try {
6464
const token = await getAccessTokenSilently({
65-
audience: 'https://api.example.com/',
66-
scope: 'read:posts',
65+
authorizationParams: {
66+
audience: 'https://api.example.com/',
67+
scope: 'read:posts',
68+
},
6769
});
6870
const response = await fetch('https://api.example.com/posts', {
6971
headers: {
@@ -72,6 +74,7 @@ const Posts = () => {
7274
});
7375
setPosts(await response.json());
7476
} catch (e) {
77+
// Handle errors such as `login_required` and `consent_required` by re-prompting for a login
7578
console.error(e);
7679
}
7780
})();
@@ -132,7 +135,9 @@ export default function App() {
132135
<Auth0ProviderWithRedirectCallback
133136
domain="YOUR_AUTH0_DOMAIN"
134137
clientId="YOUR_AUTH0_CLIENT_ID"
135-
redirectUri={window.location.origin}
138+
authorizationParams={{
139+
redirect_uri: window.location.origin,
140+
}}
136141
>
137142
<Routes>
138143
<Route path="/" exact />
@@ -171,8 +176,10 @@ export const wrapRootElement = ({ element }) => {
171176
<Auth0Provider
172177
domain="YOUR_AUTH0_DOMAIN"
173178
clientId="YOUR_AUTH0_CLIENT_ID"
174-
redirectUri={window.location.origin}
175179
onRedirectCallback={onRedirectCallback}
180+
authorizationParams={{
181+
redirect_uri: window.location.origin,
182+
}}
176183
>
177184
{element}
178185
</Auth0Provider>
@@ -228,10 +235,11 @@ class MyApp extends App {
228235
<Auth0Provider
229236
domain="YOUR_AUTH0_DOMAIN"
230237
clientId="YOUR_AUTH0_CLIENT_ID"
231-
redirectUri={
232-
typeof window !== 'undefined' ? window.location.origin : undefined
233-
}
234238
onRedirectCallback={onRedirectCallback}
239+
authorizationParams={{
240+
redirect_uri:
241+
typeof window !== 'undefined' ? window.location.origin : undefined,
242+
}}
235243
>
236244
<Component {...pageProps} />
237245
</Auth0Provider>
@@ -265,104 +273,6 @@ export default withAuthenticationRequired(Profile);
265273
266274
See [Next.js example app](./examples/nextjs-app)
267275
268-
## Create a `useApi` hook for accessing protected APIs with an access token.
269-
270-
```js
271-
// use-api.js
272-
import { useEffect, useState } from 'react';
273-
import { useAuth0 } from '@auth0/auth0-react';
274-
275-
export const useApi = (url, options = {}) => {
276-
const { getAccessTokenSilently } = useAuth0();
277-
const [state, setState] = useState({
278-
error: null,
279-
loading: true,
280-
data: null,
281-
});
282-
const [refreshIndex, setRefreshIndex] = useState(0);
283-
284-
useEffect(() => {
285-
(async () => {
286-
try {
287-
const { audience, scope, ...fetchOptions } = options;
288-
const accessToken = await getAccessTokenSilently({ audience, scope });
289-
const res = await fetch(url, {
290-
...fetchOptions,
291-
headers: {
292-
...fetchOptions.headers,
293-
// Add the Authorization header to the existing headers
294-
Authorization: `Bearer ${accessToken}`,
295-
},
296-
});
297-
setState({
298-
...state,
299-
data: await res.json(),
300-
error: null,
301-
loading: false,
302-
});
303-
} catch (error) {
304-
setState({
305-
...state,
306-
error,
307-
loading: false,
308-
});
309-
}
310-
})();
311-
}, [refreshIndex]);
312-
313-
return {
314-
...state,
315-
refresh: () => setRefreshIndex(refreshIndex + 1),
316-
};
317-
};
318-
```
319-
320-
Then use it for accessing protected APIs from your components:
321-
322-
```jsx
323-
// users.js
324-
import { useApi } from './use-api';
325-
326-
export const Profile = () => {
327-
const opts = {
328-
audience: 'https://api.example.com/',
329-
scope: 'read:users',
330-
};
331-
const { login, getAccessTokenWithPopup } = useAuth0();
332-
const {
333-
loading,
334-
error,
335-
refresh,
336-
data: users,
337-
} = useApi('https://api.example.com/users', opts);
338-
const getTokenAndTryAgain = async () => {
339-
await getAccessTokenWithPopup(opts);
340-
refresh();
341-
};
342-
if (loading) {
343-
return <div>Loading...</div>;
344-
}
345-
if (error) {
346-
if (error.error === 'login_required') {
347-
return <button onClick={() => login(opts)}>Login</button>;
348-
}
349-
if (error.error === 'consent_required') {
350-
return (
351-
<button onClick={getTokenAndTryAgain}>Consent to reading users</button>
352-
);
353-
}
354-
return <div>Oops {error.message}</div>;
355-
}
356-
return (
357-
<ul>
358-
{users.map((user, index) => {
359-
return <li key={index}>{user}</li>;
360-
})}
361-
</ul>
362-
);
363-
};
364-
```
365-
366276
## Use with Auth0 organizations
367277
368278
[Organizations](https://auth0.com/docs/organizations) is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications. Note that Organizations is currently only available to customers on our Enterprise and Startup subscription plans.
@@ -375,8 +285,10 @@ ReactDOM.render(
375285
<Auth0Provider
376286
domain="YOUR_AUTH0_DOMAIN"
377287
clientId="YOUR_AUTH0_CLIENT_ID"
378-
redirectUri={window.location.origin}
379288
organization="YOUR_ORGANIZATION_ID"
289+
authorizationParams={{
290+
redirectUri: window.location.origin,
291+
}}
380292
>
381293
<App />
382294
</Auth0Provider>

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ ReactDOM.render(
6969
<Auth0Provider
7070
domain="YOUR_AUTH0_DOMAIN"
7171
clientId="YOUR_AUTH0_CLIENT_ID"
72-
redirectUri={window.location.origin}
72+
authorizationParams={{
73+
redirect_uri: window.location.origin,
74+
}}
7375
>
7476
<App />
7577
</Auth0Provider>,

__tests__/auth-provider.test.tsx

Lines changed: 30 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import pkg from '../package.json';
1414
import { createWrapper } from './helpers';
1515
import { Auth0Provider, useAuth0 } from '../src';
1616

17-
const clientMock = jest.mocked(new Auth0Client({ client_id: '', domain: '' }));
17+
const clientMock = jest.mocked(new Auth0Client({ clientId: '', domain: '' }));
1818

1919
describe('Auth0Provider', () => {
2020
afterEach(() => {
@@ -35,21 +35,25 @@ describe('Auth0Provider', () => {
3535
const opts = {
3636
clientId: 'foo',
3737
domain: 'bar',
38-
redirectUri: 'baz',
39-
maxAge: 'qux',
40-
extra_param: '__test_extra_param__',
38+
authorizationParams: {
39+
redirect_uri: 'baz',
40+
max_age: 'qux',
41+
extra_param: '__test_extra_param__',
42+
},
4143
};
4244
const wrapper = createWrapper(opts);
4345
const { waitForNextUpdate } = renderHook(() => useContext(Auth0Context), {
4446
wrapper,
4547
});
4648
expect(Auth0Client).toHaveBeenCalledWith(
4749
expect.objectContaining({
48-
client_id: 'foo',
50+
clientId: 'foo',
4951
domain: 'bar',
50-
redirect_uri: 'baz',
51-
max_age: 'qux',
52-
extra_param: '__test_extra_param__',
52+
authorizationParams: {
53+
redirect_uri: 'baz',
54+
max_age: 'qux',
55+
extra_param: '__test_extra_param__',
56+
},
5357
})
5458
);
5559
await waitForNextUpdate();
@@ -228,41 +232,6 @@ describe('Auth0Provider', () => {
228232
expect(result.current.error).not.toBeDefined();
229233
});
230234

231-
it('should call through to buildAuthorizeUrl method', async () => {
232-
const wrapper = createWrapper();
233-
const { waitForNextUpdate, result } = renderHook(
234-
() => useContext(Auth0Context),
235-
{ wrapper }
236-
);
237-
await waitForNextUpdate();
238-
expect(result.current.buildAuthorizeUrl).toBeInstanceOf(Function);
239-
240-
await result.current.buildAuthorizeUrl({
241-
redirectUri: '__redirect_uri__',
242-
});
243-
expect(clientMock.buildAuthorizeUrl).toHaveBeenCalledWith({
244-
redirect_uri: '__redirect_uri__',
245-
});
246-
});
247-
248-
it('should call through to buildLogoutUrl method', async () => {
249-
const wrapper = createWrapper();
250-
const { waitForNextUpdate, result } = renderHook(
251-
() => useContext(Auth0Context),
252-
{ wrapper }
253-
);
254-
await waitForNextUpdate();
255-
expect(result.current.buildLogoutUrl).toBeInstanceOf(Function);
256-
257-
const logoutOptions = {
258-
returnTo: '/',
259-
client_id: 'blah',
260-
federated: false,
261-
};
262-
result.current.buildLogoutUrl(logoutOptions);
263-
expect(clientMock.buildLogoutUrl).toHaveBeenCalledWith(logoutOptions);
264-
});
265-
266235
it('should login with a popup', async () => {
267236
clientMock.getUser.mockResolvedValue(undefined);
268237
const wrapper = createWrapper();
@@ -320,10 +289,14 @@ describe('Auth0Provider', () => {
320289
await waitForNextUpdate();
321290
expect(result.current.loginWithRedirect).toBeInstanceOf(Function);
322291
await result.current.loginWithRedirect({
323-
redirectUri: '__redirect_uri__',
292+
authorizationParams: {
293+
redirect_uri: '__redirect_uri__',
294+
},
324295
});
325296
expect(clientMock.loginWithRedirect).toHaveBeenCalledWith({
326-
redirect_uri: '__redirect_uri__',
297+
authorizationParams: {
298+
redirect_uri: '__redirect_uri__',
299+
},
327300
});
328301
});
329302

@@ -346,28 +319,7 @@ describe('Auth0Provider', () => {
346319
expect(result.current.user).toBe(user);
347320
});
348321

349-
it('should update state for local logouts', async () => {
350-
const user = { name: '__test_user__' };
351-
clientMock.getUser.mockResolvedValue(user);
352-
const wrapper = createWrapper();
353-
const { waitForNextUpdate, result } = renderHook(
354-
() => useContext(Auth0Context),
355-
{ wrapper }
356-
);
357-
await waitForNextUpdate();
358-
expect(result.current.isAuthenticated).toBe(true);
359-
expect(result.current.user).toBe(user);
360-
act(() => {
361-
result.current.logout({ localOnly: true });
362-
});
363-
expect(clientMock.logout).toHaveBeenCalledWith({
364-
localOnly: true,
365-
});
366-
expect(result.current.isAuthenticated).toBe(false);
367-
expect(result.current.user).toBeUndefined();
368-
});
369-
370-
it('should update state for local logouts with async cache', async () => {
322+
it('should update state when using onRedirect', async () => {
371323
const user = { name: '__test_user__' };
372324
clientMock.getUser.mockResolvedValue(user);
373325
// get logout to return a Promise to simulate async cache.
@@ -380,7 +332,8 @@ describe('Auth0Provider', () => {
380332
await waitForNextUpdate();
381333
expect(result.current.isAuthenticated).toBe(true);
382334
await act(async () => {
383-
await result.current.logout({ localOnly: true });
335+
// eslint-disable-next-line @typescript-eslint/no-empty-function
336+
await result.current.logout({ onRedirect: async () => {} });
384337
});
385338
expect(result.current.isAuthenticated).toBe(false);
386339
});
@@ -895,15 +848,20 @@ describe('Auth0Provider', () => {
895848
wrapper,
896849
});
897850

898-
await expect(
899-
auth0ContextRender.result.current.getIdTokenClaims
900-
).toThrowError('You forgot to wrap your component in <Auth0Provider>.');
851+
await act(async () => {
852+
await expect(
853+
auth0ContextRender.result.current.getIdTokenClaims
854+
).toThrowError('You forgot to wrap your component in <Auth0Provider>.');
855+
});
901856

902857
const customContextRender = renderHook(() => useContext(context), {
903858
wrapper,
904859
});
905860

906-
const claims = await customContextRender.result.current.getIdTokenClaims();
861+
let claims;
862+
await act(async () => {
863+
claims = await customContextRender.result.current.getIdTokenClaims();
864+
});
907865
expect(clientMock.getIdTokenClaims).toHaveBeenCalled();
908866
expect(claims).toStrictEqual({
909867
claim: '__test_claim__',

__tests__/with-authentication-required.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Auth0Client, User } from '@auth0/auth0-spa-js';
66
import Auth0Provider from '../src/auth0-provider';
77
import { Auth0ContextInterface, initialContext } from '../src/auth0-context';
88

9-
const mockClient = jest.mocked(new Auth0Client({ client_id: '', domain: '' }));
9+
const mockClient = jest.mocked(new Auth0Client({ clientId: '', domain: '' }));
1010

1111
describe('withAuthenticationRequired', () => {
1212
it('should block access to a private component when not authenticated', async () => {

examples/cra-react-router/src/Nav.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export function Nav() {
3636
<button
3737
className="btn btn-outline-secondary"
3838
id="logout"
39-
onClick={() => logout({ returnTo: window.location.origin })}
39+
onClick={() =>
40+
logout({ logoutParams: { returnTo: window.location.origin } })
41+
}
4042
>
4143
logout
4244
</button>

examples/cra-react-router/src/Users.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import { Error } from './Error';
66
const PORT = process.env.REACT_APP_API_PORT || 3001;
77

88
export function Users() {
9-
const { loading, error, data: users = [] } = useApi(
10-
`http://localhost:${PORT}/users`,
11-
{
12-
audience: process.env.REACT_APP_AUDIENCE,
13-
scope: 'read:users',
14-
}
15-
);
9+
const {
10+
loading,
11+
error,
12+
data: users = [],
13+
} = useApi(`http://localhost:${PORT}/users`, {
14+
audience: process.env.REACT_APP_AUDIENCE,
15+
scope: 'profile email read:users',
16+
});
1617

1718
if (loading) {
1819
return <Loading />;

0 commit comments

Comments
 (0)