how to add auth0 access token to react-apollo - reactjs

I'm trying to add authorization token to the apollo client in react js to let the users login ...
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { ThemeProvider } from 'react-jss';
import Theme from 'resources/theme';
import Routes from 'routes';
import './index.css';
import * as serviceWorker from './serviceWorker';
import { Auth0Provider } from "#auth0/auth0-react";
import 'bootstrap/dist/css/bootstrap.min.css';
import ReactNotification from 'react-notifications-component'
import './components/orders/fonts/NotoSansJP-Regular.otf'
import 'react-notifications-component/dist/theme.css'
import { ApolloProvider , ApolloClient, InMemoryCache } from '#apollo/client';
import { useAuth0 } from "#auth0/auth0-react";
const client = new ApolloClient({
uri: process.env.REACT_APP_API_BACK ,
headers: {
Authorization: `Bearer ${accessToken}` // doesn’t work
},
cache: new InMemoryCache()
});
ReactDOM.render(
<Auth0Provider
domain= {process.env.REACT_APP_AUTH0_DOMAIN }
clientId= {process.env.REACT_APP_AUTH0_CLIENT_ID}
redirectUri={window.location.origin}
audience={process.env.REACT_APP_AUTH0_AUDIENCE}
scope="warehouse"
>
<ApolloProvider client={client}>
<ThemeProvider theme={Theme}>
<Router>
<ReactNotification />
<Routes />
</Router>
</ThemeProvider>,
</ApolloProvider>
</Auth0Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
to get the token i need to import :
import { useAuth0 } from "#auth0/auth0-react";
the add this lines :
const { getAccessTokenSilently } = useAuth0();
but this cannot be done in index.js i think
to get the token :
const accessToken = await getAccessTokenSilently
this what i found in the docs and from google search , but i think it cannot be done in my case , most tutorials show how to get the user data ( including the token) in a profile page but that's not wha I want .
i want to pass it to the client in index.js

This is what I ended up doing:
import {
ApolloProvider,
ApolloClient,
InMemoryCache,
HttpLink,
} from '#apollo/client';
import { setContext } from '#apollo/link-context';
import { useAuth0 } from '#auth0/auth0-react';
const ApolloProviderWithAuth0 = ({ children }) => {
const { getAccessTokenSilently } = useAuth0();
const httpLink = new HttpLink({
uri: process.env.REACT_APP_GRAPHQL_URI,
});
const authLink = setContext(async (_, { headers, ...rest }) => {
let token;
try {
token = await getAccessTokenSilently();
} catch (error) {
console.log(error);
}
if (!token) return { headers, ...rest };
return {
...rest,
headers: {
...headers,
authorization: `Bearer ${token}`,
},
};
});
const client = React.useRef();
if (!client.current) {
client.current = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});
}
return (
<ApolloProvider client={client.current}>
{children}
</ApolloProvider>
);
};
export { ApolloProviderWithAuth0 };
And then use it as Provider in your App component.

You need to create dedicated component for
function useToken() {
const { getAccessTokenSilently } = useAuth0();
const [token, setToken] = useState(null);
useEffect(() => { getAccessTokenSilently().then(setToken) }, [])
return token;
}
function MyApolloProvider({ children }) {
const token = useToken();
// useRef instead of useMemo to make sure client is not re-created randomly
const clientRef = useRef(null);
if (token && !clientRef.current) {
// This code should only be executed once.
clientRef.current = new ApolloClient(/**/)
}
if (!clientRef.current) {
// Now we have to wait until client is initialized with token, so you might want to add some spinner
return null;
}
return <ApolloClient client={clientRef.current} >{children}</ApolloClient>
}
And then you use it instead of original ApolloProvider in yours ReactDOM.render.
If token changes then things become a bit more difficult.
https://www.apollographql.com/docs/react/networking/authentication/#header
You still use similar approach:
function useToken() { /* should return actual token and update it if changed */ }
function MyApolloProvider({ children }) {
const token = useToken();
const tokenRef = useRef(token);
const clientRef = useRef(null);
tokenRef.current = token; // make sure that tokenRef always has up to date token
if (!clientRef.current) {
// This function is called on each request individually and it takes token from ref
const authLink = setContext((_, { headers }) => {
// similar to documentation example, but you read token from ref
const token = tokenRef.current;
return {
headers: {
...headers,
authorization: `Bearer ${token}`,
}
}
});
clientRef.current = new ApolloClient(
link: authLink.concat(httpLink),
// ...
)
}
}
Usage in index.js regardless of first or second case:
ReactDOM.render(
<Auth0Provider /* options */>
<MyApolloProvider>
{/* ... */}
</MyApolloProvider>
</Auth0Provider>,
document.getElementById("root")
);
Main thing here is that MyApolloProvider should be within Auth0Provider to gain access to the token.

Related

The react apollo useSubscription hook doesn't work in a function call but works when the same call is assigned to a variable

I am working with apollo graphql client. The subscription in the server is working fine watching for changes.
But in the client, I am not able to log data.
I also tried to mutate but still its resulting in the same thing.
useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data)
}
})
The above code doesn't log anything out.
But,
const value = useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data)
}
})
console.log(value)
The above code seems to work fine logging out a value.
I am attaching a few codes below for more clarity.
index.js or apollo setup:
import ReactDOM from 'react-dom'
import App from './App'
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
split,
} from '#apollo/client'
import { setContext } from '#apollo/client/link/context'
import { getMainDefinition } from '#apollo/client/utilities'
import { GraphQLWsLink } from '#apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
import Assess from './Asses'
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('library-user-token')
return {
headers: {
...headers,
authorization: token ? `bearer ${token}` : null,
},
}
})
const httpLink = new HttpLink({ uri: 'http://localhost:4002' })
const wsLink = new GraphQLWsLink(
createClient({
url: 'ws://localhost:4002',
})
)
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
authLink.concat(httpLink)
)
const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
})
ReactDOM.render(
<ApolloProvider client={client}>
<Assess />
</ApolloProvider>,
document.getElementById('root')
)
App.js
//App.js
import { useSubscription, useQuery } from "#apollo/client";
import { ALL_BOOKS, BOOK_ADDED } from "./queries";
const App = () => {
console.log(BOOK_ADDED);
const result = useQuery(ALL_BOOKS);
useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data);
},
});
console.log(result)
if(result.loading){
return null
}
return (
<div>
{result?.data?.allBooks.map((r) => (
<li key={r.id}>{r.title}</li>
))}
</div>
);
};
export default App
The query and fragment:
const BOOK_DETAILS = gql`
fragment BookDetails on Books {
title
author {
name
}
published
genres
id
}
`;
export const BOOK_ADDED = gql`
subscription {
bookAdded {
...BookDetails
}
}
${BOOK_DETAILS}
`;
After reading the changelogs of #apollo/client. I got to know that the method demonstrated in question is only useful when the version of #apollo/client is >=3.7.0. As my version was #3.6.7 it wasn't logging the value out.
Earlier than this version the function required an onSubscriptionData callback
to perform the same operation, which is now deprecated. I have still demonstrated it below as someone using version <#3.7.0 might find it useful.
useSubscription(BOOK_ADDED,{
onSubscriptionData: ({subscriptionData: data}) =>{
console.log(data)
}
})
You may read the change log here.

NextJs GraphQL Subscription: How to set up connect api with Apolo [duplicate]

I am new to NextJS. I have a page that needs to display real-time data pulled from a Hasura GraphQL backend.
In other non-NextJS apps, I have used GraphQL subscriptions with the Apollo client library. Under the hood, this uses websockets.
I can get GraphQL working in NextJS when it's not using subscriptions. I'm pretty sure this is running on the server-side:
import React from "react";
import { AppProps } from "next/app";
import withApollo from 'next-with-apollo';
import { ApolloProvider } from '#apollo/react-hooks';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
export default withApollo(({ initialState }) => new ApolloClient({
uri: "https://my_hasura_instance.com/v1/graphql",
cache: new InMemoryCache().restore(initialState || {}),
request: (operation: any) => {
const token = getToken();
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
});
}
}))(App);
And I use it this way:
import { useQuery } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
query {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useQuery(myQuery);
return <p>{JSON.stringify(data)}</p>
}
However, I would instead like to do this:
import { useSubscription } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
subscription {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useSubscription(myQuery);
return <p>{JSON.stringify(data)}</p>
}
What I've tried
I've tried splitting the HttpLink and WebsocketLink elements in the ApolloClient, like so:
import React from "react";
import { AppProps } from "next/app";
import { ApolloProvider } from '#apollo/react-hooks';
import withApollo from 'next-with-apollo';
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
const wsLink = new WebSocketLink({
uri: "wss://my_hasura_instance.com/v1/graphql",
options: {
reconnect: true,
timeout: 10000,
connectionParams: () => ({
headers: {
authorization: getToken() ? `Bearer ${getToken()}` : ""
}
})
},
});
const httpLink = new HttpLink({
uri: "https://hasura-g3uc.onrender.com/v1/graphql",
});
const link = process.browser ? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
) : httpLink;
export default withApollo(({ initialState }) => new ApolloClient({
link: link,
cache: new InMemoryCache().restore(initialState || {}),
}))(App);
But when I load the page, I get an Internal Server Error, and this error in the terminal:
Error: Unable to find native implementation, or alternative implementation for WebSocket!
It seems to me that the ApolloClient is then being generated on the server-side, where there is no WebSocket implementation. How can I make this happen on the client-side?
Found workaround to make it work, take look at this answer https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-359261024
the reason was due to server-side rendering; these statements must run in the browser, so we test if we have process.browser !!
relevant section from the attached github link:
const wsLink = process.browser ? new WebSocketLink({ // if you instantiate in the server, the error will be thrown
uri: `ws://localhost:4000/subscriptions`,
options: {
reconnect: true
}
}) : null;
const httplink = new HttpLink({
uri: 'http://localhost:3000/graphql',
credentials: 'same-origin'
});
const link = process.browser ? split( //only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httplink,
) : httplink;
This answer seems to be more actual
https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-775578327
You should install ws by npm i ws and add webSocketImpl: ws to WebSocketLink argument.
import ws from 'ws';
const wsLink = new WebSocketLink({
uri: endpoints.ws,
options: {
reconnect: true,
connectionParams: () => ({
...getToken() && {Authorization: getToken()}
})
},
webSocketImpl: ws
});
Solution: Make wsLink a function variable like the code below.
// src/apollo.ts
import { ApolloClient, HttpLink, InMemoryCache } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
});
const wsLink = () => {
return new GraphQLWsLink(createClient({
url: 'ws://localhost:3000/graphql'
}));
}
export const apolloClient = new ApolloClient({
link: typeof window === 'undefined' ? httpLink : wsLink(),
cache: new InMemoryCache(),
});
// pages/_app.tsx
import { ApolloProvider } from "#apollo/client";
import { apolloClient } from "../src/apollo";
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
);
}

How to use Apollo GraphQL subscriptions in the client-side NextJS?

I am new to NextJS. I have a page that needs to display real-time data pulled from a Hasura GraphQL backend.
In other non-NextJS apps, I have used GraphQL subscriptions with the Apollo client library. Under the hood, this uses websockets.
I can get GraphQL working in NextJS when it's not using subscriptions. I'm pretty sure this is running on the server-side:
import React from "react";
import { AppProps } from "next/app";
import withApollo from 'next-with-apollo';
import { ApolloProvider } from '#apollo/react-hooks';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
export default withApollo(({ initialState }) => new ApolloClient({
uri: "https://my_hasura_instance.com/v1/graphql",
cache: new InMemoryCache().restore(initialState || {}),
request: (operation: any) => {
const token = getToken();
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
});
}
}))(App);
And I use it this way:
import { useQuery } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
query {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useQuery(myQuery);
return <p>{JSON.stringify(data)}</p>
}
However, I would instead like to do this:
import { useSubscription } from '#apollo/react-hooks';
import { gql } from 'apollo-boost';
const myQuery = gql`
subscription {
...
}
`;
const MyComponent: React.FC = () => {
const { data } = useSubscription(myQuery);
return <p>{JSON.stringify(data)}</p>
}
What I've tried
I've tried splitting the HttpLink and WebsocketLink elements in the ApolloClient, like so:
import React from "react";
import { AppProps } from "next/app";
import { ApolloProvider } from '#apollo/react-hooks';
import withApollo from 'next-with-apollo';
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";
import { split } from 'apollo-link';
import { HttpLink } from 'apollo-link-http';
import { WebSocketLink } from 'apollo-link-ws';
import { getMainDefinition } from 'apollo-utilities';
import { getToken } from "../util/auth";
interface Props extends AppProps {
apollo: any
}
const App: React.FC<Props> = ({ Component, pageProps, apollo }) => (
<ApolloProvider client={apollo}>
<Component {...pageProps}/>
</ApolloProvider>
);
const wsLink = new WebSocketLink({
uri: "wss://my_hasura_instance.com/v1/graphql",
options: {
reconnect: true,
timeout: 10000,
connectionParams: () => ({
headers: {
authorization: getToken() ? `Bearer ${getToken()}` : ""
}
})
},
});
const httpLink = new HttpLink({
uri: "https://hasura-g3uc.onrender.com/v1/graphql",
});
const link = process.browser ? split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
) : httpLink;
export default withApollo(({ initialState }) => new ApolloClient({
link: link,
cache: new InMemoryCache().restore(initialState || {}),
}))(App);
But when I load the page, I get an Internal Server Error, and this error in the terminal:
Error: Unable to find native implementation, or alternative implementation for WebSocket!
It seems to me that the ApolloClient is then being generated on the server-side, where there is no WebSocket implementation. How can I make this happen on the client-side?
Found workaround to make it work, take look at this answer https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-359261024
the reason was due to server-side rendering; these statements must run in the browser, so we test if we have process.browser !!
relevant section from the attached github link:
const wsLink = process.browser ? new WebSocketLink({ // if you instantiate in the server, the error will be thrown
uri: `ws://localhost:4000/subscriptions`,
options: {
reconnect: true
}
}) : null;
const httplink = new HttpLink({
uri: 'http://localhost:3000/graphql',
credentials: 'same-origin'
});
const link = process.browser ? split( //only create the split in the browser
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httplink,
) : httplink;
This answer seems to be more actual
https://github.com/apollographql/subscriptions-transport-ws/issues/333#issuecomment-775578327
You should install ws by npm i ws and add webSocketImpl: ws to WebSocketLink argument.
import ws from 'ws';
const wsLink = new WebSocketLink({
uri: endpoints.ws,
options: {
reconnect: true,
connectionParams: () => ({
...getToken() && {Authorization: getToken()}
})
},
webSocketImpl: ws
});
Solution: Make wsLink a function variable like the code below.
// src/apollo.ts
import { ApolloClient, HttpLink, InMemoryCache } from "#apollo/client";
import { GraphQLWsLink } from "#apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const httpLink = new HttpLink({
uri: 'http://localhost:3000/graphql'
});
const wsLink = () => {
return new GraphQLWsLink(createClient({
url: 'ws://localhost:3000/graphql'
}));
}
export const apolloClient = new ApolloClient({
link: typeof window === 'undefined' ? httpLink : wsLink(),
cache: new InMemoryCache(),
});
// pages/_app.tsx
import { ApolloProvider } from "#apollo/client";
import { apolloClient } from "../src/apollo";
function MyApp({ Component, pageProps }) {
return (
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
);
}

How to acess React context from Apollo set Context Http Link

I am trying to access a react context values within the setContext function for my Apollo client. I would like to be able to dynamically update the header for each graphql request with the react context value. But I face an error with no visible error messages in the logs. Is what I am trying to do possible?
import React, { useState, useContext } from "react";
import { render } from "react-dom";
import ApolloClient from "apollo-client";
import { ApolloProvider } from "react-apollo";
import { createHttpLink } from "apollo-link-http";
import { setContext } from "apollo-link-context";
import { InMemoryCache } from "apollo-cache-inmemory";
import Select from "./Select";
import CurrencyContext from "./CurrencyContext";
import ExchangeRates from "./ExchangeRates";
const httpLink = createHttpLink({
uri: "https://48p1r2roz4.sse.codesandbox.io"
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("token");
const currency = useContext(CurrencyContext); // How to access React context here ?
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
currencyContext: currency ? currency : {}
}
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
const currencies = ["USD", "EUR", "BTC"];
const App = () => {
const [currency, setCurrency] = useState("USD");
return (
<ApolloProvider client={client}>
<CurrencyContext.Provider value={currency}>
<h2>Provide a Query variable from Context 🚀</h2>
<Select value={currency} setValue={setCurrency} options={currencies} />
<ExchangeRates />
</CurrencyContext.Provider>
</ApolloProvider>
);
};
render(<App />, document.getElementById("root"));
You can use useImperativeHandle to access the context values from outside React tree
In the context file create a ref
export const ContextRef = React.createRef();
Then inside the Context add
React.useImperativeHandle(ContextRef, () => contextValues);
Finally, you can access the context values with
ContextRef.current.token
See: https://reactjs.org/docs/hooks-reference.html#useimperativehandle

How to write redirection inside the axios interceptors without reloading in React JS

I used axios interceptors to maintain the internal server errors. I need to redirect to another url if the response have an error without reloading. Below code I used location.href. So it's reloading. I need a solution to redirect without refreshing the page.
I tried "Redirect" in react-router-dom. But it not works for me.
export function getAxiosInstance() {
const instance = axios.create();
const token = getJwtToken();
// Set the request authentication header
instance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// Set intercepters for response
instance.interceptors.response.use(
(response) => response,
(error) => {
if (config.statusCode.errorCodes.includes(error.response.status)) {
return window.location.href = '/internal-server-error';
}
return window.location.href = '/login';
}
);
return instance;
}
Can anyone help me to solve this out?
This will take advantage of import caching:
// history.js
import { createBrowserHistory } from 'history'
export default createBrowserHistory({
/* pass a configuration object here if needed */
})
// index.js (example)
import { Router } from 'react-router-dom'
import history from './history'
import App from './App'
ReactDOM.render((
<Router history={history}>
<App />
</Router>
), holder)
// interceptor.js
import axios from 'axios';
import cookie from 'cookie-machine';
import history from '../history';
axios.interceptors.response.use(null, function(err) {
if ( err.status === 401 ) {
cookie.remove('my-token-key');
history.push('/login');
}
return Promise.reject(err);
});
I solved this by passing useHistory() from inside a <Router> to axios interceptors.
App.js:
// app.js
function App() {
return (
<Router>
<InjectAxiosInterceptors />
<Route ... />
<Route ... />
</Router>
)
}
InjectAxiosInterceptors.js:
import { useEffect } from "react"
import { useHistory } from "react-router-dom"
import { setupInterceptors } from "./plugins/http"
function InjectAxiosInterceptors () {
const history = useHistory()
useEffect(() => {
console.log('this effect is called once')
setupInterceptors(history)
}, [history])
// not rendering anything
return null
}
plugins/http.js:
import axios from "axios";
const http = axios.create({
baseURL: 'https://url'
})
/**
* #param {import('history').History} history - from useHistory() hook
*/
export const setupInterceptors = history => {
http.interceptors.response.use(res => {
// success
return res
}, err => {
const { status } = err.response
if (status === 401) {
// here we have access of the useHistory() from current Router
history.push('/login')
}
return Promise.reject(err)
})
}
export default http
You should use a history object to push new location. Check this question How to push to History in React Router v4?. This should help.

Resources