graphql-shield
graphql-shield copied to clipboard
GraphQL Shield to Work with Apollo Federation
Feature request
I want the Gateway layer of Apollo Federation to be able to run GraphQL shield. Currently that seems to be unspooled.
Is your feature request related to a problem? Please describe
I can't run GraphQL Shield at the Gateway layer. Our use case is to control Auth from the Gateway layer and control rules there.
We are a small team working with several sub services and find it better to have all auth related functionality in the Gateway layer.
Describe the solution you'd like
That I can use GraphQL Shield as normal in the Gateway Layer.
Hey @blazestudios23 :wave:,
Thank you for opening an issue. We will get back to you as soon as we can. Have you seen our Open Collective page? Please consider contributing financially to our project. This will help us involve more contributors and get to issues like yours faster.
https://opencollective.com/graphql-shield
We offer
priority
support for all financial contributors. Don't forget to addpriority
label once you become one! :smile:
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
@blazestudios23 Did you ever find an alternative to graphql-shield that works with Apollo Federation?
@blazestudios23 Did you ever find an alternative to graphql-shield that works with Apollo Federation?
Credit to vanbujm at this post Is it possible to apply graphql middleware when using managed federation , I managed to integrate apollo gateway with graphql shield. Here's a workable example for whoever concerns:
const { ApolloServer } = require('@apollo/server');
const express = require('express');
const {expressMiddleware} = require('@apollo/server/express4');
const bodyParser = require('body-parser');
const http = require('http');
const { execute } = require('graphql');
const { ApolloGateway, RemoteGraphQLDataSource } = require('@apollo/gateway');
const { shield, rule, allow, deny } = require('graphql-shield');
const { readFileSync } = require('fs');
const { applyMiddleware } = require('graphql-middleware');
const { addMocksToSchema } = require('@graphql-tools/mock');
const supergraphSdl = readFileSync('./supergraph.graphql').toString();
const hasScope = (key) => rule()(async (parent, args, ctx, info) => {
return ctx.scope.indexOf(key) > -1;
});
const permissions = shield({
Query: {
categories: hasScope('category:read'),
},
Category: {
createdBy: hasScope('category:admin'),
},
});
class AuthenticatedDataSource extends RemoteGraphQLDataSource {
willSendRequest({ request, context }) {
for (const [headerKey, headerValue] of Object.entries(context.headers)) {
request.http?.headers.set(headerKey, headerValue);
}
}
}
const gateway = new ApolloGateway({
supergraphSdl,
buildService({ name, url }) {
return new AuthenticatedDataSource({ name, url });
},
});
(async () => {
const app = express();
const httpServer = http.createServer(app);
let shieldedSchema;
const server = new ApolloServer({
gateway,
debug: true,
plugins: [
{
serverWillStart: async () => ({
schemaDidLoadOrUpdate: ({ apiSchema }) => {
// a fix on vanbujm's solution here, need to provide mocking result to shield nested fields
// Add your custom scalar mock here, e.g. Timestamp
shieldedSchema = applyMiddleware(addMocksToSchema({ schema: apiSchema, mocks: {
Timestamp: () => 0
} }), permissions);
},
}),
requestDidStart: async () => ({
responseForOperation: async ({ document, request, contextValue, operationName }) => {
contextValue.scope = (contextValue.headers['scope'] || '').split(' ');
const singleResult = await execute({ schema: shieldedSchema, document, contextValue, operationName });
const actualErrors = (singleResult?.errors || []).filter(
({ message }) => !message.includes('Cannot return null for non-nullable field')
);
if (actualErrors.length === 0) {
return undefined;
}
return {
body: { kind: 'single', singleResult },
http: {
status: undefined,
},
};
},
}),
},
],
});
await server.start();
app.use(
'/graphql',
bodyParser.json(),
expressMiddleware(server, {context: ({ req }) => {
return {
headers: req.headers,
};
}}),
);
await new Promise((resolve) => httpServer.listen(4000, '0.0.0.0', resolve));
const { address, port } = httpServer.address();
console.log(`🚀 Server ready at http://${address}:${port}/graphql`);
})();