Skip to content
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

Separate endpoint for graphql auth mandatory #92

Closed
grdmnt opened this issue May 22, 2020 · 12 comments · Fixed by #96
Closed

Separate endpoint for graphql auth mandatory #92

grdmnt opened this issue May 22, 2020 · 12 comments · Fixed by #96
Labels
question Further information is requested

Comments

@grdmnt
Copy link

grdmnt commented May 22, 2020

Question

Is it possible to mount the new auth mutations/queries into my main /graphql route? Or is there a reason why there's a separate endpoint specifically for auth?

@00dav00 00dav00 added the question Further information is requested label May 22, 2020
@00dav00
Copy link
Contributor

00dav00 commented May 22, 2020

Hey @grdmnt, at the moment all queries and mutations are added to the gem gql schema, there is no option to specify which schema they will be added to.

@grdmnt
Copy link
Author

grdmnt commented May 22, 2020

Got it! If ever, would you guys accept some help to implement this? I haven’t done much contribution to open source, but I’m willing to give this a shot.

@mcelicalderon
Copy link
Member

Of course, @grdmnt! And this is something we have discussed in the past, but we haven't been able to think of a clean way to do it. So, before you actually give it a shot, let us know here how you might implement it. Main problem is, that some auth schema mutations require us to skip the authenticate_resource! call on the controller (login for example) that you have setup in your app's controller before even parsing the graphql query. This is how graphql is expected to work, we shouldn't actually authenticate inside the schema. So, it wouldn't be that hard to actually add this gem's mutations and queries to your main schema, but this is the main reason.

I'm thinking we could maybe let users opt-it to this behavior, and let them authenticate inside the schema if that's what they really want.

I couldn't find exactly what I was looking for, but graphql's specification is clear about how authentication and authorization should happen on a separate layer https://graphql.org/learn/authorization/

@aarona
Copy link
Contributor

aarona commented May 22, 2020

I'm learning more about GraphQL these days and I'm looking to do something like this. @mcelicalderon not sure if you knew this or not but I had ported DTA to support JWT tokens. My plan was to create something like this gem so that it would use a standard authentication (JWT tokens). A few more things I'd like to add to it but I think I might integrate a graphql interface into it as well. Just need to find the time to do it.

@mcelicalderon
Copy link
Member

Nice @aarona, we might look into having JWT as a configurable option at some point. This project might interest you too https://github.com/o2web/graphql-auth

And about the question in this issue, I will also look a bit closer to see if I might have misinterpreted what I read. And even if we are not supposed to do authentication inside the schema, I think the ability to opt-in might work for some people.

@grdmnt
Copy link
Author

grdmnt commented May 23, 2020

Oh. After using the gem for a bit, I now get it why there's a need to separate the auth to the main schema. Currently there's no way to auth a graphql schema partially right? So it's either I auth the entire thing or I make it public. With that said, I guess there's no real need to do this just yet unless more people wants to do it.

I think improving the docs to make this more obvious would be nice though

@aarona
Copy link
Contributor

aarona commented May 23, 2020

@grdmnt , no thats actually not true! If you forgo the authenticate_user! method in your graphql#execute method/controller but you ARE authenticated, you will have access to current_user in that method as non-nil (it would be nil if you were not authenticated) then you pass current_user into the query context like so:

context = {
  # Query context goes here, for example:
  current_user: current_user
}
result = ApptSchema.execute(query, variables: variables, context: context, operation_name: operation_name)

Now you have access to current_user inside your queries and mutations like so:

module Mutations
  class MyMutation < BaseMutation
    field :current_user, Types::UserType, null: true

    def resolve
      return nil if context[:current_user].nil?
      context[:current_user]
    end
  end
end

And if context[:current_user] is grating to you, you can create a current_user method in your BaseMutation and BaseObject classes like so:

module Types
  class BaseObject < GraphQL::Schema::Object
    field_class Types::BaseField

    def current_user
      context[:current_user]
    end
  end
end

GraphQL also has some other method overrides that you can do that will determine if parts of the schema should be hidden or if a certain resource should hidden to the end user. This way you can have some of your API opened up publicly (there are cases where you want this) and In production, I imagine you could hide your entire schema if you wanted to.

Check out the API for GraphQL ruby. You can implement CanCanCan as well: https://graphql-ruby.org/mutations/mutation_authorization.html

@grdmnt
Copy link
Author

grdmnt commented May 23, 2020

@aarona can you expound on this part If you forgo the authenticate_user! method in your graphql#execute method/controller but you ARE authenticated. How would be authenticated if you remove the authenticate_user! callback for your graphql controller actions? Are you suggesting that you can partially authenticate your graphql schema? If so, does that mean we can authenticate the user right after we get to parse the graphql query? I'm kinda lost on the part where you remove the authenticate_user! call but then you are still authenticated.

@aarona
Copy link
Contributor

aarona commented May 23, 2020

Sorry, I wasn't being very clear. If you use the authenticate_user! call back, you're forcing the end user to be a valid authenticated user BEFORE the #execute method is called. You can forgo this and rely DTA (or in this case GraphQL Devise) to authenticate as normal and then you can pass current_user into the GQL query context. You're not guaranteed that current_user will be a valid user object. If it's nil, that would be because DTA/GQLD couldn't authenticate the user. That means that you will have to do checks in your graphql code to ensure that the user is authenticated and authorized the view/access a resource. GQL Ruby has documentation for this.

Now in your applications GQL API, you can access the current user through the context. If current_user is nil then the end user was not authenticated so you can return an error or return a null result or handle this accordingly. Also, if you have a role system in place (see slow_your_roles, a gem I ported because the original developer no longer maintains it so I've added some features and updated it to support rails 5/6), you check if the user is an admin like so:

def resolve
  # current_user is not nil and current user is an admin
  if current_user&.is_admin?
    # user is an authenticated admin so proceed
  else
    # user is either not authenticated or user is auth'ed but not an admin
    # so proceed acordingly
  end
end

You can override ready? and authorized? (see the link from my previous comment) to do custom validation BEFORE your resolve method is called.

You could do something like:

class Mutations::AdminMutation < Mutations::BaseMutation
  def ready?(**args)
    # Called with mutation args.
    # is the current user an admin?
    if !context[:current_user].admin?
      raise GraphQL::ExecutionError, "Only admins can run this mutation"
    else
      # Return true to continue the mutation:
      true
    end
  end
end

Then if you want a particular mutation to be authenticated as an admin first you would do this:

class Mutations::MySecretMutation < Mutations::AdminMutation
  def resolve
    # guaranteed to be authenticated and an admin
  end
end

I haven't tried this yet on the project I'm working on but this is what the GQL Ruby documentation suggests is possible. Now that I'm actually writing the code out, I might as well test this to make sure it works haha!

@grdmnt
Copy link
Author

grdmnt commented May 24, 2020

@aarona Thanks for that thorough explanation on how this would work! Will try that on my project as well!

I think this issue is getting out of topic already, but I think the explanation given by @aarona would be useful when deciding on how to actually implement on combining the auth endpoints in other graphql schemas.

@aarona
Copy link
Contributor

aarona commented May 25, 2020

@grdmnt you're welcome!

@mcelicalderon
Copy link
Member

Might take a while, but we are implementing this on #96

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants