Intended outcome:
To resolve a promise inside an ApolloLink
middleware to adjust request headers.
Actual outcome:
Wasn't able to write code that works.
How to reproduce the issue:
import { ApolloLink } from 'apollo-client-preset'
export const authMiddleware = new ApolloLink((operation, forward) => {
resolvePromise().then(data => {
operation.setContext(({ headers = {} }) => ({
headers: {
...headers,
authorization: || null,
}
}))
return forward(operation)
})
})
There's no examples regarding this type of middleware in the docs. How can this be achieved?
Version
@matteodem 👋 ApolloLink
expects an Observable to be returned which makes this not work. To make it super easy, I wrote a setContext
link just for this use case! The docs for it are here but in short your code would be this:
import { setContext } from "apollo-link-context";
const authMiddleware = setContext((operation, { headers }) =>
resolvePromise().then(data => ({
...headers,
authorization: data.tokenOrSomething || null
}))
);
Since this runs on each operation, you may want to cache that lookup. More info on that can be found here
@jbaxleyiii how do i set custom headers? Can you describe the full example code for setting the middleware header via Observables? Consider caching the lookup is out of the subject . but at each operation, i need to set a custom headers value to a token which i get from Observable
applyNetworkInterface() {
this.apollo.getClient().networkInterface.use([{
//function in a curly braces = what syntax is this
applyMiddleware: (req: Request, next: (error?: any) => void) => {
if (!req.options.headers) {
req.options.headers = {};
}
this.afAuth.auth.onAuthStateChanged(user => {
if (user) {
Observable.fromPromise(user.getIdToken()).subscribe((token) => {
req.options.headers['x-auth-token'] = token;
next();
})
} else {
//에러 메세지 띄울것임
console.log('user not logged in yet');
}
})
}
}])
}
this is my code for setting middleware before updating to version 2.0.0
@jbaxleyiii
const http = httpLink.create({uri: OUR_API});
const asyncMiddleware = setContext((request) => new Promise((success, fail) => {
authService.getToken().subscribe((token) => {
console.log('token',token);
success({ token: "blah" })
})
}));
apollo.create({
link: asyncMiddleware.concat(http),
cache: new InMemoryCache()
})
why wouldn't this work at all?
@pycraft114 @matteodem Here is how I got it working:
import {
ApolloClient,
InMemoryCache,
ApolloLink,
from,
} from "apollo-client-preset";
import { setContext } from "apollo-link-context";
import { createHttpLink } from "apollo-link-http";
import { AsyncStorage } from "react-native";
const GRAPHQL_API_ENDPOINT = "https://path/to/graphql/api"
const httpLink = createHttpLink({
uri: GRAPHQL_API_ENDPOINT,
});
const authMiddleware = setContext(operation =>
somePromise().then(token => {
return {
// Make sure to actually set the headers here
headers: {
authorization: token || null,
},
};
})
);
const client = new ApolloClient({
link: from([authMiddleware, httpLink]),
cache: new InMemoryCache(),
});
@jbaxleyiii The documentation was a little confusing due to not having a fully working example with the HTTP link. I understand this is meant to be generalized for any link, but your advice in this issue was a little misleading due to having to set explicitly structure the context to what HttpLink expects. How can we make the documentation clearer in the migration and other related documentations?
@vtsatskin
Could you do an example of this without apollo-client-preset? When I use apollo-client-preset
in my project, I get a nextLink.execute not found
error. I had to downgrade apollo-link
to 1.0.0
to get it to run.
Here's what I currently have:
import React from "react";
import { AsyncStorage } from "react-native";
// Apollo Packages
import { ApolloProvider } from "react-apollo";
import { ApolloClient } from "apollo-client";
import { setContext } from "apollo-link-context";
import { HttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloLink, from } from "apollo-link";
import App from "./app/app";
const httpLink = new HttpLink({
uri: "___URI___"
});
const authMiddleware = new setContext(operation => {
AsyncStorage.getItem("token").then(token => {
console.log(token);
return {
headers: {
Authorization: `Bearer: ${token}` || null
}
};
});
});
const client = new ApolloClient({
link: from([authMiddleware, httpLink]),
cache: new InMemoryCache()
});
export default class Poqet extends React.Component {
render() {
return (
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
}
}
FIXED - Missed a set of curly braces right after operation....
Following the @vtsatskin's implementation, I've changed the authMiddleware
function to works with async/await
.
const authMiddleware = setContext(async (req, { headers }) => {
const token = await AsyncStorage.getItem(TOKEN_NAME);
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
},
};
});
@vtsatskin, thank you! Worked great!
@marceloogeda, your method worked as well! Using that implementation. Thanks!
I've tried with a few approaches, both a new ApolloLink
and setContext
directly. If I have the link simply return a new modified header, it works:
const authLink = setContext((_, { headers }) => ({ headers: { ...headers, myOwnHeader: true }}));
The second I wrap the return statement in some async functionality, the header wont be set. Even if it's a simple promise like this
const test = () => {
return new Promise((resolve, reject) => {
resolve("My value");
});
}
Has anyone had this problem? I simply cannot get it to add the header, no matter what.
"apollo-client": "^2.2.2",
"apollo-link": "^1.1.0",
Managed to make it work using this approach also including pubsub:
new ApolloClientOriginal({
link: concat(
from([
setContext(async operation => {
const method = onRequest || noopHeaders;
let headersMap: Headers = (await method.call(operation)) || {};
headersMap.forEach((v, k) => {
headers[k] = v;
});
return {
headers
};
}),
new ApolloLink((operation, forward) => forward(operation))
]),
split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
new WebSocketLink(
new SubscriptionClient(pubsub, {
lazy: true,
connectionParams: headers,
reconnect: true
})
),
createHttpLink({ uri })
)
),
cache: new InMemoryCache()
});
Reference: https://github.com/rxdi/graphql-client/blob/master/src/index.ts#L35
I have the same problem as @DennisBaekgaard . Is there any update on this issue?
Thanks!
My code:
const authMiddleware = setContext((_, { headers }) => {
somePromise().then((accessToken) => {
return {
headers: {
...headers,
'Authorization': accessToken ? `Bearer ${accessToken}` : null,
'X-Requested-With': 'XmlHttpRequest',
},
};
}).catch((err) => {
// ...
});
});
My dependencies:
"apollo-boost": "^0.1.16",
"apollo-cache-inmemory": "^1.2.10",
"apollo-client": "^2.4.2",
"apollo-link": "^1.2.3",
"apollo-link-http": "^1.5.5",
"apollo-link-state": "^0.4.2",
"apollo-link-ws": "^1.0.14",
"apollo-link-context": "^1.0.18",
How would one use this with the new [v3] ApolloLink?
const authMiddleware = new ApolloLink((operation, forward) => {
operation.setContext(async ({ headers = {} }) => {
const token = await AsyncStorage.getItem("token")
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
}
})
return forward(operation)
})
how would i await the async request here?
I know i can use the setContext helper, just wondering if it was possible to use the new ApolloLink
Thanks
@JClackett your middleware is correct and can be used as is
It doesn't work though :(
The auth header isn't being set, seems to not await the AsyncStorage.getItem
Having the same issue as @JClackett. Is async just not supported in v3 yet?
I'm planning on doing the same thing, is it actually supported?
Have you tried the latest beta? https://www.npmjs.com/package/apollo-link-context/v/2.0.0-beta.0
I'm totally stuck on this.
Anyone see anything wrong in my code? The console.logs aren't working at all.
import { ApolloClient } from "apollo-client";
import { useQuery } from "@apollo/react-hooks";
import { InMemoryCache } from "apollo-cache-inmemory";
import { createHttpLink } from "apollo-link-http";
import { setContext } from "apollo-link-context";
import AsyncStorage from "@react-native-community/async-storage";
import { resolvers, typeDefs } from "./resolvers";
const cache = new InMemoryCache();
const httpLink = createHttpLink({
uri: "https://foobar.com"
});
const authLink = setContext(async (_, { headers }) => {
const authToken = await AsyncStorage.getItem("authToken");
console.log("xxxxxx");
console.log("Final token result:", authToken);
return {
headers: {
...headers,
authorization: authToken ? `Bearer ${authToken}` : "",
},
};
});
const client = new ApolloClient({
cache: cache,
link: authLink.concat(httpLink),
typeDefs: typeDefs,
resolvers: resolvers,
});
export default client;
My console.log calls just aren't called - at all. No idea why.
I'm totally stuck on this.
Anyone see anything wrong in my code? The console.logs aren't working at all.
import { ApolloClient } from "apollo-client"; import { useQuery } from "@apollo/react-hooks"; import { InMemoryCache } from "apollo-cache-inmemory"; import { createHttpLink } from "apollo-link-http"; import { setContext } from "apollo-link-context"; import AsyncStorage from "@react-native-community/async-storage"; import { resolvers, typeDefs } from "./resolvers"; const cache = new InMemoryCache(); const httpLink = createHttpLink({ uri: "https://foobar.com" }); const authLink = setContext(async (_, { headers }) => { const authToken = await AsyncStorage.getItem("authToken"); console.log("xxxxxx"); console.log("Final token result:", authToken); return { headers: { ...headers, authorization: authToken ? `Bearer ${authToken}` : "", }, }; }); const client = new ApolloClient({ cache: cache, link: authLink.concat(httpLink), typeDefs: typeDefs, resolvers: resolvers, }); export default client;
My console.log calls just aren't called - at all. No idea why.
Although I'm far from a Apollo/graphql wizard, I can just provide you with how I did it. (can't guarantee this is the most efficiënt/cleanest way).
I have a very similar setup, I think something along these lines should work for you
import {ApolloLink} from 'apollo-link';
...
const client = new ApolloClient({
link: ApolloLink.from([authLink, httpLink]),
cache,
typeDefs,
resolvers,
});
@sergiotapia-papa did you manage to fix this? I'm facing the same issue
@felipepalazzo I did fix it dude, it was a pain in the ass! I'll post in what I did tomorrow morning when I get into work.
@JClackett did you ever solve this? 😬
@moritzjacobs Kind of...
Used this little snippet as a drop in replacement
export function setContext(setter: ContextSetter): ApolloLink {
return new ApolloLink((operation: Operation, forward: NextLink) => {
const { ...request } = operation
return new Observable((observer) => {
let handle: any
Promise.resolve(request)
.then((req) => setter(req, operation.getContext()))
.then(operation.setContext)
.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()
}
})
})
}
Then used it like:
const httpLink = new HttpLink({
uri: API_URL,
})
const authMiddleware = setContext(async () => {
const token = await AsyncStorage.getItem(AUTH_TOKEN_KEY)
return {
headers: {
authorization: token ? `Bearer ${token}` : "",
},
}
})
const client = new ApolloClient({
link: concat(authMiddleware, httpLink),
cache: new InMemoryCache(),
defaultOptions: {
mutate: {
errorPolicy: "all",
},
query: {
errorPolicy: "all",
},
},
})
@sergiotapia-papa Can you post your solution?
I wasn't able to get it working with the above snippets. Here's what worked for me... I've edited it somewhat to remove react/hooks specific code (I create a useClient
hook which returns client
).
import getToken from "../getToken"
const httpLink = new HttpLink({
uri:
process.env.REACT_APP_GRAPHQL_ENDPOINT ||
"https://rti-hasura.herokuapp.com/v1/graphql",
})
const cache = new InMemoryCache()
const createRequestLink = (getToken: GetToken) =>
new ApolloLink(
(operation, forward) =>
new Observable((observer) => {
let handle: Subscription
Promise.resolve(operation)
.then(async (operation: Operation) => {
const token = await getToken()
operation.setContext({
headers: {
Authorization: token ? `Bearer ${token}` : null,
},
})
})
.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: concat(createRequestLink(getToken), httpLink),
cache,
defaultOptions: {
mutate: {
errorPolicy: "all",
},
query: {
errorPolicy: "all",
},
},
})
I'm using auth0-spa-js
together with @apollo/client
version, and this is what works for me:
...
const { isAuthenticated, getTokenSilently } = useAuth0()
const restLink = new RestLink({
uri: composeUrl(REST_API_URL, 'https'),
})
...
const withToken = setContext(() =>
getTokenSilently().then(tkn => {
const token = tkn
return { token }
})
)
...
const authLink = new ApolloLink((operation, forward) => {
const { token } = operation.getContext()
operation.setContext(({ headers }) => ({
headers: {
...headers,
Authorization: isAuthenticated ? `Bearer ${token}` : null,
},
}))
return forward(operation)
})
...
const client = new ApolloClient({
link: ApolloLink.from([withToken, authLink, restLink]),
cache,
})
Hope it helps :-)
For the record, fetching an authentication token asynchronously in setContext
using async/await appears to be working fine with Apollo Client 3, at least with version 3.1.2:
import {
ApolloClient,
InMemoryCache,
createHttpLink,
ApolloProvider,
from,
gql
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'
import { Auth } from 'aws-amplify'
const httpLink = createHttpLink({
uri: process.env.GRAPH_API_URL
})
const authenticationLink = setContext(async (_, { headers }) => {
const token = (await Auth.currentSession()).idToken.jwtToken
return {
headers: {
...headers,
Authorization: token
}
}
})
const apolloClient = new ApolloClient({
name: 'web',
version: '1.0.0',
link: from([authenticationLink, httpLink]),
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network'
}
}
})
with idea from @edufschmidt,
import { ApolloClient, ApolloLink, createHttpLink, InMemoryCache } from "@apollo/client";
import { setContext } from '@apollo/client/link/context';
import AsyncStorage from "@react-native-community/async-storage";
const httpLink = createHttpLink({ uri: `YOUR_HOST` });
const withToken = setContext(async () =>{
const token = await AsyncStorage.getItem('token')
return { token }
})
const authMiddleware = new ApolloLink((operation, forward) => {
const { token } = operation.getContext();
operation.setContext(() => ({
headers: {
Authorization: token ? `Bearer ${token}` : '',
}
}));
return forward(operation);
});
const link = ApolloLink.from([withToken, authMiddleware.concat(httpLink)]);
const client = new ApolloClient({
link,
...
This works :)
@yjb94 any possible ideas why your solution works perfectly if I inject the token manually but when I use template literal token, it doesn't work? this is killin me hah.
@HughBerriez never heard of your issue in any cases... what happens if you do 'Bearer ' + token
?
Found the issue - token was getting mapped to auth header with quotes in the payload and we weren't stripping the quotes on the backend. So performed the fix on the frontend temporarily. Ty though!
Most helpful comment
@pycraft114 @matteodem Here is how I got it working:
@jbaxleyiii The documentation was a little confusing due to not having a fully working example with the HTTP link. I understand this is meant to be generalized for any link, but your advice in this issue was a little misleading due to having to set explicitly structure the context to what HttpLink expects. How can we make the documentation clearer in the migration and other related documentations?