I've been trying to send a cookie back to the client from the server. I get the response data but i don't see "set-cookie" in the response headers
My Apollo Server Configuration:
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, connection, res }) => ({
dummyModels: dummyModels,
models: models,
req,
connection,
res,
currentUser: dummyModels.users[2],
dummyUsers: dummyModels.dummyUsers,
}),
});
app.use(cors({
credentials: true,
origin: 'http://localhost:3000',
// preflightContinue: true,
}));
My resolver:
login: async (parent, args, context) => {
const _include_headers = function(body, response, resolveWithFullResponse) {
return {'headers': response.headers, 'data': body};
};
const loginRequestOptions = {
method: 'POST',
uri: 'http://localhost:3000/incorta/authservice/login',
qs: {
// access_token: 'xxxxx xxxxx', // -> uri + '?access_token=xxxxx%20xxxxx'
user: args.input.username,
pass: args.input.password,
tenant: args.input.tenantName,
},
transform: _include_headers,
json: true // Automatically parses the JSON string in the response
};
const loginResponse = await request(loginRequestOptions);
console.log(loginResponse);
context.res.cookie(
'JSESSIONID',
tough.Cookie.parse(loginResponse.headers['set-cookie'][0]).value,
{
// expires : new Date(Date.now() + 9999999),
// path: '/incorta/',
// HttpOnly: false,
// maxAge: 1000 * 60 * 60 * 24 * 99, // 99 days
},
);
context.res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
return loginResponse.data;
},
Note: i'm using request-promise-native to make the request
My Apollo Client Configuration:
const httpLink = createHttpLink({
uri: 'http://172.16.16.130:4000/graphql',
credentials: 'include',
fetchOptions: {
credentials: 'include',
},
});
const wsLink = new WebSocketLink({
uri: 'ws://172.16.16.130:4000/graphql',
options: {
reconnect: true,
connectionParams: {
headers: {
'x-user-header': localStorage.getItem('userObject'),
},
},
}
});
const terminatingLink = split(
// split based on operation type
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const link = ApolloLink.from([terminatingLink]);
const cache = new InMemoryCache();
export const client = new ApolloClient({
link,
cache,
});
I have tried tinkering with options. i don't know what i'm missing here.
You can use the apollo-server-plugin-http-headers package for setting cookies in apollo server.
Usage is as simple as this from within your resolvers:
context.setCookies.push({
name: "cookieName",
value: "cookieContent",
options: {
domain: "example.com",
expires: new Date("2021-01-01T00:00:00"),
httpOnly: true,
maxAge: 3600,
path: "/",
sameSite: true,
secure: true
}
});
Related
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 looking to pass the request context into a KOA middleware that is generated from a require (https://github.com/Shopify/koa-shopify-auth). I set some API keys that I need to pass into it from a previous middleware but have no access to them when I reach createShopifyAuth.
I've tried passing in the global server.context but it doesn't seem to be set from the previous middleware.
server.use(async (ctx, next) => {
await shopifyKeys;
if (url.parse(ctx.originalUrl, true).query.shop) {
const shops = url.parse(ctx.originalUrl, true).query.shop;
server.context.keys = [shopifyKeys[shops].key, shopifyKeys[shops].secret];
console.log(server.context.keys);
}
return next();
});
server.use(
createShopifyAuth({
apiKey: server.context.keys[0],
secret: server.context.keys[1],
scopes: [
'read_products',
'read_checkouts',
'read_orders',
'write_orders',
],
async afterAuth(ctx) {
const { shop, accessToken } = ctx.session;
ctx.cookies.set('shopOrigin', shop, {
httpOnly: false,
secure: true,
sameSite: 'none',
});
const registration = await registerWebhook({
address: `${HOST}/webhooks/orders/paid`,
topic: 'ORDERS_PAID',
accessToken,
shop,
apiVersion: ApiVersion.July20,
});
if (registration.success) {
console.log('Successfully registered webhook!');
} else {
console.log(
'Failed to register webhook',
registration.result.data.webhookSubscriptionCreate.userErrors,
);
}
ctx.redirect('/');
},
}),
);
Any help with figuring out how to get the context into the second server.use would be appreciated.
I am allegedly a newbie when it comes to KOA, but the only way I manage to make it was passing the data via cookies, individually. Here is an example:
server.use(
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET_KEY,
scopes: [
"read_products",
"write_products",
"read_script_tags",
"write_script_tags",
"read_themes",
"write_themes",
],
accessMode: "offline",
afterAuth(ctx) {
const { shop, accessToken } = ctx.session;
ctx.cookies.set("shopOrigin", shop, {
httpOnly: false,
secure: true,
sameSite: "none",
});
ctx.cookies.set("accessToken", accessToken, {
httpOnly: false,
secure: true,
sameSite: "none",
});
ctx.redirect("/");
},
}),
);
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;
I am using Robin Wieruch's fullstack boilerplate but it is missing authentication for subscriptions. It uses JWT token for sessions and it is working fine for http but for ws auth is completely missing.
I need to pass user trough context for subscriptions as well, I need session info in subscriptions resolver to be able to decide weather I should fire subscription or not.
I did search Apollo docs, I saw I should use onConnect: (connectionParams, webSocket, context) function, but there is no fully functional fullstack example, I am not sure how to pass JWT from client to be able to get it in webSocket object.
Here is what I have so far:
Server:
import express from 'express';
import {
ApolloServer,
AuthenticationError,
} from 'apollo-server-express';
const app = express();
app.use(cors());
const getMe = async req => {
const token = req.headers['x-token'];
if (token) {
try {
return await jwt.verify(token, process.env.SECRET);
} catch (e) {
throw new AuthenticationError(
'Your session expired. Sign in again.',
);
}
}
};
const server = new ApolloServer({
introspection: true,
typeDefs: schema,
resolvers,
subscriptions: {
onConnect: (connectionParams, webSocket, context) => {
console.log(webSocket);
},
},
context: async ({ req, connection }) => {
// subscriptions
if (connection) {
return {
// how to pass me here as well?
models,
};
}
// mutations and queries
if (req) {
const me = await getMe(req);
return {
models,
me,
secret: process.env.SECRET,
};
}
},
});
server.applyMiddleware({ app, path: '/graphql' });
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
const isTest = !!process.env.TEST_DATABASE_URL;
const isProduction = process.env.NODE_ENV === 'production';
const port = process.env.PORT || 8000;
httpServer.listen({ port }, () => {
console.log(`Apollo Server on http://localhost:${port}/graphql`);
});
Client:
const httpLink = createUploadLink({
uri: 'http://localhost:8000/graphql',
fetch: customFetch,
});
const wsLink = new WebSocketLink({
uri: `ws://localhost:8000/graphql`,
options: {
reconnect: true,
},
});
const terminatingLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return (
kind === 'OperationDefinition' && operation === 'subscription'
);
},
wsLink,
httpLink,
);
const authLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers = {} }) => {
const token = localStorage.getItem('token');
if (token) {
headers = { ...headers, 'x-token': token };
}
return { headers };
});
return forward(operation);
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.forEach(({ message, locations, path }) => {
console.log('GraphQL error', message);
if (message === 'UNAUTHENTICATED') {
signOut(client);
}
});
}
if (networkError) {
console.log('Network error', networkError);
if (networkError.statusCode === 401) {
signOut(client);
}
}
});
const link = ApolloLink.from([authLink, errorLink, terminatingLink]);
const cache = new InMemoryCache();
const client = new ApolloClient({
link,
cache,
resolvers,
typeDefs,
});
You need to use connectionParams to set the JWT from the client-side. Below is the code snippet using the angular framework:
const WS_URI = `wss://${environment.HOST}:${environment.PORT}${
environment.WS_PATH
}`;
const wsClient = subscriptionService.getWSClient(WS_URI, {
lazy: true,
// When connectionParams is a function, it gets evaluated before each connection.
connectionParams: () => {
return {
token: `Bearer ${authService.getJwt()}`
};
},
reconnect: true,
reconnectionAttempts: 5,
connectionCallback: (error: Error[]) => {
if (error) {
console.log(error);
}
console.log('connectionCallback');
},
inactivityTimeout: 1000
});
const wsLink = new WebSocketLink(wsClient);
In your server-side, you are correct, using onConnect event handler to handle the JWT. E.g.
const server = new ApolloServer({
typeDefs,
resolvers,
context: contextFunction,
introspection: true,
subscriptions: {
onConnect: (
connectionParams: IWebSocketConnectionParams,
webSocket: WebSocket,
connectionContext: ConnectionContext,
) => {
console.log('websocket connect');
console.log('connectionParams: ', connectionParams);
if (connectionParams.token) {
const token: string = validateToken(connectionParams.token);
const userConnector = new UserConnector<IMemoryDB>(memoryDB);
let user: IUser | undefined;
try {
const userType: UserType = UserType[token];
user = userConnector.findUserByUserType(userType);
} catch (error) {
throw error;
}
const context: ISubscriptionContext = {
// pubsub: postgresPubSub,
pubsub,
subscribeUser: user,
userConnector,
locationConnector: new LocationConnector<IMemoryDB>(memoryDB),
};
return context;
}
throw new Error('Missing auth token!');
},
onDisconnect: (webSocket: WebSocket, connectionContext: ConnectionContext) => {
console.log('websocket disconnect');
},
},
});
server-side: https://github.com/mrdulin/apollo-graphql-tutorial/blob/master/src/subscriptions/server.ts#L72
client-side: https://github.com/mrdulin/angular-apollo-starter/blob/master/src/app/graphql/graphql.module.ts#L38
I need to POST a large payload in a GraphQL mutation. How do I increase the body size limit of Apollo Server?
I'm using apollo-server-express version 2.9.3.
My code (simplified):
const myGraphQLSchema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
user: UserQuery,
},
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: () => ({
...UserMutations,
}),
}),
});
const apolloServer = new ApolloServer(schema: myGraphQLSchema);
const app = express();
app.use(apolloServer.getMiddleware({ path: '/graphql' });
Not exactly sure in which version it was added, but on 2.9.15 you can apply it in applyMiddleware function.
const apolloServer = new ApolloServer(someConfig);
apolloServer.applyMiddleware({
app,
cors: {
origin: true,
credentials: true,
},
bodyParserConfig: {
limit:"10mb"
}
});
Simply add an Express body parser before your Apollo server middleware:
import { json } from 'express';
app.use(json({ limit: '2mb' });
app.use(apolloServer.getMiddleware({ path: '/graphql' });
If you want to get fancy, you can have a separate body size limit for authenticated vs unauthenticated requests:
const jsonParsers = [
json({ limit: '16kb' }),
json({ limit: '2mb' }),
];
function parseJsonSmart(req: Request, res: Response, next: NextFunction) {
// How exactly you do auth depends on your app
const isAuthenticated = req.context.isAuthenticated();
return jsonParsers[isAuthenticated ? 1 : 0](req, res, next);
}
app.use(parseJsonSmart);
app.use(apolloServer.getMiddleware({ path: '/graphql' });