I have a vue web app from which I'm trying to run a subscription using a hasura query.
My problem is that I cannot pass to the Websocket request an authorization token as the backend expects.
These are my current settings:
const token = localStorage.getItem("token") || null;
const options = {
httpUri: //graphql http entpoint,
wsUri: //graphql ws endpoint
};
let link = new HttpLink({
uri: options.httpUri
});
// Create the subscription websocket link if available
if (options.wsUri) {
const wsLink = new WebSocketLink(
new SubscriptionClient(options.wsUri, {
lazy: true,
reconnect: true,
connectionParams: {
headers: {
Authorization: `Bearer ${token}`
}
}
})
);
// using the ability to split links, you can send data to each link
// depending on what kind of operation is being sent
link = split(
// split based on operation type
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
link
);
}
const authLink = setContext((_, { headers }) => {
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ""
}
};
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message }) => {
if (message.includes("unauthorized")) {
EventBus.$emit("unauthorized");
}
});
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const apolloClient = new ApolloClient({
link: ApolloLink.from([errorLink, authLink, link]),
cache: new InMemoryCache(),
connectToDevTools: true
});
const apolloProvider = new VueApollo({
defaultClient: apolloClient
});
When I try to run the subscription I get
HTTP Authentication failed; no valid credentials available
And in the ws request header I cannot see my Authorization bearer set.
A side info I need authorization for both http and ws requests
I think errorLink or authLink unexpectedly change websocket header token. You try modifying a bit:
const httpLink = from([
authLink,
// errorLink,
new HttpLink({
uri: Config.httpDataHost,
headers: {
[XHasuraClientName]: Config.hasuraClientName
}
})
]);
const wsLink = new WebSocketLink({ ... });
const link = ...
const apolloClient = new ApolloClient({
link: ApolloLink.from([errorLink, link]),
cache: new InMemoryCache(),
connectToDevTools: true
});
If it doesn't work, you can try commenting errorLink to check. Another thing is, you shouldn't get token globally, but use lazy function so ApolloClient can always get latest access token from local storage
const getIdToken = () => localStorage.getItem('token') || null;
const wsLink = new WebSocketLink({
uri: options.wsUri,
options: {
connectionParams: () => ({
headers: {
Authorization: getIdToken(),
}
}),
...
}
});
PS: I have an example repository with React + Apollo Client 3.0. Although you are using Vue.js,Apollo Client construction is the same https://github.com/hgiasac/ra-hasura-typescript-boilerplate/blob/auth-jwt/src/shared/ApolloClient.ts
Related
when put token value as hardcoded it's work properly,but when i got token from asyncstorage and pass token in headers i got this error
Response not sucessful:Recevied status code 500 What is wrong in mycode?
import { ApolloClient,ApolloProvider,InMemoryCache,gql } from '#apollo/client'
import { createUploadLink } from 'apollo-upload-client'
const getToken = async () => {
const token = await AsyncStorage.getItem('#storage_Key')
return token
}
const token = getToken()
const client = new ApolloClient({
link: createUploadLink({
uri: 'http://192.168.1.82:8080/graphql',
headers: {
authorization: token
}
}),
cache: new InMemoryCache()
})
When I add authorizan in headers I got this Error:Response not sucessful:Received status code 500 In React native otherwise it's work perfectly . What is issue here?
Can you try then function for async function
getToken().then((token) => {
const client = new ApolloClient({
link: createUploadLink({
uri: 'http://192.168.1.82:8080/graphql',
headers: {
authorization: token
}
}),
cache: new InMemoryCache()
});
});
I've got a graphql URI that I need to query from my react-native App. This URI is public and I've got access to its schema/structure when I simply type the URI in my browser.
As soon as I try to query it from my code, I get the [TypeError: Network request failed] error (logs are created in the function that builds my ApolloClient).
I've checked the URI a million time, it's the same as the one I put in my browser, and the one I've used in the past to successfully query the DB.
This is the client-building function:
export function initServices({
uri,
authToken,
mockMeanDelay = 400,
mock = false,
mockScenarios = [],
}: Options): Services {
let mockRemoteController = null;
let linkToOutsideWorld: ApolloLink;
const messageBus = createMessageBus();
const terminatingLink = createUploadLink({
uri: CORRECT_URI_HERE,
})
const authLink = setContext(async (_, { headers }) => {
const token = await authToken();
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
},
};
});
linkToOutsideWorld = from([authLink, withCustomScalars(), terminatingLink]);
const errorReportingLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
// eslint-disable-next-line no-console
console.error(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
),
);
// eslint-disable-next-line no-console
if (networkError) console.error(`[Network error]: ${networkError}`);
});
const link = ApolloLink.from([errorReportingLink, linkToOutsideWorld]);
const fragmentMatcher = new IntrospectionFragmentMatcher({
// #ts-ignore
introspectionQueryResultData: introspectionResult,
});
const apolloClient = new ApolloClient({
link,
defaultOptions: {
watchQuery: {
// We prefer using a `cache-and-network` policy so that screens
// are always in sync with backend
// Otherwise, the default policy would not fetch the server
// data from server if the result of query is already in cache
fetchPolicy: 'cache-and-network',
},
},
cache: new InMemoryCache({
cacheRedirects: {
Query: {
// #ts-ignore issue in typing of cacheRedirects
userById: (_, { userId }: QueryUserByIdArgs, { getCacheKey }) =>
getCacheKey({ __typename: 'User', id: userId }),
// #ts-ignore issue in typing of cacheRedirects
gatheringSpaceById: (
_,
{ gatheringSpaceId }: QueryGatheringSpaceByIdArgs,
{ getCacheKey },
) =>
getCacheKey({
__typename: 'GatheringSpace',
id: gatheringSpaceId,
}),
// #ts-ignore issue in typing of cacheRedirects
gatheringInstanceById: (
_,
{ gatheringInstanceId }: QueryGatheringInstanceByIdArgs,
{ getCacheKey },
) =>
getCacheKey({
__typename: 'GatheringInstance',
id: gatheringInstanceId,
}),
},
},
fragmentMatcher,
}),
});
return { apolloClient, messageBus, mockRemoteController };
}
When I replace the URI with another publicly available one, it seems to work so my guess is that there's an issue with the back-end side. But how is it possible that I have full access to the schema and queries with my browser?
Any tips to help debugging are welcome too!
Thanks for your help!
I’m trying to set an authorization middleware yo my Apollo Client but something is not working. I can set the corresponding header correctly but my server still responds as i’m not authenticated.
Tried this:
const httpLink = new HttpLink({
uri: process.env.API_GRAPHQL,
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem("token");
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
});
const apolloClient = new ApolloClient({
link: authLink.concat(httpLink),
includeUnusedVariables: true,
credentials: "include",
connectToDevTools: true,
cache: new InMemoryCache({})
And this:
const httpLink = new HttpLink({
uri: process.env.API_GRAPHQL,
});
const authMiddleware = new ApolloLink((operation, forward) => {
var token = localStorage.getItem("token");
operation.setContext({
headers: {
Authorization: token ? `Bearer ${token}` : "",
},
});
console.log(operation);
return forward(operation);
});
const apolloClient = new ApolloClient({
link: authMiddleware.concat(httpLink),
includeUnusedVariables: true,
credentials: "include",
connectToDevTools: true,
cache: new InMemoryCache({})
I’ve also tried with createHttpLink instead of new httpLink, and with concat(middleware, httpLink) instead of middleware.concat(httpLink).
The sent request has correctly set the token, but the response is still an auth error.
If i do not set any middleware and hard-code my token the Client works fine, something in the construction of my link is not working…
Is there something i’m missing? what are my possibilities? Is there a way I can debug my client?
Thank’s in advance.
I'm using Apollo Client as a graphql client on my next.js application, Here is the function that creates a client for me:
let client: ApolloClient<any>;
export const __ssrMode__: boolean = typeof window === "undefined";
export const uri: string = "http://localhost:3001/graphql";
const createApolloClient = (): ApolloClient<any> => {
return new ApolloClient({
credentials: "include",
ssrMode: __ssrMode__,
link: createHttpLink({
uri,
credentials: "include",
}),
cache: new InMemoryCache(),
});
};
Surprisingly, when I make a mutation to the graphql server I'm able to set the cookies but, I'm not able to get the cookies from the client. What may be possibily the problem?
I came to the same problem, my solution was to create a client every time a server-side rendering is made, maybe it's not ideal to have a client to execute GraphQL calls in the browser and others in the server but it's what worked best for me. This is the code:
import { ApolloClient, createHttpLink, InMemoryCache } from '#apollo/client';
import { NextPageContext } from 'next';
import { setContext } from '#apollo/client/link/context';
export const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
credentials: 'include',
});
const CreateClient = (ctx: NextPageContext | null) => {
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
cookie:
(typeof window === 'undefined'
? ctx?.req?.headers.cookie || undefined
: undefined) || '',
},
};
});
return new ApolloClient({
credentials: 'include',
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
ssrMode: true,
});
};
export default CreateClient;
So, what I do is pass the context from the getServerSideProps and see if I have some cookies there, if so I just set the cookies, you can also send the authorization token if it's in the cookie. To call it is very simple:
export async function getServerSideProps(context: NextPageContext) {
const client = CreateClient(context);
const { data } = await client.query({
query: SOME_QUERY,
});
return {
props: {
data,
},
};
}
You can also do a HOC as in the Ben Awad tutorial Apollo Client HOC but I think it was too much for what I was trying to do. Hope it helped you or helps someone there :)
Also, I'm using Next 12.1.5 and React 18
I'm learning graphQL with React native and using the apollo client. I'm experimenting with some code that has a simple login screen and I'm trying to check my understanding of the cache. My graphql client code is below. By turning on the debug for the persistCache I see the line when use CMD + R to reload an iOS simulator with expo. This suggests the cache is working.
[apollo-cache-persist] Restored cache of size 29
My question is what else is needed to complete the overall process of not needing to login again? I assume I need to maintain state on whether it's logged in and not show the login screen. I'm after some examples which show this.
const retryLink = new RetryLink({
delay: {
initial: 300,
max: 5000,
jitter: true
},
attempts: {
max: Infinity,
retryIf: (error = {}) => {
return error.statusCode > 299 || !error.statusCode
}
}
});
const formatObject = data => _.isObject(data) ? JSON.stringify(data) : data;
const formatGraphQLError = err =>
`Message: ${err.message}, Location: ${formatObject(
err.locations
)}`;
const errorLink = onError(({ networkError = "", graphQLErrors = [] } = {}) => {
if (networkError)
console.log(`[Network Error]: ${networkError}`);
if (graphQLErrors.length)
graphQLErrors.map(formatGraphQLError).forEach(err => console.log(`[GraphQL Error]: ${err}`))
});
const authLink = setContext(async (_, { headers }) => {
const token = await Auth.token();
if (token)
return {
headers: {
...headers,
authorization: `Bearer ${token}`
}
};
else return { headers };
});
const httpLink = new HttpLink({
uri: Config.apiUrl
});
const cache = new InMemoryCache();
// Set up cache persistence.
persistCache({
cache,
storage: AsyncStorage,
trigger: 'background',
debug: true
});
const logLink = new ApolloLink((operation, forward) => {
console.log("Running GraphQL query or mutation");
return forward(operation);
});
//--
//-- Combine the links in your required order.
//--
let _notifications = 42;
const client = new ApolloClient({
resolvers: {
Query: {
permission: async (_, { type }) => await Permissions.askAsync(type),
token: async () => await Auth.token(),
notifications: () => _notifications
},
Mutation: {
login: async (_, { email, password }) => {
return await Auth.login(email, password)
},
updateNotifications: async (_, { notifications }) => _notifications = notifications
}
},
link: ApolloLink.from([
logLink,
retryLink,
errorLink,
authLink,
httpLink
]),
cache
});
export default client;