Skip to content

Commit e167d9c

Browse files
authored
Switch from react-auth0-components to auth0/auth0-react (#3425)
The former has been unmaintained for 5 years. I was looking at why the login button wasn't working on first load and fell into a rabbit hole... Regarding the update, this is fairly "straightforward". We now use the context provided with the auth0-react library instead of reinventing the wheel and having our own. The auth0 library also sets up its own callback things so we don't need to set that up manually anymore either. The main pain point was figuring out how to know within the axios interceptors whether we were logged in or not as calling `getAccessTokenSilently` will try to log you in if you're not (do not ask me why... I already didn't have hair anymore before but it's way worse now). Turned out that we could use `getIdTokenClaims` for this. I would've loved to set/unset the interceptor based on the `isAuthenticated` boolean we can get from auth0 but the interceptor also sets the base URL so we can't do that or it breaks the heartbeat request.
1 parent 2b7d705 commit e167d9c

File tree

23 files changed

+186
-340
lines changed

23 files changed

+186
-340
lines changed

ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"comment2": "IMPORTANT! react-virtualized is held at 9.21.1 due to https://github.com/bvaughn/react-virtualized/issues/1226 breaking scrolling to anchors on ListRules",
1818
"comment3": "see also https://github.com/bvaughn/react-virtualized/issues/1179#issuecomment-572854217",
1919
"dependencies": {
20+
"@auth0/auth0-react": "^2.3.0",
2021
"@date-io/date-fns": "^1.3.13",
2122
"@hot-loader/react-dom": "^16.4.0",
2223
"@material-ui/core": "^4.11.2",
@@ -40,7 +41,6 @@
4041
"qs": "^6.10.1",
4142
"ramda": "^0.27.1",
4243
"react": "^16.13.1",
43-
"react-auth0-components": "^1.0.4",
4444
"react-codemirror2": "^7.2.1",
4545
"react-diff-view": "^2.4.7",
4646
"react-dom": "^16.13.1",

ui/src/App.jsx

Lines changed: 19 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,34 @@
11
import { hot } from 'react-hot-loader/root';
2-
import React, { Fragment, useEffect, useState } from 'react';
3-
import { Authorize } from 'react-auth0-components';
2+
import React, { Fragment } from 'react';
43
import CssBaseline from '@material-ui/core/CssBaseline';
54
import { ThemeProvider } from '@material-ui/styles';
6-
import axios from 'axios';
7-
import { AuthContext } from './utils/AuthContext';
8-
import { BASE_URL, USER_SESSION } from './utils/constants';
5+
import { Auth0Provider } from '@auth0/auth0-react';
96
import theme from './theme';
107
import Main from './Main';
118

12-
axios.interceptors.request.use(config => {
13-
const result = config;
14-
15-
if (!config.url.startsWith('http')) {
16-
let accessToken = null;
17-
18-
result.baseURL = BASE_URL;
19-
20-
try {
21-
({ accessToken } = JSON.parse(
22-
localStorage.getItem(USER_SESSION)
23-
).authResult);
24-
} catch {
25-
accessToken = null;
26-
}
27-
28-
if (accessToken) {
29-
result.headers.Authorization = `Bearer ${accessToken}`;
30-
}
31-
}
32-
33-
return result;
34-
});
35-
axios.interceptors.response.use(
36-
response => response,
37-
error => {
38-
const errorMsg = error.response
39-
? error.response.data.exception || error.response.data.detail || null
40-
: error.message;
41-
42-
// If we found a more detailed error message
43-
// raise an Error with that instead.
44-
if (errorMsg !== null) {
45-
throw new Error(errorMsg);
46-
}
47-
48-
throw error;
49-
}
50-
);
51-
529
const App = () => {
53-
const userSession = localStorage.getItem(USER_SESSION);
54-
const [authorize, setAuthorize] = useState(Boolean(userSession));
55-
const [authContext, setAuthContext] = useState({
56-
authorize: () => setAuthorize(true),
57-
unauthorize: () => {
58-
setAuthorize(false);
59-
setAuthContext({
60-
...authContext,
61-
user: null,
62-
});
63-
},
64-
user: null,
65-
});
66-
// Wait until authorization is done before rendering
67-
// to make sure users who are logged in are able to access protected views
68-
const [ready, setReady] = useState(false);
69-
70-
// When the user is not logged in, handleAuthorize and handleError will never
71-
// be triggered since the `authorize` prop is set to `false`.
72-
useEffect(() => {
73-
if (!userSession) {
74-
setReady(true);
75-
}
76-
}, [userSession]);
77-
78-
const handleAuthorize = user => {
79-
setAuthContext({
80-
...authContext,
81-
user,
82-
});
83-
setReady(true);
84-
};
85-
86-
const handleError = () => {
87-
setAuthContext({
88-
...authContext,
89-
user: null,
90-
});
91-
setReady(true);
92-
};
93-
94-
const render = () => <Main />;
95-
9610
return (
9711
<Fragment>
9812
<CssBaseline />
99-
<AuthContext.Provider value={authContext}>
13+
<Auth0Provider
14+
domain={process.env.AUTH0_DOMAIN}
15+
clientId={process.env.AUTH0_CLIENT_ID}
16+
redirectUri={process.env.AUTH0_REDIRECT_URI}
17+
audience={process.env.AUTH0_AUDIENCE}
18+
scope={process.env.AUTH0_SCOPE}
19+
authorizationParams={{
20+
audience: process.env.AUTH0_AUDIENCE,
21+
scope: process.env.AUTH0_SCOPE,
22+
}}
23+
leeway={30}
24+
useRefreshTokens
25+
useRefreshTokensFallback
26+
cacheLocation="localstorage"
27+
responseType={process.env.AUTH0_RESPONSE_TYPE}>
10028
<ThemeProvider theme={theme}>
101-
<Authorize
102-
authorize={authorize}
103-
onAuthorize={handleAuthorize}
104-
onError={handleError}
105-
popup
106-
domain={process.env.AUTH0_DOMAIN}
107-
clientID={process.env.AUTH0_CLIENT_ID}
108-
audience={process.env.AUTH0_AUDIENCE}
109-
redirectUri={process.env.AUTH0_REDIRECT_URI}
110-
responseType={process.env.AUTH0_RESPONSE_TYPE}
111-
scope={process.env.AUTH0_SCOPE}
112-
render={ready ? render : null}
113-
leeway={30}
114-
/>
29+
<Main />
11530
</ThemeProvider>
116-
</AuthContext.Provider>
31+
</Auth0Provider>
11732
</Fragment>
11833
);
11934
};

ui/src/Main.jsx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react';
22
import { BrowserRouter, Switch } from 'react-router-dom';
33
import { makeStyles } from '@material-ui/styles';
44
import axios from 'axios';
5+
import { useAuth0 } from '@auth0/auth0-react';
6+
import { BASE_URL } from './utils/constants';
57
import Dashboard from './components/Dashboard';
68
import ErrorPanel from './components/ErrorPanel';
79
import RouteWithProps from './components/RouteWithProps';
@@ -31,7 +33,57 @@ const useStyles = makeStyles({
3133
},
3234
});
3335

36+
function setupAxiosInterceptors(getAccessTokenSilently, getIdTokenClaims) {
37+
axios.interceptors.request.use(async config => {
38+
const result = config;
39+
40+
if (!config.url.startsWith('http')) {
41+
result.baseURL = BASE_URL;
42+
43+
const claims = await getIdTokenClaims();
44+
45+
if (claims) {
46+
const accessToken = await getAccessTokenSilently();
47+
48+
result.headers.Authorization = `Bearer ${accessToken}`;
49+
}
50+
}
51+
52+
return result;
53+
});
54+
55+
axios.interceptors.response.use(
56+
response => response,
57+
error => {
58+
const errorMsg = error.response
59+
? error.response.data.exception || error.response.data.detail || null
60+
: error.message;
61+
62+
// If we found a more detailed error message
63+
// raise an Error with that instead.
64+
if (errorMsg !== null) {
65+
throw new Error(errorMsg);
66+
}
67+
68+
throw error;
69+
}
70+
);
71+
}
72+
3473
function Main() {
74+
const { isLoading, getAccessTokenSilently, getIdTokenClaims } = useAuth0();
75+
const [isReady, setReady] = useState(false);
76+
77+
useEffect(() => {
78+
setupAxiosInterceptors(getAccessTokenSilently, getIdTokenClaims);
79+
}, [getAccessTokenSilently, isLoading]);
80+
81+
useEffect(() => {
82+
if (!isReady && !isLoading) {
83+
setReady(true);
84+
}
85+
}, [isLoading]);
86+
3587
useStyles();
3688
const [backendError, setBackendError] = useState('');
3789

@@ -46,6 +98,10 @@ function Main() {
4698
);
4799
}, []);
48100

101+
if (!isReady) {
102+
return <div>Loading...</div>;
103+
}
104+
49105
return backendError ? (
50106
<BrowserRouter>
51107
<Dashboard title="Error" disabled>

ui/src/components/Dashboard/SettingsMenu.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import { withAuth0 } from '@auth0/auth0-react';
12
import React, { useState, Fragment } from 'react';
23
import { makeStyles } from '@material-ui/styles';
34
import Menu from '@material-ui/core/Menu';
45
import MenuItem from '@material-ui/core/MenuItem';
56
import IconButton from '@material-ui/core/IconButton';
67
import SettingsOutlineIcon from 'mdi-react/SettingsOutlineIcon';
78
import Link from '../../utils/Link';
8-
import { withUser } from '../../utils/AuthContext';
99
import menuItems from './menuItems';
1010

1111
const useStyles = makeStyles(theme => ({
@@ -26,7 +26,7 @@ const useStyles = makeStyles(theme => ({
2626
},
2727
}));
2828

29-
function SettingsMenu({ user, disabled }) {
29+
function SettingsMenu({ auth0, disabled }) {
3030
const classes = useStyles();
3131
const [anchorEl, setAnchorEl] = useState(null);
3232
const handleMenuOpen = e => setAnchorEl(e.currentTarget);
@@ -35,7 +35,7 @@ function SettingsMenu({ user, disabled }) {
3535
return (
3636
<Fragment>
3737
<IconButton
38-
disabled={!user || disabled}
38+
disabled={!auth0.user || disabled}
3939
className={classes.settings}
4040
aria-haspopup="true"
4141
aria-controls="user-menu"
@@ -44,7 +44,7 @@ function SettingsMenu({ user, disabled }) {
4444
<SettingsOutlineIcon
4545
size={24}
4646
className={
47-
user && !disabled
47+
auth0.user && !disabled
4848
? classes.settingsIcon
4949
: classes.settingsIconDisabled
5050
}
@@ -75,4 +75,4 @@ function SettingsMenu({ user, disabled }) {
7575
);
7676
}
7777

78-
export default withUser(SettingsMenu);
78+
export default withAuth0(SettingsMenu);

ui/src/components/Dashboard/UserMenu.jsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withAuth0 } from '@auth0/auth0-react';
12
import React, { useState, Fragment } from 'react';
23
import { makeStyles } from '@material-ui/styles';
34
import Avatar from '@material-ui/core/Avatar';
@@ -8,8 +9,6 @@ import LogoutVariantIcon from 'mdi-react/LogoutVariantIcon';
89
import ContentCopyIcon from 'mdi-react/ContentCopyIcon';
910
import copy from 'clipboard-copy';
1011
import Button from '../Button';
11-
import { USER_SESSION } from '../../utils/constants';
12-
import { withUser } from '../../utils/AuthContext';
1312

1413
const useStyles = makeStyles(theme => ({
1514
avatar: {
@@ -20,43 +19,45 @@ const useStyles = makeStyles(theme => ({
2019
}));
2120

2221
function UserMenu(props) {
23-
const { user, onAuthorize, onUnauthorize } = props;
22+
const { auth0 } = props;
2423
const classes = useStyles();
2524
const [anchorEl, setAnchorEl] = useState(null);
2625
const handleMenuOpen = e => setAnchorEl(e.currentTarget);
2726
const handleMenuClose = () => setAnchorEl(null);
2827
const handleLogoutClick = () => {
2928
handleMenuClose();
30-
onUnauthorize();
29+
auth0.logout({
30+
openUrl: false,
31+
});
3132
};
3233

33-
const handleCopyAccessToken = () => {
34-
const { accessToken } = JSON.parse(
35-
localStorage.getItem(USER_SESSION)
36-
).authResult;
34+
const handleCopyAccessToken = async () => {
35+
const accessToken = await auth0.getAccessTokenSilently();
3736

3837
copy(accessToken);
3938
handleMenuClose();
4039
};
4140

41+
const handleLogin = auth0.loginWithPopup;
42+
4243
return (
4344
<Fragment>
44-
{user ? (
45+
{auth0.user ? (
4546
<IconButton
4647
className={classes.avatar}
4748
aria-haspopup="true"
4849
aria-controls="user-menu"
4950
aria-label="user menu"
5051
onClick={handleMenuOpen}>
51-
{user.picture ? (
52-
<Avatar alt={user.nickname} src={user.picture} />
52+
{auth0.user.picture ? (
53+
<Avatar alt={auth0.user.nickname} src={auth0.user.picture} />
5354
) : (
54-
<Avatar alt={user.name}>{user.name[0]}</Avatar>
55+
<Avatar alt={auth0.user.name}>{auth0.user.name[0]}</Avatar>
5556
)}
5657
</IconButton>
5758
) : (
5859
<Button
59-
onClick={onAuthorize}
60+
onClick={handleLogin}
6061
size="small"
6162
variant="contained"
6263
color="secondary">
@@ -83,4 +84,4 @@ function UserMenu(props) {
8384
);
8485
}
8586

86-
export default withUser(UserMenu);
87+
export default withAuth0(UserMenu);

0 commit comments

Comments
 (0)