Reputation: 12422
I have a GraphQL gateway to federate a couple of subgraphs. The gateway executes incoming operations across those subgraphs.
OPTIONS method is triggered (preflight), nothing in the backend logs. The error observed in the browser below
Access to fetch at 'http://dev.gateway.mydomain.net:5000/graphql'
from origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
If an opaque response serves your needs, set the request's mode to 'no-cors'
to fetch the resource with CORS disabled.
Access-Control-Allow-Origin: *
Below code for my Apollo gateway
I have marked as comments everything I have tried (TRY 1..7) to resolve my CORS issue
const { ApolloServer } = require('apollo-server-express');
const { ApolloGateway, RemoteGraphQLDataSource } = require('@apollo/gateway');
const express = require('express');
const path = require('path');
const expressJwt = require('express-jwt');
const jwksRsa = require('jwks-rsa');
const expressSession = require('express-session');
const passport = require('passport');
const Auth0Strategy = require('passport-auth0');
const jsonwebtoken = require('jsonwebtoken');
const dotenv = require('dotenv');
const cors = require('cors');
const userInViews = require('./lib/middleware/userInViews');
const authRouter = require('./routes/auth');
const indexRouter = require('./routes/index');
const usersRouter = require('./routes/users');
const graphqlRouter = require('./routes/graphql');
const headersRouter = require('./routes/headers');
dotenv.config({ path: `./gateway.${process.env.NODE_ENV}.env` });
const port = 5000;
const strategy = new Auth0Strategy(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: process.env.AUTH0_CALLBACK_URL || `http://localhost:${port}/callback`,
},
(accessToken, refreshToken, extraParams, profile, done) => {
console.log('ACCESS TOKEN', accessToken);
const decoded = jsonwebtoken.decode(accessToken);
const customProfile = profile;
customProfile.permissions = decoded.permissions;
customProfile.authorization = `Bearer ${accessToken}`;
return done(null, customProfile);
},
);
passport.use(strategy);
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
const session = {
secret: 'FILTERED',
cookie: {},
resave: false,
saveUninitialized: true,
};
const app = express();
app.use(express.static('assets'));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
if (app.get('env') === 'production') {
session.cookie.secure = true;
app.set('trust proxy', 1);
}
app.use(expressSession(session));
app.use(passport.initialize());
app.use(passport.session());
app.use(userInViews()); // Application level middleware: Add user to view
app.use('/', authRouter);
app.use('/', indexRouter);
app.use('/', usersRouter);
app.use('/', graphqlRouter);
app.use('/', headersRouter);
app.set('json spaces', 2);
const localUserLoginMutationCheck = expressJwt({
secret: 'f.....Y',
algorithms: ['HS256'],
credentialsRequired: false,
});
const auth0AuthenticationCheck = expressJwt({
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
}),
audience: 'http://dev.gateway.mydomain.net:5000',
issuer: [`https://${process.env.AUTH0_DOMAIN}/`],
algorithms: ['RS256'],
credentialsRequired: false,
});
app.use(
auth0AuthenticationCheck,
);
/**
* TRY 1
*/
const corsOptions = {
origin: '*',
credentials: true, // access-control-allow-credentials:true
optionSuccessStatus: 200,
};
app.use(cors(corsOptions));
/**
* TRY 2
*/
app.use((req, res, next) => {
console.log('INCOMING REQUEST HEADERS', req.headers);
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
console.log('INCOMING REQUEST HEADERS AFTER', req.headers);
next();
});
const gateway = new ApolloGateway({
serviceList: [
{ name: 'siebel', url: 'http://localhost:4000/graphql' },
{ name: 'sciencelogic', url: 'http://localhost:4001/graphql' },
],
buildService({ name, url }) {
return new RemoteGraphQLDataSource({
url,
willSendRequest({ request, context }) {
console.log('CONTEXT', context);
request.http.headers.set(
'user',
context.user
? JSON.stringify(context.user)
: null,
);
},
});
},
});
const gqlServer = new ApolloServer({
gateway,
/**
* TRY 3
*/
// cors: true,
/**
* TRY 4
*/
// cors: {
// origin: '*',
// methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
// preflightContinue: false,
// optionsSuccessStatus: 204,
// credentials: true,
// },
/**
* TRY 5
*/
// cors: {
// credentials: true,
// origin: (origin, callback) => {
// const whitelist = [
// 'http://localhost:3000',
// 'https://stage-tool.mydomain.net',
// 'https://stage.tool.mydomain.net',
// 'https://dsr.mydomain.net',
// ];
// if (whitelist.indexOf(origin) !== -1) {
// callback(null, true);
// } else {
// callback(new Error('Not allowed by CORS'));
// }
// },
// },
subscriptions: false,
context: ({ req }) => {
const user = req.user || null;
return { user };
},
introspection: true,
});
// const corsOptions = {
// origin: 'http://localhost:3000',
// credentials: true,
// };
gqlServer.applyMiddleware({
app,
/**
* TRY 6
*/
cors: false,
/**
* TRY 7
*/
// cors: corsOptions,
});
app.listen({ port }, () => {
console.log(`Gateway ready at http://localhost:${port}${gqlServer.graphqlPath}`);
});
const stackTrace = require('stack-trace');
const { ApolloServer } = require('apollo-server');
const { applyMiddleware } = require('graphql-middleware');
const { buildFederatedSchema } = require('@apollo/federation');
const jwt = require('jsonwebtoken');
const nodemailer = require('nodemailer');
const GqlHelpers = require('../gql-helpers');
const { siebelPermissions } = require('../../permissions');
const SiebelContactIntegration = require('./lib/ads-contact');
console.debug(`env ${JSON.stringify(process.env, null, 2)}`);
const resolvers = require(`./lib/gql/siebel.resolvers.${process.env.NODE_ENV}`);
const siebelSchema = GqlHelpers.gqlImport(__dirname, `./lib/gql/siebel.${process.env.NODE_ENV}.graphql`);
const typeDefs = GqlHelpers.gqlWrapper([siebelSchema]);
const federatedSchema = buildFederatedSchema([{ typeDefs, resolvers }]);
const server = new ApolloServer(
{
cors: true,
schema: applyMiddleware(
federatedSchema,
siebelPermissions,
),
context: ({ req }) => {
const jwtToken = req.headers.authorization ? req.headers.authorization : null;
console.log('HEADERS (SIEBEL)', req.headers);
const jwtTokenArray = jwtToken ? jwtToken.split(' ') : [];
const decodedJwt = jwtTokenArray.length > 0 ? jwt.decode(jwtTokenArray[1]) : null;
const jwtIssuer = decodedJwt && decodedJwt.iss ? decodedJwt.iss : null;
const jwtSubject = decodedJwt && decodedJwt.sub ? decodedJwt.sub : null;
const user = req.headers.user ? JSON.parse(req.headers.user) : null;
return { user, jwtIssuer, jwtSubject };
},
dataSources: () => ({
siebelContactIntegration: new SiebelContactIntegration(),
}),
cacheControl: {
defaultMaxAge: 3600,
},
introspection: true,
},
);
server.listen(
{
port: 4000,
},
() => {
console.log(`🚀 Siebel GraphQL Server ready at ${process.env.SAPI_URL}${server.graphqlPath}`);
},
);
apollo-client
libraryNote the fetch options set to no-cors
import React, { useEffect, useState } from "react";
import {
ApolloClient,
InMemoryCache,
HttpLink,
ApolloProvider,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { useAuth0 } from "@auth0/auth0-react";
function ApolloWrapper({ children }) {
const { isAuthenticated, getAccessTokenSilently } = useAuth0();
const [bearerToken, setBearerToken] = useState("");
const httpLink = new HttpLink({
uri: "http://dev.gateway.mydomain.net:5000/graphql",
});
useEffect(() => {
const getToken = async () => {
const token = isAuthenticated ? await getAccessTokenSilently() : "";
setBearerToken(token);
console.log(token);
};
getToken();
}, [getAccessTokenSilently, isAuthenticated]);
const authLink = setContext((_, { headers, ...rest }) => {
if (!bearerToken) return { headers, ...rest };
return {
...rest,
headers: {
...headers,
authorization: `Bearer ${bearerToken}` || null,
},
};
});
const client = new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
fetchOptions: {
mode: 'no-cors', // <== Note the fetch options
},
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
export default ApolloWrapper;
using Apollo client useLazyQuery
to execute the GraphQL query.
import { useLazyQuery } from "@apollo/client";
const [getEmail, { loading, error, data }] = useLazyQuery(
gqlSiebelFindContact,
{
errorPolicy: "all",
}
);
I am trying to figure out why I am getting the preflight request doesn't pass access control check
and whether the issue is to be fixed on the frontend or backend code.
I am able to workaround the problem by configuring my Chrome browser for no CORS
.
Any further pointers to troubleshoot and find the ground issue are most welcome
Upvotes: 4
Views: 4500
Reputation: 17487
The error message points to a failed preflight request, which is an OPTIONS request without credentials. Such preflight requests should be handled by the app.use(cors(corsOptions))
middleware. But this middleware comes rather late, so this will not work if any of the earlier middlewares (e.g., app.use(passport.session())
) already responds to this OPTIONS request.
The screenshot of the OPTIONS request with Content-Length: 8
in the response seems to imply that this happens here.
Upvotes: 1