{"id":23052111,"url":"https://github.com/exaspark/graphql-guard","last_synced_at":"2025-05-15T18:08:30.284Z","repository":{"id":45752794,"uuid":"97640797","full_name":"exAspArk/graphql-guard","owner":"exAspArk","description":"Simple authorization gem for GraphQL :lock:","archived":false,"fork":false,"pushed_at":"2022-09-12T16:36:46.000Z","size":119,"stargazers_count":471,"open_issues_count":10,"forks_count":35,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-05-14T09:53:41.860Z","etag":null,"topics":["authorization","cancancan","gem","graphql","graphql-ruby","guard","pundit","ruby","schema-masking"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/exAspArk.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-18T20:26:01.000Z","updated_at":"2025-04-08T07:12:24.000Z","dependencies_parsed_at":"2022-07-30T12:48:13.118Z","dependency_job_id":null,"html_url":"https://github.com/exAspArk/graphql-guard","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exAspArk%2Fgraphql-guard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exAspArk%2Fgraphql-guard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exAspArk%2Fgraphql-guard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/exAspArk%2Fgraphql-guard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/exAspArk","download_url":"https://codeload.github.com/exAspArk/graphql-guard/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254394722,"owners_count":22063984,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","cancancan","gem","graphql","graphql-ruby","guard","pundit","ruby","schema-masking"],"created_at":"2024-12-16T00:05:18.900Z","updated_at":"2025-05-15T18:08:30.217Z","avatar_url":"https://github.com/exAspArk.png","language":"Ruby","readme":"# graphql-guard\n\n[![Build Status](https://travis-ci.org/exAspArk/graphql-guard.svg?branch=master)](https://travis-ci.org/exAspArk/graphql-guard)\n[![Coverage Status](https://coveralls.io/repos/github/exAspArk/graphql-guard/badge.svg)](https://coveralls.io/github/exAspArk/graphql-guard)\n[![Code Climate](https://img.shields.io/codeclimate/maintainability/exAspArk/graphql-guard.svg)](https://codeclimate.com/github/exAspArk/graphql-guard/maintainability)\n[![Downloads](https://img.shields.io/gem/dt/graphql-guard.svg)](https://rubygems.org/gems/graphql-guard)\n[![Latest Version](https://img.shields.io/gem/v/graphql-guard.svg)](https://rubygems.org/gems/graphql-guard)\n\nThis gem provides a field-level authorization for [graphql-ruby](https://github.com/rmosolgo/graphql-ruby).\n\n## Contents\n\n* [Usage](#usage)\n  * [Inline policies](#inline-policies)\n  * [Policy object](#policy-object)\n* [Priority order](#priority-order)\n* [Integration](#integration)\n  * [CanCanCan](#cancancan)\n  * [Pundit](#pundit)\n* [Error handling](#error-handling)\n* [Schema masking](#schema-masking)\n* [Installation](#installation)\n* [Testing](#testing)\n* [Development](#development)\n* [Contributing](#contributing)\n* [License](#license)\n* [Code of Conduct](#code-of-conduct)\n\n## Usage\n\nDefine a GraphQL schema:\n\n```ruby\n# Define a type\nclass PostType \u003c GraphQL::Schema::Object\n  field :id, ID, null: false\n  field :title, String, null: true\nend\n\n# Define a query\nclass QueryType \u003c GraphQL::Schema::Object\n  field :posts, [PostType], null: false do\n    argument :user_id, ID, required: true\n  end\n\n  def posts(user_id:)\n    Post.where(user_id: user_id)\n  end\nend\n\n# Define a schema\nclass Schema \u003c GraphQL::Schema\n  use GraphQL::Execution::Interpreter\n  use GraphQL::Analysis::AST\n  query QueryType\nend\n\n# Execute query\nSchema.execute(query, variables: { userId: 1 }, context: { current_user: current_user })\n```\n\n### Inline policies\n\nAdd `GraphQL::Guard` to your schema:\n\n\u003cpre\u003e\nclass Schema \u003c GraphQL::Schema\n  use GraphQL::Execution::Interpreter\n  use GraphQL::Analysis::AST\n  query QueryType\n  \u003cb\u003euse GraphQL::Guard.new\u003c/b\u003e\nend\n\u003c/pre\u003e\n\nNow you can define `guard` for a field, which will check permissions before resolving the field:\n\n\u003cpre\u003e\nclass QueryType \u003c GraphQL::Schema::Object\n  \u003cb\u003efield :posts\u003c/b\u003e, [PostType], null: false do\n    argument :user_id, ID, required: true\n    \u003cb\u003eguard -\u003e(obj, args, ctx) {\u003c/b\u003e args[:user_id] == ctx[:current_user].id \u003cb\u003e}\u003c/b\u003e\n  end\n  ...\nend\n\u003c/pre\u003e\n\nYou can also define `guard`, which will be executed for every `*` field in the type:\n\n\u003cpre\u003e\nclass PostType \u003c GraphQL::Schema::Object\n  \u003cb\u003eguard -\u003e(obj, args, ctx) {\u003c/b\u003e ctx[:current_user].admin? \u003cb\u003e}\u003c/b\u003e\n  ...\nend\n\u003c/pre\u003e\n\nIf `guard` block returns `nil` or `false`, then it'll raise a `GraphQL::Guard::NotAuthorizedError` error.\n\n### Policy object\n\nAlternatively, it's possible to extract and describe all policies by using PORO (Plain Old Ruby Object), which should implement a `guard` method. For example:\n\n\u003cpre\u003e\nclass \u003cb\u003eGraphqlPolicy\u003c/b\u003e\n  RULES = {\n    QueryType =\u003e {\n      \u003cb\u003eposts: -\u003e(obj, args, ctx) {\u003c/b\u003e args[:user_id] == ctx[:current_user].id \u003cb\u003e}\u003c/b\u003e\n    },\n    PostType =\u003e {\n      \u003cb\u003e'*': -\u003e(obj, args, ctx) {\u003c/b\u003e ctx[:current_user].admin? \u003cb\u003e}\u003c/b\u003e\n    }\n  }\n\n  def self.\u003cb\u003eguard(type, field)\u003c/b\u003e\n    RULES.dig(type, field)\n  end\nend\n\u003c/pre\u003e\n\nPass this object to `GraphQL::Guard`:\n\n\u003cpre\u003e\nclass Schema \u003c GraphQL::Schema\n  use GraphQL::Execution::Interpreter\n  use GraphQL::Analysis::AST\n  query QueryType\n  use GraphQL::Guard.new(\u003cb\u003epolicy_object: GraphqlPolicy\u003c/b\u003e)\nend\n\u003c/pre\u003e\n\nWhen using a policy object, you may want to allow [introspection queries](http://graphql.org/learn/introspection/) to skip authorization. A simple way to avoid having to whitelist every introspection type in the `RULES` hash of your policy object is to check the `type` parameter in the `guard` method:\n\n\u003cpre\u003e\ndef self.guard(type, field)\n  \u003cb\u003etype.introspection? ? -\u003e(_obj, _args, _ctx) { true } :\u003c/b\u003e RULES.dig(type, field) # or \"false\" to restrict an access\nend\n\u003c/pre\u003e\n\n## Priority order\n\n`GraphQL::Guard` will use the policy in the following order of priority:\n\n1. Inline policy on the field.\n2. Policy from the policy object on the field.\n3. Inline policy on the type.\n2. Policy from the policy object on the type.\n\n\u003cpre\u003e\nclass \u003cb\u003eGraphqlPolicy\u003c/b\u003e\n  RULES = {\n    PostType =\u003e {\n      \u003cb\u003e'*': -\u003e(obj, args, ctx) {\u003c/b\u003e ctx[:current_user].admin? \u003cb\u003e}\u003c/b\u003e,                                # \u003c=== \u003cb\u003e4\u003c/b\u003e\n      \u003cb\u003etitle: -\u003e(obj, args, ctx) {\u003c/b\u003e ctx[:current_user].admin? \u003cb\u003e}\u003c/b\u003e                               # \u003c=== \u003cb\u003e2\u003c/b\u003e\n    }\n  }\n\n  def self.guard(type, field)\n    RULES.dig(type, field)\n  end\nend\n\nclass PostType \u003c GraphQL::Schema::Object\n  \u003cb\u003eguard -\u003e(obj, args, ctx) {\u003c/b\u003e ctx[:current_user].admin? \u003cb\u003e}\u003c/b\u003e                                    # \u003c=== \u003cb\u003e3\u003c/b\u003e\n  field :title, String, null: true, \u003cb\u003eguard: -\u003e(obj, args, ctx) {\u003c/b\u003e ctx[:current_user].admin? \u003cb\u003e}\u003c/b\u003e # \u003c=== \u003cb\u003e1\u003c/b\u003e\nend\n\nclass Schema \u003c GraphQL::Schema\n  use GraphQL::Execution::Interpreter\n  use GraphQL::Analysis::AST\n  query QueryType\n  use GraphQL::Guard.new(\u003cb\u003epolicy_object: GraphqlPolicy\u003c/b\u003e)\nend\n\u003c/pre\u003e\n\n## Integration\n\nYou can simply reuse your existing policies if you really want. You don't need any monkey patches or magic for it ;)\n\n### CanCanCan\n\n\u003cpre\u003e\n# Define an ability\nclass \u003cb\u003eAbility\u003c/b\u003e\n  include CanCan::Ability\n\n  def initialize(user)\n    user ||= User.new\n    if user.admin?\n      can :manage, :all\n    else\n      can :read, Post, author_id: user.id\n    end\n  end\nend\n\n# Use the ability in your guard\nclass PostType \u003c GraphQL::Schema::Object\n  guard -\u003e(post, args, ctx) { \u003cb\u003ectx[:current_ability].can?(:read, post)\u003c/b\u003e }\n  ...\nend\n\n# Pass the ability\nSchema.execute(query, context: { \u003cb\u003ecurrent_ability: Ability.new(current_user)\u003c/b\u003e })\n\u003c/pre\u003e\n\n### Pundit\n\n\u003cpre\u003e\n# Define a policy\nclass \u003cb\u003ePostPolicy\u003c/b\u003e \u003c ApplicationPolicy\n  def show?\n    user.admin? || record.author_id == user.id\n  end\nend\n\n# Use the ability in your guard\nclass PostType \u003c GraphQL::Schema::Object\n  guard -\u003e(post, args, ctx) { \u003cb\u003ePostPolicy.new(ctx[:current_user], post).show?\u003c/b\u003e }\n  ...\nend\n\n# Pass current_user\nSchema.execute(query, context: { \u003cb\u003ecurrent_user: current_user\u003c/b\u003e })\n\u003c/pre\u003e\n\n## Error handling\n\nBy default `GraphQL::Guard` raises a `GraphQL::Guard::NotAuthorizedError` exception if access to the field is not authorized.\nYou can change this behavior, by passing custom `not_authorized` lambda. For example:\n\n\u003cpre\u003e\nclass SchemaWithErrors \u003c GraphQL::Schema\n  use GraphQL::Execution::Interpreter\n  use GraphQL::Analysis::AST\n  query QueryType\n  use GraphQL::Guard.new(\n    # By default it raises an error\n    # not_authorized: -\u003e(type, field) do\n    #   raise GraphQL::Guard::NotAuthorizedError.new(\"#{type}.#{field}\")\n    # end\n\n    # Returns an error in the response\n    \u003cb\u003enot_authorized: -\u003e(type, field) do\n      GraphQL::ExecutionError.new(\"Not authorized to access #{type}.#{field}\")\n    end\u003c/b\u003e\n  )\nend\n\u003c/pre\u003e\n\nIn this case executing a query will continue, but return `nil` for not authorized field and also an array of `errors`:\n\n\u003cpre\u003e\nSchemaWithErrors.execute(\"query { \u003cb\u003eposts\u003c/b\u003e(user_id: 1) { id title } }\")\n# =\u003e {\n#   \"data\" =\u003e \u003cb\u003enil\u003c/b\u003e,\n#   \"errors\" =\u003e [{\n#     \"messages\" =\u003e \u003cb\u003e\"Not authorized to access Query.posts\"\u003c/b\u003e,\n#     \"locations\": { \"line\" =\u003e 1, \"column\" =\u003e 9 },\n#     \"path\" =\u003e [\u003cb\u003e\"posts\"\u003c/b\u003e]\n#   }]\n# }\n\u003c/pre\u003e\n\nIn more advanced cases, you may want not to return `errors` only for some unauthorized fields. Simply return `nil` if user is not authorized to access the field. You can achieve it, for example, by placing the logic into your `PolicyObject`:\n\n\u003cpre\u003e\nclass \u003cb\u003eGraphqlPolicy\u003c/b\u003e\n  RULES = {\n    PostType =\u003e {\n      '*': {\n        guard: -\u003e(obj, args, ctx) { ... },\n        \u003cb\u003enot_authorized:\u003c/b\u003e -\u003e(type, field) { GraphQL::ExecutionError.new(\"Not authorized to access #{type}.#{field}\") }\n      }\n      title: {\n        guard: -\u003e(obj, args, ctx) { ... },\n        \u003cb\u003enot_authorized:\u003c/b\u003e -\u003e(type, field) { nil } # simply return nil if not authorized, no errors\n      }\n    }\n  }\n\n  def self.guard(type, field)\n    RULES.dig(type, field, :guard)\n  end\n\n  def self.\u003cb\u003enot_authorized_handler\u003c/b\u003e(type, field)\n    RULES\u003c/b\u003e.dig(type, field, \u003cb\u003e:not_authorized\u003c/b\u003e) || RULES\u003c/b\u003e.dig(type, :'*', \u003cb\u003e:not_authorized\u003c/b\u003e)\n  end\nend\n\nclass Schema \u003c GraphQL::Schema\n  use GraphQL::Execution::Interpreter\n  use GraphQL::Analysis::AST\n  query QueryType\n  mutation MutationType\n\n  use GraphQL::Guard.new(\n    policy_object: GraphqlPolicy,\n    not_authorized: -\u003e(type, field) {\n      handler = GraphqlPolicy.\u003cb\u003enot_authorized_handler\u003c/b\u003e(type, field)\n      handler.call(type, field)\n    }\n  )\nend\n\u003c/pre\u003e\n\n## Schema masking\n\nIt's possible to hide fields from being introspectable and accessible based on the context. For example:\n\n\u003cpre\u003e\nclass PostType \u003c GraphQL::Schema::Object\n  field :id, ID, null: false\n  field :title, String, null: true do\n    # The field \"title\" is accessible only for beta testers\n    \u003cb\u003emask -\u003e(ctx) {\u003c/b\u003e ctx[:current_user].beta_tester? \u003cb\u003e}\u003c/b\u003e\n  end\nend\n\u003c/pre\u003e\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'graphql-guard'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install graphql-guard\n\n## Testing\n\nIt's possible to test fields with `guard` in isolation:\n\n\u003cpre\u003e\n# Your type\nclass QueryType \u003c GraphQL::Schema::Object\n  field :posts, [PostType], null: false, \u003cb\u003eguard -\u003e(obj, args, ctx) {\u003c/b\u003e ... \u003cb\u003e}\u003c/b\u003e\nend\n\n# Your test\n\u003cb\u003erequire \"graphql/guard/testing\"\u003c/b\u003e\n\nposts = QueryType.\u003cb\u003efield_with_guard('posts')\u003c/b\u003e\nresult = posts.\u003cb\u003eguard(obj, args, ctx)\u003c/b\u003e\nexpect(result).to eq(true)\n\u003c/pre\u003e\n\nIf you would like to test your fields with policy objects:\n\n\n\u003cpre\u003e\n# Your type\nclass QueryType \u003c GraphQL::Schema::Object\n  field :posts, [PostType], null: false\nend\n\n# Your policy object\nclass \u003cb\u003eGraphqlPolicy\u003c/b\u003e\n  def self.\u003cb\u003eguard\u003c/b\u003e(type, field)\n    \u003cb\u003e-\u003e(obj, args, ctx) {\u003c/b\u003e ... \u003cb\u003e}\u003c/b\u003e\n  end\nend\n\n# Your test\n\u003cb\u003erequire \"graphql/guard/testing\"\u003c/b\u003e\n\nposts = QueryType.\u003cb\u003efield_with_guard('posts', GraphqlPolicy)\u003c/b\u003e\nresult = posts.\u003cb\u003eguard(obj, args, ctx)\u003c/b\u003e\nexpect(result).to eq(true)\n\u003c/pre\u003e\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/exAspArk/graphql-guard. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Graphql::Guard project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/exAspArk/graphql-guard/blob/master/CODE_OF_CONDUCT.md).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexaspark%2Fgraphql-guard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexaspark%2Fgraphql-guard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexaspark%2Fgraphql-guard/lists"}