I am working on a single page webapp using React and Apollo Client, and I am wondering about how to correctly communicate authentication state between Apollo Client links and React context.
Within the client, I have written an AuthProvider context that supplies the current user information, so that anywhere in the component tree I can do
const authState = useAuthState()
const dispatch = useAuthDispatch()
and thus query and update the authentication information as I need. I have used this, for example, to write a PrivateRoute
component that redirects the viewer if she is not authenticated:
const PrivateRoute: FunctionComponent<RouteProps> = ({ children, ...rest }) => {
const authState = useAuthState()
return (
<Route
{...rest}
render={({ location }) =>
authState.user ? (
children
) : (
<Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>
)
}
/>
)
}
This is all fine. My issue arises when combining this with my chosen form of authentication, which is JWT. I am storing the access token in the authState
and the refresh token is set as an httpOnly cookie by the back-end on login.
But I have to send the access token as an Authorization: Bearer
header on each request, which I want to do using an Apollo Link, as follows:
const authLink = setContext(async (_, { headers }) => {
const token = getTokenFromAuthStateSomehow()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ""
}
}
})
But this is within an Apollo Link, where I of course don't have direct access to the React Context. getTokenFromAuthStateSomehow()
is a function I do not know how to write.
Then the next issue is what happens when this request fails because the access token has expired. I want to use Apollo's onError
to catch a 401 error from the API and retry the request by getting a refreshed token:
const retryLink = onError(({ networkError }) => {
if (networkError) {
const newToken = getRefreshedToken()
if (newToken) {
retryRequest()
setTokenInAuthStateSomehow(newToken)
}
}
})
But then we have the same problem - now I need to send the new token back to authState
, i.e. to React Context: setTokenInAuthStateSomehow()
is a function I do not know how to write either.
So, the overarching question is: how do I communicate between an Apollo Link and React Context? Do I have to setup some listeners or events somehow? I would love any information or push in the right direction.