Apollo client not sending token to backend until page refresh - graphql

I've been working on an app and only realized this issue when I started to clear the cache, but my app only works fine on refresh. When I clear all the cache, refresh then run through my app, I realized that my queries were returning my custom error "GraphQL error: Not authenticated as user".
I believe something is wrong with the way that I've set up my apollo client. It seems that the context is being set as soon as it's instantiated and then never changes the context even if the token exists. It would also explain why after logging in then refreshing, the queries work with the token until the local storage/cache is cleared. So my question is what's wrong with what I have?
import { persistCache } from "apollo-cache-persist";
import { ISLOGGEDIN_QUERY } from "./components/gql/Queries"
const cache = new InMemoryCache();
const token = localStorage.getItem('token')
persistCache({
cache,
storage: localStorage
})
const client = new ApolloClient({
uri: "http://localhost:4000/graphql",
cache,
resolvers: {
Mutation: {
changeValue: (_, args, { cache }) => {
const { isAuth } = token ? cache.readQuery({ query: ISLOGGEDIN_QUERY }) : false;
cache.writeData({
data: { isAuth: !isAuth }
})
return null;
}
}
},
request: (operation) => {
operation.setContext({
headers: {
authorization: token ? token : ''
}
})
},
});
//set default values
client.cache.writeData({ data: { isAuth: token ? true : false } })
export default client;```

I know I'm a bit late but I was having this problem too and found these
https://www.apollographql.com/docs/react/networking/authentication/#reset-store-on-logout
https://stackoverflow.com/a/65204972/13491532
You can just call clear store after your login mutation
import { useApolloClient } from "#apollo/client";
const client = useApolloClient();
client.clearStore();

Related

Apollo Client - Simultaneous subscriptions from same component

I'm trying to make 2 simultaneous subscriptions with Apollo Client but the connection get closed and reopened every 2 seconds:
This is my code concerning subscriptions:
//apollo.js
const httpLink = createHttpLink({
includeUnusedVariables: true,
uri:
process.env.API_GRAPHQL ||
// Change to your graphql endpoint.
headers: {
Authorization:
"Bearer TOKEN",
},
});
const wsLink = new GraphQLWsLink(
createClient({
url: process.env.WS_GRAPHQL,
connectionParams: {
Authorization:
"Bearer TOKEN",
},
options: {
reconnect: true,
},
})
);
const link = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === "OperationDefinition" && operation === "subscription";
},
wsLink,
httpLink
);
// subscriber executer
const {
result: locationUpdates,
// loading: loadingLocation,
// error: devicesError,
// refetch: refetchDevices,
onResult: onResultLocations,
} = useSubscription(locationsLivesTrue, () => ({
}));
const { result: me, onResult: onResultMe } = useSubscription(
meUpdates,
() => ({})
);
If I execute only one subscription it works fine.
I also tried to subscribe directly from the client when I provide the app, but got the same result.
#juanmac My original post was deleted so I will answer here. Since you asked me a question there, I think it is fine I will answer inside your newest post ;)
A loop was used. Inside the loop, a subscribeToMore was used.
Inside that function, updateQuery was used.
There were some problems but I do not know if they were resolved. I will remind you, that it was React Native, and there are some stability issues with subscriptions etc.
I hope that helps.

calling back-end api (laravel) from getServerSideProps() in next js

I am using next js as a front-end and laravel as a back-end. and i want to call back-end (laravel) api from getServerSideProps() method. as shown below
export async function getServerSideProps(context) {
const response = await Axios.request({
url: 'http://localhost:8000/api/event',
method: 'get',
headers: {
Cookie: context.req.headers.cookie,
},
})
const events = response.events
console.log(response)
return {
props: { events },
}
}
so i have also set the cookie but i am getting response with message unauthenticated like below
I just wanted to say a huge THANK YOU to Riaz Kahn for his answer. After a lot of banging my face against a wall this was the answer. I'm going to post a working example of my getServerSideProps function for anyone arriving here in the future. The getUser({[configObject]}) function is just returning a promise from an axios.get('my-user/route', config) call. This is working properly in a Next 13 app using standard pages functionality (not using experimental app directory).
export const getServerSideProps = async (context: any) => {
const {req, res} = context;
try {
const {data: user} = await getUser({
headers: {...req.headers}
});
return {
props: {
fallback: {user}
}
}
} catch (e) {
res.writeHead(302, {Location: '/login'});
res.end();
}
}

Inject token from auth0 to React-admin Hasura data provider

This example demonstrates how to use Auth0 with react-admin. It is working as expected.
We are trying to adjust it so it will use the hasura data provider. We've created a new file dataProvider.js that will construct the data-provider:
import buildHasuraProvider from "ra-data-hasura";
import { ApolloClient, InMemoryCache } from "#apollo/client";
export const initDataProvider = async (token) => {
const client = new ApolloClient({
uri: process.env.REACT_APP_GRAPHQL_URI,
headers: {
Authorization: `Bearer ${token}`,
},
cache: new InMemoryCache(),
});
const dataProvider = await buildHasuraProvider({ client });
return dataProvider;
};
However, we are missing the JWT token which is created as part of the Auth0 authentication process. We do not know how to get the token in order to initialize the data provider with it. Does react-admin know how to do it on its own? if not, how do we access the JWT token to do it ourselves manually?
This is the authProvider source-code:
import authConfig from "./authConfig";
import {Auth0Client} from '#auth0/auth0-spa-js';
const auth0 = new Auth0Client({
domain: authConfig.domain,
client_id: authConfig.clientID,
redirect_uri: authConfig.redirectURI,
cacheLocation: 'localstorage',
useRefreshTokens: true
});
export default {
// called when the user attempts to log in
login: (url) => {
if (typeof url === 'undefined') {
return auth0.loginWithRedirect()
}
return auth0.handleRedirectCallback(url.location);
},
// called when the user clicks on the logout button
logout: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) { // need to check for this as react-admin calls logout in case checkAuth failed
return auth0.logout({
redirect_uri: window.location.origin,
federated: true // have to be enabled to invalidate refresh token
});
}
return Promise.resolve()
})
},
// called when the API returns an error
checkError: ({status}) => {
if (status === 401 || status === 403) {
return Promise.reject();
}
return Promise.resolve();
},
// called when the user navigates to a new location, to check for authentication
checkAuth: () => {
return auth0.isAuthenticated().then(function (isAuthenticated) {
if (isAuthenticated) {
return Promise.resolve();
}
return auth0.getTokenSilently()
})
},
// called when the user navigates to a new location, to check for permissions / roles
getPermissions: () => {
return Promise.resolve()
},
};
It is unclear to us if there is a point where we can extract the token from.
getTokenSilently should give you back the token.
You'll have to structure your React app such that you have access to the result of this method before you construct your data provider.

Apollo useQuery() - "refetch" is ignored if the response is the same

I am trying to use Apollo-client to pull my users info and stuck with this problem:
I have this Container component responsible for pulling the user's data (not authentication) once it is rendered. User may be logged in or not, the query returns either viewer = null or viewer = {...usersProps}.
Container makes the request const { data, refetch } = useQuery<Viewer>(VIEWER);, successfully receives the response and saves it in the data property that I use to read .viewer from and set it as my current user.
Then the user can log-out, once they do that I clear the Container's user property setUser(undefined) (not showed in the code below, not important).
The problem occurred when I try to re-login: Call of refetch triggers the graphql http request but since it returns the same data that was returned during the previous initial login - useQuery() ignores it and does not update data. Well, technically there could not be an update, the data is the same. So my code setUser(viewer); does not getting executed for second time and user stucks on the login page.
const { data, refetch } = useQuery<Viewer>(VIEWER);
const viewer = data && data.viewer;
useEffect(() => {
if (viewer) {
setUser(viewer);
}
}, [ viewer ]);
That query with the same response ignore almost makes sense, so I tried different approach, with callbacks:
const { refetch } = useQuery<Viewer>(VIEWER, {
onCompleted: data => {
if (data.viewer) {
setUser(data.viewer);
}
}
});
Here I would totally expect Apollo to call the onCompleted callback, with the same data or not... but it does not do that. So I am kinda stuck with this - how do I make Apollo to react on my query's refetch so I could re-populate user in my Container's state?
This is a scenario where apollo's caches come handy.
Client
import { resolvers, typeDefs } from './resolvers';
let cache = new InMemoryCache()
const client = new ApolloClient({
cache,
link: new HttpLink({
uri: 'http://localhost:4000/graphql',
headers: {
authorization: localStorage.getItem('token'),
},
}),
typeDefs,
resolvers,
});
cache.writeData({
data: {
isLoggedIn: !!localStorage.getItem('token'),
cartItems: [],
},
})
LoginPage
const IS_LOGGED_IN = gql`
query IsUserLoggedIn {
isLoggedIn #client
}
`;
function IsLoggedIn() {
const { data } = useQuery(IS_LOGGED_IN);
return data.isLoggedIn ? <Pages /> : <Login />;
}
onLogin
function Login() {
const { data, refetch } = useQuery(LOGIN_QUERY);
let viewer = data && data.viewer
if (viewer){
localStorage.setItem('token',viewer.token)
}
// rest of the stuff
}
onLogout
onLogout={() => {
client.writeData({ data: { isLoggedIn: false } });
localStorage.clear();
}}
For more information regarding management of local state. Check this out.
Hope this helps!

How to cache using apollo-server

The apollo basic example at https://www.apollographql.com/docs/apollo-server/features/data-sources.html#Implementing-your-own-cache-backend they state that doing a redis cache is as simple as:
const { RedisCache } = require('apollo-server-cache-redis');
const server = new ApolloServer({
typeDefs,
resolvers,
cache: new RedisCache({
host: 'redis-server',
// Options are passed through to the Redis client
}),
dataSources: () => ({
moviesAPI: new MoviesAPI(),
}),
});
When I look at the examples of non-redis, it states that it's a simple { get, set } for cache. This means I should theoretically be able to do.
cache : {
get : function() {
console.log("GET!");
},
set : function() {
console.log("SET!");
}
}
No matter what I try, my cache functions are never called when I'm utilizing the graphQL explorer that apollo-server provides natively.
I have tried with cacheControl : true and with cacheControl set like it is in https://medium.com/brikl-engineering/serverless-graphql-cached-in-redis-with-apollo-server-2-0-f491695cac7f . Nothing.
Is there an example of how to implement basic caching in Apollo that does not utilize the paid Apollo Engine system?
You can look at the implementation of this package which caches the full response to implement your own cache.
import { RedisCache } from "apollo-server-redis";
import responseCachePlugin from "apollo-server-plugin-response-cache";
const server = new ApolloServer({
...
plugins: [responseCachePlugin()],
cache: new RedisCache({
connectTimeout: 5000,
reconnectOnError: function(err) {
Logger.error("Reconnect on error", err);
const targetError = "READONLY";
if (err.message.slice(0, targetError.length) === targetError) {
// Only reconnect when the error starts with "READONLY"
return true;
}
},
retryStrategy: function(times) {
Logger.error("Redis Retry", times);
if (times >= 3) {
return undefined;
}
return Math.min(times * 50, 2000);
},
socket_keepalive: false,
host: "localhost",
port: 6379,
password: "test"
}),
});
You should be able to use the NPM package 'apollo-server-caching' by implementing your own interface. See Implementing Your Own Cache which provides an example.

Resources