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

Authenticate non-root fields #243

Open
janosrusiczki opened this issue Sep 26, 2022 · 6 comments
Open

Authenticate non-root fields #243

janosrusiczki opened this issue Sep 26, 2022 · 6 comments

Comments

@janosrusiczki
Copy link

janosrusiczki commented Sep 26, 2022

Is it possible to authenticate non-root fields?

After some hurdles I managed to set this gem up with graphql 2.0.14. I got to the point where I set authenticate_default: false in my main schema and if I set authenticate: true on one of the root fields in QueryType I'm getting an error if I want to access it while not being authenticated. However if I add authenticate: true to one of the non-root fields it doesn't seem to have any effect.

In my case I have:

# ./app/graphql/types/photo_type.rb
module Types
  class PhotoType < GraphQL::Schema::Object
    field_class GraphqlDevise::Types::BaseField

    description 'A Photo'

    field :id, String, null: false, authenticate: true
    [ snip ]
  end
end

I can access id without being authenticated.

Is it possible to have this type of granular authentication? If yes, what am I doing wrong?

@mcelicalderon
Copy link
Member

Hey @janosrusiczki! You are not doing something wrong, this is the correct behavior of the gem as commented in

# Authenticate only root level queries

This was done for performance reasons at first as not doing this would mean we have to run the authentication code for every field and that might happen a lot of times in a single query. I'll try to run some benchmarks and see what's the actual effect of enabling the feature for nested fields

@janosrusiczki
Copy link
Author

I agree, it's probably a big overhead to check all the fields.

Maybe a global flag in an initializer: granular authentication on / off?

Or some way to enable it on each type? Default being disabled.

@mcelicalderon
Copy link
Member

We need to think about this a bit more, but I kind of feel we might not want to allow this unless it has a very low impact to performance. For a more granular access control I think it would be better to use an authorization gem. This one was kind of an inspiration for this project, but I don't see much activity on the project.

Anyway, there might be a fast way to handle the scenario where nested fields don't set the authenticate value at least. Or as you said, make it optional so the project using the gem can decide. I might also be overthinking this since the code is relatively fast and checking in every field might have a very low impact. I'll look into it

@tatosjb
Copy link

tatosjb commented Nov 26, 2022

I give a look at graphql-guard gem. It does not work on the newer version of ruby-graphql, and since all the interface it was written on top of was removed, I think it'll not be updated to work with. It was written on top of accepts_definitions, to_graphql, and graphql_definition. All of them were removed on version 2.0 and I think this is the reason behind the inactivity of that repository.

Maybe instead of adding this on top of this gem, what about building one, not-opinative gem, giving a way for the developer to implement his own "guard" method, other than the default one, that I think should be as simple as "authenticated", but on the field, something like authorized: ->(user, context) { sample_code }

@mcelicalderon
Copy link
Member

That's a good idea. That might be a good alternative. graphql-guard definitively looks abandoned. But I still have to look into supporting this in this gem. As I said before, maybe performance won't be too affected if we check every level of the query (at least for presence of the authenticate key)

@tatosjb
Copy link

tatosjb commented Nov 29, 2022

I think that I have a hint about why it's abandoned looking into this doc
image

Ps: no end code down there, just some ideas about it for anyone that comes here

Doing something like this should work.

Configure authenticate_default to true

module Types
  class BaseField < GraphQL::Schema::Field
    include ::GraphqlDevise::FieldAuthentication

    attr_accessor :authorize

    def initialize(*args, authorize: true, **kwargs, &block)
      super(*args, **kwargs, &block)

      @authorize = authorize
    end

    def authorized?(obj, args, ctx)
      authorize.instance_of?(Proc) ? authorize.call(obj, args, ctx) : authorize
    end

    argument_class Types::BaseArgument
  end
end

And it gives you some power to use it like:

class Authorize < ApplicationService
  def initialize(action, restrict: false)
    @action = action
    @restrict = restrict
    @authorizer = YourAuthorizerClass.new
  end

  def call
    proc do |obj, _args, ctx|
      user_allowed?(ctx[:current_resource], obj)
    end
  end

  private

  attr_accessor :action, :restrict, :authorizer

  def user_allowed?(user, record)
    authorizer.authorize?(user, record, action, restrict:)
  end
end
module AuthTypes
  ADMIN = ::Authorize.call(:any, restrict: true)

  READ = ::Authorize.call(:show, restrict: false)
  CREATE = ::Authorize.call(:create, restrict: false)
  UPDATE = ::Authorize.call(:update, restrict: false)
  LIST = ::Authorize.call(:index, restrict: false)
  DESTROY = ::Authorize.call(:destroy, restrict: false)
end
module Types
  class UserType < Types::BaseObject
    field :id, ID, null: false, authorize: AuthTypes::READ
    field :name, String, null: true, authorize: AuthTypes::READ
    field :title, String, null: true, authorize: AuthTypes::READ
    field :email, String, null: false, authorize: AuthTypes::READ
    field :encrypted_password, String, null: false, authorize: AuthTypes::ADMIN
    field :reset_password_token, String, authorize: AuthTypes::ADMIN
    field :reset_password_sent_at, GraphQL::Types::ISO8601DateTime, authorize: AuthTypes::ADMIN
    field :remember_created_at, GraphQL::Types::ISO8601DateTime, authorize: AuthTypes::ADMIN
    field :created_at, GraphQL::Types::ISO8601DateTime, null: false, authorize: AuthTypes::ADMIN
    field :updated_at, GraphQL::Types::ISO8601DateTime, null: false, authorize: AuthTypes::ADMIN
  end
end

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

No branches or pull requests

3 participants