Shield seems to only work for the first request, but allows subsequent requests
michaelrevans opened this issue · comments
Question about GraphQL Shield
I haven't managed to set up graphql-shield
to correctly block requests to certain queries/mutations. I'm using express
and express-graphql
. Here are the relevant (I hope) parts of my setup:
import { GraphQLObjectType, GraphQLSchema } from "graphql";
import { graphqlHTTP } from "express-graphql";
import { applyMiddleware } from 'graphql-middleware';
const app = express();
const Query = new GraphQLObjectType({
name: "Query",
fields: {
institution: getInstitution(),
...
login: login(),
},
});
const Mutation = new GraphQLObjectType({
name: "Mutation",
fields: () => ({
...
}),
});
export default new GraphQLSchema({
query: Query,
mutation: Mutation,
});
const isAuthenticated = rule()(async (_parent, _args, _context) => {
return false;
});
export const permissions = shield(
{
Query: {
"*": isAuthenticated,
login: allow,
},
Mutation: {
"*": isAuthenticated,
}
}
);
app.use(
"/graphql",
graphqlHTTP((_, res) => ({
schema: applyMiddleware(schema, permissions),
}))
);
Unfortunately the wildcard doesn't seem to work for me in this case - whenever I make a query other than login
it gets a Not Authorized
the first time, but succeeds the following times. My only workaround now is to add every query and mutation to the permissions list, which I'd prefer not to do, because they'll always be the same.
I haven't found anything in the documentation to suggest a solution.
- I have checked other questions and found none that matches mine.
Hey @michaelrevans 👋,
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! 😄
@michaelrevans are you also allowing the types you are returning or only fields? This is a common misconception that it's enough to allow the field, but forget to allow the type.
I am also observing this. It appears to be related to wildcard rules.
Given a simple schema:
type Query {
ping: String!
}
The following will allow the first query { ping }
request and deny the second:
export const permissions = shield(
{
Query: {
"*": allow
}
},
{ fallbackRule: deny }
);
The following will deny the first query { ping }
request and allow the second:
export const permissions = shield(
{
Query: {
"*": deny
}
},
{ fallbackRule: allow }
);
The following will allow all query { ping }
requests:
export const permissions = shield(
{
Query: {
"*": deny,
ping: allow
}
},
{ fallbackRule: allow }
);
I narrowed my problem down to my Jest testing setup pattern. The problem appears to be surprising behavior with applyMiddleware
and shield
. Is there some kind of side effect that prevents applying the same permissions
object to multiple schemas?
import { loadSchema } from '@graphql-tools/load';
import { ApolloServer } from 'apollo-server';
import { GraphQLSchema } from 'graphql';
import { applyMiddleware } from 'graphql-middleware';
import { allow, deny, shield } from 'graphql-shield';
const permissions = shield({ Query: { '*': allow } }, { fallbackRule: deny });
let schema: GraphQLSchema;
// NOTE: `beforeAll` works
beforeEach(async () => {
schema = applyMiddleware(
await loadSchema(`type Query { ping: String! }`, {
loaders: [],
resolvers: {
Query: {
ping: () => 'pong',
},
},
}),
// NOTE: inlining (re-evaluating) works
permissions,
);
});
describe('bug', () => {
test('sequential queries work', async () => {
const server = new ApolloServer({ schema });
const result1 = await server.executeOperation({
query: `query { ping }`,
});
expect(result1.errors).toBe(undefined);
const result2 = await server.executeOperation({
query: `query { ping }`,
});
expect(result2.errors).toBe(undefined);
});
test('new servers do not work', async () => {
const server = new ApolloServer({ schema });
const result = await server.executeOperation({
query: `query { ping }`,
});
expect(result.errors).toBe(undefined);
});
});
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.