Hi, how would the React wrapper work in combination with @apollo/hooks?
At some point in my application, I would like to add an additional header with my access token if the user decided to log in. Some graphql queries should be available when the user isn't logged in.
So I have a scenario where I want to add the header only at a later point.
More to the point, consider the following code:
import { ApolloClient } from "apollo-client";
import { HttpLink } from "apollo-link-http";
import { setContext } from "apollo-link-context";
import { InMemoryCache } from "apollo-cache-inmemory";
const setAuthorizationLink = setContext((request, previousContext) => {
// get hook??
// get accessToken somehow???
return {
headers: { authorization: "1234" }
};
});
export function createApolloClient() {
const httpLink = new HttpLink({
uri: "/api/graphql"
});
const link = setAuthorizationLink.concat(httpLink);
return new ApolloClient({
link,
cache: new InMemoryCache()
});
}
How do I get my bearer token inside the setContext callback?
That's a good question. I don't know how all the apollo setup works, so I'm not sure I'll be able to help you that much. What I can say is that if you have to setup everything outside a react component (like you're doing right now), it's unlikely the react wrapper will help you that much. The react wrapper is using React context, which needs a component to hold the state, so you'll have to ignore the wrapper and use the sdk itself.
Feel free to open an issue in the samples repo if you have more questions, since the react wrapper is not part of this sdk.
A quick way to get the token in apollo link is through localStorage. After login, write token to localStorage. Is storing the access token in localStorage safe? What if the token expires? Does it need to call getTokenSilently() for every api call and update localStorage to ensure token is valid? also is it a way to share the auth0client outside of a component, so apollo link can use getTokenSilently() directly (no localStorage)?
@johannwattzon Storing tokens in local storage is not recommended for security reasons.
As @luisrudge mentioned, if you're having to use the Auth0 client outside of a React component, then you will most likely have to use the SDK directly rather than the React wrapper.
is it in annyway possible to not overcomplicate things and make the following code or something similar work.
import { useAuth0 } from "./components//utils/Auth/react-auth0-spa";
// const accessToken = await auth0.getTokenSilently();
const { getTokenSilently } = useAuth0();
const client = new ApolloClient({
uri: "wss://learn.hasura.io/graphql",
options: {
reconnect: true,
connectionParams: {
headers: {
Authorization: `Bearer ${getTokenSilently()}`
}
}
}
});
@NicTanghe The token might need to be refreshed when it expires. Also getTokenSilently is async. Also looking for a reasonable approach for auth0 + apollo client/link.
@franciscolourenco Have you got a sample repo you can share that you're trying to get working?
@stevehobbsdev I setup an example repo here. The apollo client and ecosystem assumes the token is stored in localStorage, and that it also doesn't expire.
Have a look at these lines:
We could call the login method after authentication is complete, and store it in memory, and restart apollo.
The main question is how often does the token expire and new token need to be retrieved.
Does apollo need to be refreshed every time?
i think this may help you. the following code will check the token each time the apollo client makes a request to the server and seamlessly get a new token if not valid, although it is not for auth0 specifically, i'm sure it could be easily modified. this is from ben awad's
https://github.com/benawad/jwt-auth-example
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "bootstrap/dist/css/bootstrap.min.css";
import { ApolloProvider } from "@apollo/react-hooks";
import { getAccessToken, setAccessToken } from "./accessToken";
import { App } from "./App";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";
import { onError } from "apollo-link-error";
import { ApolloLink, Observable } from "apollo-link";
import { TokenRefreshLink } from "apollo-link-token-refresh";
import jwtDecode from "jwt-decode";
const cache = new InMemoryCache({});
const requestLink = new ApolloLink(
(operation, forward) =>
new Observable(observer => {
let handle: any;
Promise.resolve(operation)
.then(operation => {
const accessToken = getAccessToken();
if (accessToken) {
operation.setContext({
headers: {
authorization: bearer ${accessToken}
}
});
}
})
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer)
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const client = new ApolloClient({
link: ApolloLink.from([
new TokenRefreshLink({
accessTokenField: "accessToken",
isTokenValidOrUndefined: () => {
const token = getAccessToken();
if (!token) {
return true;
}
try {
const { exp } = jwtDecode(token);
if (Date.now() >= exp * 1000) {
return false;
} else {
return true;
}
} catch {
return false;
}
},
fetchAccessToken: () => {
return fetch("http://localhost:4000/refresh_token", {
method: "POST",
credentials: "include"
});
},
handleFetch: accessToken => {
setAccessToken(accessToken);
},
handleError: err => {
console.warn("Your refresh token is invalid. Try to relogin");
console.error(err);
}
}),
onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors);
console.log(networkError);
}),
requestLink,
new HttpLink({
uri: "http://localhost:4000/graphql",
credentials: "include"
})
]),
cache
});
ReactDOM.render(
,
document.getElementById("root")
);
@micahgoodreau thanks. ApolloLink looks "sophisticated"
You can wrap ApolloProvider in the Auth0 Context Consumer and pass the Auth0 client context into a function that returns the ApolloClient as a singleton.
It would look something like this:
<Auth0Provider domain="example.us.auth0.com" clientId="<clientId>" redirectUri={window.location.origin} audience="https://example.us.auth0.com/api/v2/" scope="profile read:current_user">
<Auth0Context.Consumer>
{(auth0Client) => {
return (
<ApolloProvider client={aFunctionToGetApolloClient(auth0Client)}>
<App />
</ApolloProvider>
);
}}
</Auth0Context.Consumer>
</Auth0Provider>
In this example, you will have access to the auth0Client.getAccessTokenSilently function inside your apollo client function where you can setContext for headers or whatever you need to do.
Most helpful comment
You can wrap ApolloProvider in the Auth0 Context Consumer and pass the Auth0 client context into a function that returns the ApolloClient as a singleton.
It would look something like this:
In this example, you will have access to the auth0Client.getAccessTokenSilently function inside your apollo client function where you can setContext for headers or whatever you need to do.