Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React: Apollo hooks #69

Closed
nojaf opened this issue Jul 13, 2019 · 12 comments
Closed

React: Apollo hooks #69

nojaf opened this issue Jul 13, 2019 · 12 comments

Comments

@nojaf
Copy link

nojaf commented Jul 13, 2019

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.

@nojaf
Copy link
Author

nojaf commented Jul 14, 2019

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?

@luisrudge
Copy link
Contributor

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.

@luisrudge
Copy link
Contributor

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.

@johannwattzon
Copy link

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)?

@stevehobbsdev
Copy link
Contributor

@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.

@NicTanghe
Copy link

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()}`
      }
    }
  }
});

@franciscolourenco
Copy link

@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.

@stevehobbsdev
Copy link
Contributor

@franciscolourenco Have you got a sample repo you can share that you're trying to get working?

@franciscolourenco
Copy link

@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?

@micahgoodreau
Copy link

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")
);

@franciscolourenco
Copy link

@micahgoodreau thanks. ApolloLink looks "sophisticated"

@agiangrant
Copy link

agiangrant commented Aug 30, 2020

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants