remove localStorage usage with auth0 (#1690)

Auth0 best practices recommend to not store tokens in localStorage. [Reference](https://auth0.com/docs/security/store-tokens#don-t-store-tokens-in-local-storage)

Updated code to make use of [checkSession](https://auth0.com/docs/libraries/auth0js/v9#using-checksession-to-acquire-new-tokens) which makes use of cookies.
This commit is contained in:
Praveen Durairaj 2019-03-13 12:34:36 +05:30 committed by Shahidh K Muhammed
parent 50b02e950b
commit 36cb6f8b13
8 changed files with 150 additions and 106 deletions

View File

@ -5,35 +5,25 @@ import { WebSocketLink } from "apollo-link-ws";
import { split } from "apollo-link";
import { getMainDefinition } from "apollo-utilities";
import { SubscriptionClient } from "subscriptions-transport-ws";
import { setContext } from "apollo-link-context";
import { GRAPHQL_URL, REALTIME_GRAPHQL_URL } from "./utils/constants";
import auth from "./components/Auth/Auth";
const getHeaders = () => {
const token = localStorage.getItem("auth0:id_token");
const headers = {
authorization: token ? `Bearer ${token}` : ""
};
const headers = {};
const token = auth.getIdToken();
if (token) {
headers.authorization = `Bearer ${token}`;
}
return headers;
};
const makeApolloClient = () => {
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("auth0:id_token");
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ""
}
};
});
const token = localStorage.getItem("auth0:id_token");
// Create an http link:
const httpLink = new HttpLink({
uri: GRAPHQL_URL,
fetch,
headers: getHeaders(token)
headers: getHeaders()
});
// Create a WebSocket link:
@ -41,8 +31,13 @@ const makeApolloClient = () => {
new SubscriptionClient(REALTIME_GRAPHQL_URL, {
reconnect: true,
timeout: 30000,
connectionParams: {
headers: getHeaders(token)
connectionParams: () => {
return { headers: getHeaders() };
},
connectionCallback: err => {
if (err) {
wsLink.subscriptionClient.close(false, false);
}
}
})
);
@ -59,7 +54,7 @@ const makeApolloClient = () => {
);
const client = new ApolloClient({
link: authLink.concat(link),
link: link,
cache: new InMemoryCache({
addTypename: true
})

View File

@ -1,63 +1,41 @@
import history from "../../utils/history";
import auth0 from "auth0-js";
import gql from "graphql-tag";
import { AUTH_CONFIG } from "./auth0-variables";
export default class Auth {
class Auth {
auth0 = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: "token id_token",
scope: "openid profile"
});
constructor() {
this.accessToken = null;
this.idToken = null;
this.expiresAt = null;
this.sub = null;
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.handleAuthentication = this.handleAuthentication.bind(this);
this.isAuthenticated = this.isAuthenticated.bind(this);
this.getAccessToken = this.getAccessToken.bind(this);
this.getIdToken = this.getIdToken.bind(this);
this.renewSession = this.renewSession.bind(this);
}
login() {
this.auth0.authorize();
}
handleAuthentication = client => {
handleAuthentication = () => {
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
// store in db
this.auth0.client.userInfo(authResult.accessToken, function(err, user) {
// Now you have the user's information
client
.mutate({
mutation: gql`
mutation($userId: String!, $nickname: String) {
insert_users(
objects: [{ auth0_id: $userId, name: $nickname }]
on_conflict: {
constraint: users_pkey
update_columns: [last_seen, name]
}
) {
affected_rows
}
}
`,
variables: {
userId: user.sub,
nickname: user.nickname
}
})
.then(() => {
// history.replace("/home");
window.location.href = "/home";
})
.catch(error => {
console.error(error);
// alert(JSON.stringify(error));
});
// The code to insert this user info to db has been handled at Auth0 Rule.
});
} else if (err) {
history.replace("/home");
@ -69,34 +47,70 @@ export default class Auth {
};
setSession(authResult) {
// Set isLoggedIn flag in localStorage
localStorage.setItem("isLoggedIn", "true");
// Set the time that the access token will expire at
let expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
);
localStorage.setItem("auth0:access_token", authResult.accessToken);
localStorage.setItem("auth0:id_token", authResult.idToken);
localStorage.setItem("auth0:expires_at", expiresAt);
localStorage.setItem("auth0:id_token:sub", authResult.idTokenPayload.sub);
let expiresAt = authResult.expiresIn * 1000 + new Date().getTime();
this.accessToken = authResult.accessToken;
this.idToken = authResult.idToken;
this.expiresAt = expiresAt;
this.sub = authResult.idTokenPayload.sub;
// navigate to the home route
history.replace("/home");
// window.location.href="/home";
}
getAccessToken() {
return this.accessToken;
}
getIdToken() {
return this.idToken;
}
getSub() {
return this.sub;
}
renewSession() {
const _this = this;
return new Promise((resolve, reject) => {
_this.auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
_this.setSession(authResult);
resolve(authResult);
} else if (err) {
_this.logout();
reject(err);
}
});
});
}
logout() {
// Clear access token and ID token from local storage
localStorage.removeItem("auth0:access_token");
localStorage.removeItem("auth0:id_token");
localStorage.removeItem("auth0:expires_at");
localStorage.removeItem("auth0:id_token:sub");
// Remove tokens and expiry time
this.accessToken = null;
this.idToken = null;
this.expiresAt = 0;
// Remove isLoggedIn flag from localStorage
localStorage.removeItem("isLoggedIn");
// navigate to the home route
history.replace("/home");
history.replace("/");
// window.location.href="/home";
}
isAuthenticated() {
// Check whether the current time is past the
// access token's expiry time
let expiresAt = JSON.parse(localStorage.getItem("auth0:expires_at"));
let expiresAt = this.expiresAt;
return new Date().getTime() < expiresAt;
}
}
const auth = new Auth();
export default auth;

View File

@ -7,7 +7,13 @@ import TodoPublicWrapper from "../Todo/TodoPublicWrapper";
import TodoPrivateWrapper from "../Todo/TodoPrivateWrapper";
import OnlineUsers from "../OnlineUsers/OnlineUsers";
import { Navbar, Button } from "react-bootstrap";
import auth from "../Auth/Auth";
class App extends Component {
constructor() {
super();
this.state = { session: false };
}
login() {
this.props.auth.login();
}
@ -15,7 +21,7 @@ class App extends Component {
this.props.auth.logout();
}
updateLastSeen = () => {
const userId = localStorage.getItem("auth0:id_token:sub");
const userId = auth.sub;
const timestamp = moment().format();
if (this.props.client) {
this.props.client
@ -44,13 +50,25 @@ class App extends Component {
}
};
componentDidMount() {
// eslint-disable-next-line
const lastSeenMutation = setInterval(this.updateLastSeen.bind(this), 5000);
const { renewSession } = auth;
if (localStorage.getItem("isLoggedIn") === "true") {
// eslint-disable-next-line
const lastSeenMutation = setInterval(
this.updateLastSeen.bind(this),
5000
);
renewSession().then(data => {
this.setState({ session: true });
});
} else {
window.location.href = "/";
}
}
render() {
const { isAuthenticated } = this.props.auth;
if (!isAuthenticated()) {
window.location.href = "/";
if (!this.state.session) {
return <div>Loading</div>;
}
return (
<div>
@ -87,13 +105,19 @@ class App extends Component {
<div className="col-md-6 col-sm-12">
<div className="wd95 addPaddTopBottom">
<div className="sectionHeader">Personal todos</div>
<TodoPrivateWrapper client={this.props.client} />
<TodoPrivateWrapper
client={this.props.client}
userId={auth.getSub()}
/>
</div>
</div>
<div className="col-xs-12 col-md-6 col-sm-12 grayBgColor todoMainWrapper commonBorRight">
<div className="wd95 addPaddTopBottom">
<div className="sectionHeader">Public todos</div>
<TodoPublicWrapper client={this.props.client} />
<TodoPublicWrapper
client={this.props.client}
userId={auth.getSub()}
/>
</div>
</div>
</div>
@ -104,11 +128,7 @@ class App extends Component {
</div>
<div className="footerWrapper">
<span>
<a
href="/console"
target="_blank"
rel="noopener noreferrer"
>
<a href="/console" target="_blank" rel="noopener noreferrer">
Backend
</a>
<span className="adminSecret">

View File

@ -32,7 +32,7 @@ class LandingPage extends Component {
{!isAuthenticated() && (
<button
id="qsLoginBtn"
bsStyle="primary"
bsstyle="primary"
className="btn-margin logoutBtn"
onClick={this.login.bind(this)}
>

View File

@ -5,12 +5,11 @@ import "../../styles/App.css";
class TodoPrivateWrapper extends Component {
render() {
const userId = localStorage.getItem("auth0:id_token:sub");
return (
<div className="todoWrapper">
<TodoInput userId={userId} type="private" />
<TodoInput userId={this.props.userId} type="private" />
<TodoPrivateList
userId={userId}
userId={this.props.userId}
client={this.props.client}
type="private"
/>

View File

@ -5,12 +5,12 @@ import "../../styles/App.css";
class TodoPublicWrapper extends Component {
render() {
const userId = localStorage.getItem("auth0:id_token:sub");
// const userId = localStorage.getItem("auth0:id_token:sub");
return (
<div className="todoWrapper">
<TodoInput userId={userId} type="public" />
<TodoInput userId={this.props.userId} type="public" />
<TodoPublicList
userId={userId}
userId={this.props.userId}
type="public"
client={this.props.client}
/>

View File

@ -3,24 +3,40 @@ import { Route, Router } from "react-router-dom";
import Home from "./components/Home/Home";
import Callback from "./components/Callback/Callback";
import Auth from "./components/Auth/Auth";
import auth from "./components/Auth/Auth";
import LandingPage from "./components/LandingPage/LandingPage";
import history from "./utils/history";
import { ApolloProvider } from "react-apollo";
import makeApolloClient from "./apollo";
const client = makeApolloClient();
let client;
const provideClient = component => {
return <ApolloProvider client={client}>{component}</ApolloProvider>;
const provideClient = (Component, renderProps) => {
// check if logged in
if (localStorage.getItem("isLoggedIn") === "true") {
// check if client exists
if (!client) {
client = makeApolloClient();
}
return (
<ApolloProvider client={client}>
<Component {...renderProps} auth={auth} client={client} />
</ApolloProvider>
);
} else {
// not logged in already, hence redirect to login page
if (renderProps.match.path !== "/") {
window.location.href = "/";
} else {
return <Component auth={auth} {...renderProps} />;
}
}
};
const auth = new Auth();
const handleAuthentication = ({ location }) => {
if (/access_token|id_token|error/.test(location.hash)) {
auth.handleAuthentication(client);
auth.handleAuthentication();
}
};
@ -31,18 +47,12 @@ export const makeMainRoutes = () => {
<Route
exact
path="/"
render={props =>
provideClient(
<LandingPage auth={auth} client={client} {...props} />
)
}
render={props => provideClient(LandingPage, props)}
/>
<Route
exact
path="/home"
render={props =>
provideClient(<Home auth={auth} client={client} {...props} />)
}
render={props => provideClient(Home, props)}
/>
<Route
path="/callback"

View File

@ -1,11 +1,17 @@
const HASURA_GRAPHQL_ENGINE_HOSTNAME = window.location.host;
const scheme = (proto) => {
return window.location.protocol === 'https:' ? `${proto}s` : proto;
}
const scheme = proto => {
return window.location.protocol === "https:" ? `${proto}s` : proto;
};
export const GRAPHQL_URL = `${scheme('http')}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/v1alpha1/graphql`;
export const REALTIME_GRAPHQL_URL = `${scheme('ws')}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/v1alpha1/graphql`;
export const GRAPHQL_URL = `${scheme(
"http"
)}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/v1alpha1/graphql`;
export const REALTIME_GRAPHQL_URL = `${scheme(
"ws"
)}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/v1alpha1/graphql`;
export const authClientId = "Fl-hdc6xdYIkok9ynbcL6zoUZPAIdOZN";
export const authDomain = "hasura-react-apollo-todo.auth0.com";
export const callbackUrl = `${scheme('http')}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/callback`;
export const callbackUrl = `${scheme(
"http"
)}://${HASURA_GRAPHQL_ENGINE_HOSTNAME}/callback`;