-
Notifications
You must be signed in to change notification settings - Fork 365
GraphQL Endpoint Authentication and Authorization #96
Comments
Hi, Carlos |
I'm also interested in this and welcome any advice. Here's some relevant discussion. |
@dalerka @cdelgadob its a valid concern. Looks like AWS provides different ways to authenticate AWS API Gateway http://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-control-access-to-api.html We can design/prioritize this in coming days. |
Also some info here http://dev.apollodata.com/react/auth.html |
@nikgraf I have implemented Cognito User Pool Authentication on my local branch. Will create a PR on this soon. 2 Problems trying to solve now after authenticating my endpoints : 1) how to pass token to graphiQL endpoint 2) my resolver now has a rest wrapper and this rest endpoint is also secured, so my POST request to graphql endpoint must pass authorization header token value to resolver and another rest endpoint |
well 1) got resolved :) I added chrome plugin to add auth header (now client can generate token which is valid for 1 hour and then set this in the chrome header) and graphiQL works! Looking into 2) |
OK resolved 1) and 2) unfortunately there is no serverless plugin to mock cognito |
OK great! this works dherault/serverless-offline#118 |
I think it's import to consider authentication and authorization as two different problems. Authentication is how someone proves who they are to the GraphQL server. The most common approach here is to include an Authorization header with a token (for example a JWT). My personal feeling on using authorizers (either custom or Cognito) with the API GW is that they're great in a REST environment where I can tell from the URL who should be able to access it but completely inappropriate for GraphQL. For example: I can protect POST, PUT, DELETE to /pages and /page/:n but allow unauthenticated access to GET /pages and /pages/:n using authorizers With GraphQL everything sits behind a single API endpoint. If you put an authorizer on it then the entire GraphQL API ceases to be publicly available which is a problem if you want to allow self registration, login, password reset or other functionality for anonymous users while restricting other operations/information. Using the previous example I should be able to request pages anonymously with only the mutations to create, update and delete pages restricted. For authorization Facebook seems to recommend doing it in the business logic or the resolver. This approach allows partial failures where a user may be authorized to access some information but not all. The GraphQL spec itself talks about partial errors "A response may contain both a partial response as well as encountered errors in the case that an error occurred on a field which was replaced with null." See http://facebook.github.io/graphql/October2016/#sec-Response Typically I put authentication into the handler. It will validate the token from the Authorization header and lookup the relevant user. The user information is then put into the request context that is available to the resolvers. This allows the resolvers to restrict access to information or to pass user information on to the business logic. It also frees the resolvers from authentication as they are told who the user is. |
@buggy thanks for your valuable inputs. I completely agree that authentication and authorization as two different problems and must be handled differently (also explained here https://dev-blog.apollodata.com/a-guide-to-authentication-in-graphql-e002a4039d1 and https://dev-blog.apollodata.com/auth-in-graphql-part-2-c6441bcc4302). #130 deals with Authentication and NOT authorization. We certainly need additional work to implement Authorization. Also implementing Authentication in graphql is evaluated in the posts above where it can either be passed as JWT token or handled in graphql layer itself (with its own downsides). Another one is that for a given use case you might not want to expose your schema to the outside world using graphiqL (if by default users are authenticated to access endpoint but not authorized to access resources). Regarding your observations - "If you put an authorizer on it then the entire GraphQL API ceases to be publicly available which is a problem if you want to allow self registration, login, password reset or other functionality for anonymous users while restricting other operations/information." I would say yes but again it depends on the use cases we are dealing with. For instance, in internal use cases where authorization/signup is handled in a separate layer which makes API call to graphql there might be no signups etc. In other cases Cognito can sign up the user and add him/her to the user pool along with different use cases https://github.com/aws/amazon-cognito-identity-js but it could very well be handled at the resolver layer. We need to think more! but yeah I guess this is just a beginning of a good discussion. |
What I have done so far is: I used a JWT to authenticate users. The JWT is placed in the |
@nikgraf I love that approach because it doesn't tie you to an underlying service. Can you post an example? |
@danbruder in the mean time you might want to look at few examples as open PR's for the same. In addition Ryan gave a wonderful talk at GraphQL Conference this year https://github.com/chenkie/graphql-auth |
@sid88in thanks a lot! |
@danbruder, @sid88in, I have just shared my implementation for GraphQL authorization, which performs check for the operation being called (mutation or query) and for the data which is queried afterwards. Reading previous posts my approach can be in the same line as @nikgraf suggests |
Conversation is old, but sharing my two cents. Regarding this:
– @buggy In GraphQL, everything sits behind a single endpoint, right. But what if that same everything sat behind two different endpoints? For example: POST /graphql
POST /graphql/authed Then you can apply Lambda Authorizers (custom or Cognito) only to the Some things to consider if using this approach:
Using Apollo Server, that'd look something like this: const { ApolloServer } = require('apollo-server-lambda');
const User = require('../models/User');
const schema = require('../schema');
const getUser = async ({ requestContext: { authorizer } }) => {
if (authorizer) {
const {
claims: { sub: id },
} = authorizer;
const user = await User.getById(id);
if (user) {
return user;
}
}
return null;
};
const server = new ApolloServer({
schema,
context: async ({ event }) => ({
user: await getUser(event),
}),
});
exports.handler = server.createHandler();
I'm using a GraphQL middleware for this, with the excellent graphql-shield package. I defined a rule like this: const isAuthenticated = rule()(async (parent, args, { user }) => {
if (user !== null) {
return true;
}
return new AuthenticationError('You must be logged in');
}); And I apply it to those fields that require authentication.
I'm using the Apollo Client, which allows to implement this pretty easily with the apollo-link-context package. import { setContext } from 'apollo-link-context';
import Auth from '../services/Auth';
const authLink = setContext(async (request, { headers }) => {
const token = await Auth.getToken();
if (token) {
return {
headers: {
...headers,
Authorization: `Bearer ${token}`,
},
uri: 'https://your.api.com/graphql/authed',
};
}
});
export default authLink; I hope this can be useful for someone looking for a solution to this problem. And please let me know if you find any flaws in this approach! |
As @olistic said, I would really recommend as well checking GraphQL Shield for the authorization side of this topic, it's a very easy to use permissions layer based on middlewares and boolean logic. |
Thanks Dilip! Looks very interesting. How would this work with a multi tenant app?
… El 16 dic 2018, a las 6:26, Dilip ***@***.***> escribió:
I would recommend checking GraphQL Shield for the authorization side of this topic, it's a very easy to use permissions layer based on middlewares and boolean logic.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
|
Great question! My first thought is to package the auth rules in a separate dependency but that would cause some issues like duplication of types maybe. So instead of giving an uninformed opinion i'm digging into it now with separate functions. Will post something when I have the answer. |
@cdelgadob @dukuo If you use Apollo server in Node you could use the context function in constructor to pass user permission down to middleware (graphql-shield) or resolvers. |
I might have confused terms, I was thinking on keeping authorization + authentication across multiple serverless functions! Speaking of which, @MeixnerTobias solution on how to implement headers or baking client id in the token (the later sounding not so safe IMO) would apply as well. Has anyone played with schema stitching across serverless functions ? |
I am wondering how are you implementing authorization on a graphql endpoint in AWS.
My idea is to use Cognito Groups and match them with allowed queries/mutation per group in a custom authorizer lambda function.
Is this something reasonable?
It is important that the graphql lambda function is not hit by any unauthorized request.
The text was updated successfully, but these errors were encountered: