mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
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:
parent
50b02e950b
commit
36cb6f8b13
@ -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
|
||||
})
|
||||
|
@ -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;
|
||||
|
@ -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">
|
||||
|
@ -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)}
|
||||
>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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"
|
||||
|
@ -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`;
|
||||
|
Loading…
Reference in New Issue
Block a user