maticzav / graphql-shield

🛡 A GraphQL tool to ease the creation of permission layer.
https://graphql-shield.com
MIT License
3.54k stars 171 forks source link

GraphQL Shield to Work with Apollo Federation #1218

Open fullStackDataSolutions opened 3 years ago

fullStackDataSolutions commented 3 years ago

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.

open-collective-bot[bot] commented 3 years ago

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 add priority label once you become one! :smile:

stale[bot] commented 2 years ago

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.

AlfieGoat commented 1 year ago

@blazestudios23 Did you ever find an alternative to graphql-shield that works with Apollo Federation?

ayame30 commented 1 year ago

@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`);

})();