-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic schema selection based on the role of the current user #2010
Comments
@gsamartian have you found a solution to handle hot schema updates ? |
This would also benefits my business use case. |
There has been many a discussion about adding data viewing limitations via permissions/ roles in some fashion to GraphQL (the spec) and the reply was always (and I'm paraphrasing), permission/ roles, being a part of business logic, must be found within that logic and not in the schema. I see it the same way. Permissions or Roles can be done in many ways and at different levels of fidelity. As long as Apollo Server doesn't go past the bounds of resolvers, then it shouldn't be opinionated as to how anything gets resolved. A GraphQL server should stay a "thin" gateway to your business logic, where either your business logic returns something to the resolver, or nothing, and all dependent on the user context. You can find an example of what I mean here: https://www.prisma.io/tutorials/best-practices-for-permissions-in-graphql-servers-ct07/ This is built on top of GraphQL-Yoga, which uses Apollo Server. Most notably:
😁 Scott |
I believe that for security, more layers are better (if they don't complicate matters). Having course-grained access control that even limits schema visibility based on user level is an easy extra security layer on top of what should have been implemented already. |
That's exactly the issue. Putting data viewing permissions inside the schema means a lot more work in terms of how the endpoint should traverse the schema (or not). That's assuming the Apollo team would even break from the spec too, which I highly doubt they will. It's been said a few times to other suggestions about authorization I've read that Authorization is outside the scope of the spec. I've gathered some links to hopefully change your minds, where the founders of GraphQL chime in and/ or give some insight as to why it's better to have access permissions in the business logic level of your app's stack. And lastly the tutorial for GraphQL on Authorization. graphql/graphql-js#113 (comment) In big bold letters:
I hate to say it, but you are barking up the wrong tree here, unfortunately. 😊 Scott |
I'm not proposing to extend the schema, I'm asking to select the schema based on the context of the incoming request. In the first link you gave Lee actually says the exact same thing:
This is exactly the same reasoning, and facebook does it too. |
Um, you are assuming they use some unknown extra parsing in their GraphQL server to split the users between the two schemas. Whereas, I'd assume they have a staging or quality server (or servers) that form a totally different end point, which uses/ serves that "internal" schema. Authorization can then happen at the point of authentication and it would be a very simple solution with "standard spec" GraphQL servers. Scott |
I suppose, but it would still be handy to do it in-server. (picking nits: authorization happens in the resolvers ;-) ) |
For field level authorization yes, the authorization should be at or after the resolvers. For allowing only devs and/or admins into a test server, authorization can happen during authentication (or rather authentication is authorization for access to that schema). Scott |
One thing that might offer a means to a "selective schema", and is in fact exactly that, is the new federation feature. If the Apollo team would offer a side door to allowing devs a way to add their own logic to the current federation logic, it wouldn't break the GraphQL spec, but will offer a way to set rules like permissions on who can see what "chuck" of business logic a user can see/ use. Scott |
I suppose that is acceptable, albeit that the stitching for local graphql services will mean parsing + stringifying + parsing the incoming queries and possibly forcing the queries to happen over http instead of running in-process. Not terrible but of course it could be more efficient |
Um, federation isn't schema stitching. In fact, from my understanding, it's the opposite. Scott |
I was looking at the example given const gateway = new ApolloGateway({
serviceList: [
{ name: 'accounts', url: 'http://localhost:4001' },
{ name: 'products', url: 'http://localhost:4002' },
{ name: 'reviews', url: 'http://localhost:4003' }
]
});
const server = new ApolloServer({ gateway });
server.listen(); the gateway proxies the incoming request to the given services, right? So it needs to parse and recreate the proper query? |
Ok. Reading further, the Gateway "pulls in" the schema from the different services and composes them together. I don't think that would be bad in terms of performance, if some sort of access rules were given on top of the federation system. Off topic, but I wonder how changes get propagated, for instance when one service gets a new schema, the Gateway needs to know about that change to update its own composed schema. So, how would that work? Nevermind. It's a rhetorical question. Scott |
Right, it seems like federation is just a declarative version of stitching, and the schema is still only prepared the one time… |
I still think it would be great to be able to say
|
This can be done via plugins now: (I'm using Koa in this example) import {execute, parse} from "graphql";
import {ApolloServer} from "apollo-server-koa";
import getSchemaForRole from "./my-schema-defs";
import authenticateUser from "./my-auth-backend";
import app from "./my-koa-app";
const defaultRole = "user";
const apolloServer = new ApolloServer({
schema: await getSchemaForRole(defaultRole),
context: ({ ctx }) => {
return {
user: await authenticateUser(ctx) // authenticate the user however you see fit
};
},
plugins: [{
requestDidStart: () => ({
responseForOperation: async operation => {
const {context, request} = operation;
const {query, variables, operationName} = request;
const {role} = context.user;
// dynamically select the schema based on the current user's role
const schema = await getSchemaForRole(role || defaultRole);
return execute(schema, parse(query), null, context, variables, operationName);
}
})
}]
});
apolloServer.applyMiddleware({ app }); Note that this will seamlessly work for introspection queries. (if you refresh the playground after logging-in you'll get the schema for that role, etc) Edit: The solution above breaks instrumentation and tracing in addition to blocking some server plugins from running. (any plugins that hook into So here's another method that doesn't have this problem. It could be considered a bit hacky, but I'm leaving it here for those that really need these features during development. It works by calling class ExtendedApolloServer extends ApolloServer {
private readonly _schemaCb?: (
...args: Parameters<ApolloServer["createGraphQLServerOptions"]>
) => Promise<GraphQLSchema> | GraphQLSchema;
private readonly _derivedData: WeakMap<GraphQLSchema, any> = new WeakMap();
constructor({schemaCallback, ...rest}: Config & {
schemaCallback?: ExtendedApolloServer["_schemaCb"];
}) {
super(rest);
this._schemaCb = schemaCallback;
}
public async createGraphQLServerOptions(
...args: Parameters<ApolloServer["createGraphQLServerOptions"]>
): Promise<GraphQLOptions> {
const options = await super.createGraphQLServerOptions.apply(this, args);
if (this._schemaCb) {
const schema = await this._schemaCb.apply(null, args);
if (!this._derivedData.has(schema))
this._derivedData.set(schema,
this.constructor.prototype.generateSchemaDerivedData.call(this, schema));
Object.assign(options, await this._derivedData.get(schema));
}
return options;
}
}
// Example usage:
const apolloServer = new ExtendedApolloServer({
...,
schemaCallback: ctx => getSchemaFromConext(ctx)
}); |
Duplicate of #5786. |
hi,
We are using apollo-server as the graphql server.
We have multiple graphql endpoints ( each in its own apollo graphql server ) created each for a specific business capability/domain/bounded content.
We now want to expose these to the clients. we have clients who may or may not have access to all the business capabilities.
For example, Client A may have access to business capability 1 and Client B may have access to business capability 1 as well as business capability 2, while another client may have access to all other business capabilities.
i want to know if i can create a graphql gateway or something similar with an api endpoint which when exposed provides the access to the right business capabilities based on the access permissions of the current client.
Also, i want to only expose the only business capabilities/fields in the graphql explorer/playground in the docs/schema section on the right pane based on the permission/role of the current user who is logged-in to view and explore the features exposed to him/her.
I see there is a feature of schema stitching where-in we can have a schema composed of multiple remote schemas each from a separate graphql endpoint. But, i do not see an option of fine grained selection of schemas which can be passed to apollo server based on the profile/role/permissions of the current user. basically, i want to refresh the schema based on the current user and not have a a single static schema as constructed in the startup.
Is there a way to achieve that with apollo graphql or is there another tools in the graphql ecosystem which i can leverage for my requirement.
The text was updated successfully, but these errors were encountered: