Can someone help me on this, My setup was as follows prior to Apollo 2.0, I had a server.js in which i used express and graphql-server-express
I had a http only cookie session, when a user logs in I set the jwt token as a cookie and it is set in browser as http only.
On subsequent request I validate the cookie that the browser passes back. It was all working fine and I could access
the token from req.session.token in any other resolver and validate the jwt token saved in the cookie session.
server.js
import express from 'express';
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express';
import { ApolloEngine } from 'apollo-engine';
import bodyParser from 'body-parser';
import cors from 'cors';
import cookieSession from 'cookie-session';
import schema from './schema/';
const server = express();
server.use(
cookieSession({
name: 'session',
keys: 'k1,k2',
maxAge: 30 * 60 * 1000,
domain: '.mydomain.com',
path: '/',
}),
);
const corsOptions = {
origin: 'http://local.mydomain.com:3000',
credentials: true,
methods: ['GET', 'PUT', 'POST', 'OPTIONS'],
};
server.use(cors(corsOptions));
server.use(
'/graphql',
bodyParser.json(),
graphqlExpress(req => ({
schema,
tracing: true,
context: { req },
})),
);
if (process.env.NODE_ENV !== 'production') {
server.use('/graphiql',graphiqlExpress({endpointURL: '/graphql'}));
}
const engine = new ApolloEngine({
apiKey: engineConfig.apiKey,
});
engine.listen(
{
port: 3000,
graphqlPaths: ['/graphql'],
expressApp: server,
},
() => {console.log('GraphiQL is now running');},
);
authenticateResolver.js
const authenticateResolver = {
Query: {
authenticate: async (root, args, context) => {
const { req } = context;
const auth = `Basic ${Buffer.from(`${args.username}:${args.password}`).toString('base64')}`;
const axiosResponse = await axios.post("localhot:8080/login, 'true',
{
headers: {
Authorization: auth,
},
});
if (axiosResponse.status === 200 && axiosResponse.data.token) {
req.session.token = axiosResponse.data.token;
}
return {
status: 200,
};
},
But when I upgraded to Apollo 2.0 my server.js code changed, authenticateResolver was as is.
I am now unable to access req.session.token in any subsequent requests since the cookie session is not getting set.
When I open Developer tools in chrome I cannot see the cookie being set when Authentication is called.
What am I doing wrong here ?
server.js # After Apollo 2.0 upgrade
import express from 'express';
import { ApolloServer, gql } from 'apollo-server-express';
import cors from 'cors';
import cookieSession from 'cookie-session';
import { mergedTypes, resolvers } from './schema/';
const server = express();
server.use(
cookieSession({
name: 'session',
keys: 'k1,k2',
maxAge: 30 * 60 * 1000,
domain: '.mydomain.com',
path: '/',
}),
);
const corsOptions = {
origin: 'http://local.mydomain.com:3000',
credentials: true,
methods: ['GET', 'PUT', 'POST', 'OPTIONS'],
};
server.use(cors(corsOptions));
server.listen({ port: 3000 }, () => {
console.log('Server ready');
console.log('Try your health check at: .well-known/apollo/app-health');
});
const apollo = new ApolloServer({
typeDefs: gql`
${mergedTypes}
`,
resolvers,
engine: false,
context: ({ req }) => ({ req }),
});
apollo.applyMiddleware({
server
});
Yes, If you look at the graphql playground there is a settings option if you click on that you can observe few settings, one of them being
"request.credentials": "omit" just change it to
"request.credentials": "include" and save settings and it should now work.
My code looks as follows as well:
const app = express()
app.use(
cookieSession({
name: 'session',
keys: corsConfig.cookieSecret.split(','),
maxAge: 60 * 60 * 1000,
domain: corsConfig.cookieDomain,
path: '/',
})
)
const corsOptions = {
origin: corsConfig.corsWhitelist.split(','),
credentials: true,
methods: ['GET', 'PUT', 'POST', 'OPTIONS'],
}
app.use(cors(corsOptions))
const apollo = new ApolloServer({
typeDefs: gql`
${mergedTypes}
`,
resolvers,
engine: false,
context: ({ req }) => ({ req }),
tracing: true,
debug: !process.env.PRODUCTION,
introspection: !process.env.PRODUCTION,
})
apollo.applyMiddleware({
app,
path: '/',
cors: corsOptions,
})
app.listen({ port: engineConfig.port }, () => {
console.log('🚀 - Server ready')
console.log('Try your health check at: .well-known/apollo/app-health')
})
Related
I have service Test-Service which provides some data via graphql and Apollo Gateway. And I would like to cache requests on Apollo gateway, but it doesn't work, I still see in logs of Test-Service that it receives requests. Here is code of Apollo Gateway
import {ApolloServer} from'#apollo/server'
import {ApolloGateway, IntrospectAndCompose} from '#apollo/gateway'
import {hiveApollo} from'#graphql-hive/client'
import {KeyvAdapter} from"#apollo/utils.keyvadapter"
import {ApolloServerPluginCacheControl} from"#apollo/server/plugin/cacheControl"
import {startStandaloneServer} from'#apollo/server/standalone'
// const Keyv = require("keyv");
import {InMemoryLRUCache} from "#apollo/utils.keyvaluecache"
import responseCachePlugin from '#apollo/server-plugin-response-cache';
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{name: 'test-service', url: 'http://test-service-svc/graphql'}
],
pollIntervalInMs: 5000
})
})
async function startApolloServer() {
const server = new ApolloServer({
gateway,
plugins: [
responseCachePlugin()
],
// cache: new KeyvAdapter(new Keyv({
// url: "redis://graphql-gateway-redis:6379",
// sentinels: [
// {host: "graphql-gateway-redis", port: 26379}
// ]
// })
// )
cache: new InMemoryLRUCache(),
cacheControl: {
defaultMaxAge: 50,
},
});
const {url} = await startStandaloneServer(server, {
listen: {port: 4000}
})
console.log(`🚀 Server ready at: ${url}`)
}
startApolloServer();
As you can see I also tried using redis. And here is example schema:
type Query {
getInfo(
userId: Int
): [Response]
getInfoCached(
userId: Int
): [Response] #cacheControl(maxAge: 30)
}
type Response #cacheControl(maxAge: 30) {
id: Int,
createdAt: DateTime, #cacheControl(maxAge: 10),
firstName: String
}
I'm wondering on how to pass the next-auth session as context to my nexus queries. The reson behind is that I want the sessions email to retrieve data from my database with nexus. I'm also using Apollo Server and next-connect here.
Here's what I tried:
The Apollo Server
import { ApolloServer } from "apollo-server-micro";
import { MicroRequest } from 'apollo-server-micro/dist/types';
import { ServerResponse } from 'http';
import { getRequestOrigin } from './../../server/get-request-origin';
import handler from "../../server/api-route";
import prisma from "../../server/db/prisma";
import { schema } from "../../server/graphql/schema";
export const config = {
api: {
bodyParser: false,
},
};
export interface GraphQLContext {
session?: {
user: {
name: string
email: string
image: string
},
expires: Date // This is the expiry of the session, not any of the tokens within the session
};
prisma: typeof prisma;
origin: string;
}
const apolloServer = new ApolloServer({
schema,
context: ({ req }): GraphQLContext => ({
session: req.user,
origin: getRequestOrigin(req),
prisma,
}),
})
const startServer = apolloServer.start();
export default handler().use((req: MicroRequest, res: ServerResponse) => {
startServer.then(() => {
apolloServer.createHandler({
path: "/api",
})(req, res);
});
});
My middleware to pass the session:
import { NextApiRequest, NextApiResponse } from "next";
import { Session } from 'next-auth';
import cookieSession from "cookie-session";
import { error } from "next/dist/build/output/log";
import { getSession } from 'next-auth/react';
import nc from "next-connect";
import { trustProxyMiddleware } from "./trust-proxy-middleware";
export interface Request extends NextApiRequest {
user?: Session | null;
}
const COOKIE_SECRET = process.env.COOKIE_SECRET;
/**
* Create an API route handler with next-connect and all the necessary middlewares
*
* #example
* ```ts
* export default handler().get((req, res) => { ... })
* ```
*/
function handler() {
if (!COOKIE_SECRET)
throw new Error(`Please add COOKIE_SECRET to your .env.local file!`);
return (
nc<Request, NextApiResponse>({
onError: (err, _, res) => {
error(err);
res.status(500).end(err.toString());
},
})
// In order for authentication to work on Vercel, req.protocol needs to be set correctly.
// However, Vercel's and Netlify's reverse proxy setup breaks req.protocol, which the custom
// trustProxyMiddleware fixes again.
.use(trustProxyMiddleware)
.use(
cookieSession({
name: "session",
keys: [COOKIE_SECRET],
maxAge: 24 * 60 * 60 * 1000 * 30,
// Do not change the lines below, they make cy.auth() work in e2e tests
secure:
process.env.NODE_ENV !== "development" &&
!process.env.INSECURE_AUTH,
signed:
process.env.NODE_ENV !== "development" &&
!process.env.INSECURE_AUTH,
})
)
.use(async (req: Request, res: NextApiResponse) => {
const session = await getSession({ req })
if (session) {
// Signed in
console.log("Session", JSON.stringify(session, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
})
);
}
export default handler;
And the nexus query
const queries = extendType({
type: "Query",
definition: (t) => {
t.field("currentUser", {
type: "User",
resolve: (_, __, ctx) => {
console.log(ctx);
if (!ctx.session?.user.email) return null;
return prisma.user.findUnique({
where: {
email: ctx.session?.user.email,
},
});
},
});
},
});
I'm trying to setup apollo server in my NextJS project, I'm using apollo-server-micro and I ran into these issues:
the apollo studio sandbox is unable to reach the server due to CORS.
This is in pages/api/graphql
import { ApolloServer } from "apollo-server-micro";
import { typeDefs } from "./schemas";
import { resolvers } from "./resolvers/index";
import { createContext } from "./db/context";
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: ({ res, req }) => createContext(res, req),
uploads: false,
introspection: true,
formatError: (error) => {
console.log(error);
return error;
},
});
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
module.exports = apolloServer
.start()
.then(() => apolloServer.createHandler({ path: "/api/graphql" }));
then I tried to enable cors but without success because I get this error:
API resolved without sending a response for /api/graphql, this may result in stalled requests.
import { ApolloServer } from "apollo-server-micro";
import { typeDefs } from "./schemas";
import { resolvers } from "./resolvers/index";
import { createContext } from "./db/context";
import { send } from "micro";
import cors from "micro-cors";
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: ({ res, req }) => createContext(res, req),
uploads: false,
introspection: true,
formatError: (error) => {
console.log(error);
return error;
},
});
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
};
module.exports = apolloServer.start().then(() => {
const handler = apolloServer.createHandler({ path: "/api/graphql" });
return cors((req, res) => {
console.log(res);
return req.method === "OPTIONS" ? send(res, 200, 'ok') : handler(req, res);
});
});
Ok so I see a lot of answers for how to enable cors for apollo-express, but I haven't found one really for apollo-server-lambda.
This is the error that I'm getting from chrome:
Access to XMLHttpRequest at 'https://5j3gae3086.execute-api.us-east-2.amazonaws.com/alpha/'
from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight
request doesn't pass access control check: The 'Access-Control-Allow-Origin' header
has a value 'https://example.com' that is not equal to the supplied origin.
I do not know how to change the value "https://example.com." Here is my code of how I'm trying to create the server:
const { ApolloServer } = require('apollo-server-lambda')
const typeDefs = require('./schema')
const resolvers = require ('./resolvers')
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
playground: {
endpoint: "/alpha/graphql",
},
});
exports.graphqlHandler = server.createHandler({
cors: {
// origin: true,
origin: "http://localhost:4200", // <-- This is not changing the header value. Do I need to do it from the frontend?
credentials: true,
},
});
What else do I need to do here?
Edit
I'm not sure if this is relevant, but here is my graphql.module.ts file. This is how I'm setting grahql in the frontend:
import { NgModule } from '#angular/core';
import { APOLLO_OPTIONS } from 'apollo-angular';
import { ApolloClientOptions, InMemoryCache } from '#apollo/client/core';
import { HttpLink } from 'apollo-angular/http';
const uri = 'https://5j3gae3086.execute-api.us-east-2.amazonaws.com/alpha/'; // <-- add the URL of the GraphQL server here
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
return {
link: httpLink.create({ uri,
// these comments are things that I tried with no luck :(
// fetchOptions: {
// mode: 'no-cors',
// },
// headers: {
// 'Access-Control-Allow-Origin': 'http://localhost:4200',
// 'Access-Control-Allow-Methods': 'POST',
// 'Access-Control-Allow-Headers': 'application/json'
// "Access-Control-Allow-Credentials" : true
// "X-CSRFToken": Cookies.get('csrftoken')
// },
}),
cache: new InMemoryCache(),
};
}
#NgModule({
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule { }
Also in case anyone is curious, I'm using AWS Api Gateway to use the lambda, but I believe I have the configuration for cors added correctly on that.
I'm at a loss with this. What do I need to change?
Following the CORS setup instructions here I can successfully use apollo-angular to return results for a simple query. No special headers etc. were needed.
https://www.apollographql.com/docs/apollo-server/deployment/lambda/
// serverless.yml
events:
- http:
path: graphql
method: post
cors: true
- http:
path: graphql
method: get
cors: true
// graphql.js
exports.graphqlHandler = server.createHandler({
cors: {
origin: '*',
credentials: true,
},
});
// graphql.module.ts
import {NgModule} from '#angular/core';
import {APOLLO_OPTIONS} from 'apollo-angular';
import {ApolloClientOptions, InMemoryCache} from '#apollo/client/core';
import {HttpLink} from 'apollo-angular/http';
const uri = 'https://xxx/dev/graphql';
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
return {
link: httpLink.create({uri}),
cache: new InMemoryCache(),
};
}
#NgModule({
providers: [
{
provide: APOLLO_OPTIONS,
useFactory: createApollo,
deps: [HttpLink],
},
],
})
export class GraphQLModule {}
// In Angular 10
this.apollo
.watchQuery({
query: gql`
{
users {
email
}
}
`,
})
.valueChanges.subscribe(result => {
console.log(result.data);
});
Unlike an initial question, graphql.js is replaced to typescript as following.
// graphql.ts
exports.graphqlHandler = server.createHandler({
expressGetMiddlewareOptions: {
cors: {
origin: '*',
credentials: true,
},
},
});
I am trying to use REST endpoints to post data and GraphQL for query and fetch along with apollo-link-state. My rest endpoint is getting hit and application is getting created. But when I try to run the query to write to cache it's not hitting the graphql endpoint. and I keep getting the following error:
Unhandled Rejection (Error): Can't find field findApplicationByUuid({"uuid":"912dc46d-2ef8-4a77-91bc-fec421f5e4bc"}) on object (ROOT_QUERY) {
"application": {
"type": "id",
"id": "$ROOT_QUERY.application",
"generated": true
}
}.
Here are my GQL query
import gql from 'graphql-tag';
const START_APP = gql`
mutation startApp($type: String!) {
application: startApp( input: { applicationType: $type})
#rest(type: "Application", path: "v1/member/application/create", method: "POST") {
uuid: applicationUuid
}
}
`;
const GET_APP = gql`
query getAppByUuid($uuid: String!) {
application: findApplicationByUuid(uuid: $uuid) {
uuid,
type,
version,
}
}
`;
export {
START_APP,
GET_APP,
};
Here is my resolver:
import { START_APP, GET_APP } from './application'
import client from '../apolloClient';
const startApp = async (_, { type }, { cache }) => {
client.mutate({
variables: { type },
mutation: START_APP,
}).then(({ data: { application } }) => {
const { uuid } = application;
const { data } = cache.readQuery({
query: GET_APP,
variables: { uuid },
});
cache.writeQuery({
query: GET_APP,
data,
});
});
};
const resolvers = {
Mutation: {
startApp,
},
};
Here are my links:
import { resolvers, defaults } from './resolvers';
const cache = new InMemoryCache();
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(`[GQL Error]: Msg: ${message}, Loc: ${locations}, Path: ${path}`));
if (networkError) console.log(`[Network error]: ${networkError}`);
});
const stateLink = withClientState({
cache,
defaults,
resolvers,
});
const restLink = new RestLink({
uri: 'http://localhost:7010/api/',
credentials: 'include',
});
const batchHttpLink = new BatchHttpLink({
uri: 'http://localhost:7010/api/graphql',
credentials: 'include',
});
const httpLink = new HttpLink({
uri: 'http://loaclhost:7010/api/graphql',
credentials: 'include',
});
const link = ApolloLink.from([
errorLink,
stateLink,
restLink,
httpLink,
]);
my client
const client = new ApolloClient({
link,
cache,
});
My react component:
// Remote Mutation
const START_APP = gql`
mutation startApp($type: String!) {
startApp(type: $type) #client {
uuid
}
}
`;
const StartApp = ({ match }) => {
const { type } = match.params;
return (
<Mutation mutation={START_APP} variables={{ type }}>
{startApp => (<button onClick={startApp}>click me</button>)}
</Mutation>
)
};
When I hit the button it calls create endpoint and creates the app and returns the uuid. But the following I want to happen is hit the graphql endpoint and query for the application using the uuid returned from the rest request, and write that data to the cache/state.