LoginSignup
8
1

More than 3 years have passed since last update.

GraphQLでの権限制御について

Last updated at Posted at 2020-12-14

これはSupershipグループ Advent Calendar 2020の14日目の記事です。

はじめに

GraphQLでAPIの実装で権限を付けるのに検討した内容です。
検証コードはgraphql-rubyを使ってます。

Gemfile
source 'https://rubygems.org'

gem 'graphql', '~> 1.11'

権限エラーをどう扱うかについて

作ってるAPIによってどう扱うのが適切か検討する必要はあります。

エラーメッセージのレスポンスを返すケース

エラー用のレスポンスを返すケースです。

{
  "errors" : [
    {
      "message" : "Not authorized"
    }
  ]
}

ユーザーに権限のないことを気づかれないように返すケース

ユーザーから権限があるかを知られたくないケースになります。
懸念としてはそのデーターがなくて空のなのか権限がなくて空にしてるのかがデバッグする時に分かりづらいという点があります。

配列以外の型のケース
class UserType < GraphQL::Schema::Object
  field :name, ID, null: true

  def name
    context[:admin] ? object.name : nil
  end
end
配列型のケース
class PostType < GraphQL::Schema::Object
  field :id, ID, null: false
end

class UserType < GraphQL::Schema::Object
  field :posts, [PostType], null: false

  def posts
    context[:admin] ? object.posts : []
  end
end

権限の設定が漏れやすいケース

  • postの情報は全てのuserから取得できる
  • userの情報はadminしか取得できない

という仕様にした場合

require 'graphql'

USERS = {
  '1' => {
    id: '1',
    name: 'user1'
  }
}

POSTS = {
  '1' => {
    id: '1',
    user: USERS['1']
  }
}

class UserType < GraphQL::Schema::Object
  field :id, ID, null: false
  field :name, ID, null: false
end

class PostType < GraphQL::Schema::Object
  field :id, ID, null: false
  field :user, UserType, null: false
end

class QueryType < GraphQL::Schema::Object
  field :user, UserType, null: false do
    argument :id, ID, required: true
  end

  field :post, PostType, null: false do
    argument :id, ID, required: true
  end

  def user(id:)
    return USERS[id] if context[:admin]

    raise GraphQL::ExecutionError, 'Not authorized'
  end

  def post(id:)
    POSTS[id]
  end
end

class Schema < GraphQL::Schema
  query(QueryType)
end

query = <<-GRAPHQL
query($id: ID!) {
  user(id: $id) {
    id
    name
  }
}
GRAPHQL

puts Schema.execute(query, context: { admin: false }, variables: { id: 1 }).to_h
query($id: ID!) {
  user(id: $id) {
    id
    name
  }
}

このクエリを実行した場合はuserのid、nameが取得できず権限エラーになりますが

query($id: ID!) {
  post(id: $id) {
    user {
      id
      name
    }
  }
}

このクエリを実行した場合は権限エラーにならずuserのid, nameを取得できてしまいます。
userのid, nameをpost経由で取りに行くことでadminじゃなくてもuserのid, nameが取得できてしまうという権限漏れが起きてしまいます。

修正したコード

require 'graphql'

USERS = {
  '1' => {
    id: '1',
    name: 'user1'
  }
}

POSTS = {
  '1' => {
    id: '1',
    user: USERS['1']
  }
}

class UserType < GraphQL::Schema::Object
  field :id, ID, null: false
  field :name, ID, null: false

  def id
    return USERS[id] if context[:admin]

    raise GraphQL::ExecutionError, 'Not authorized'
  end

  def name
    return USERS[id] if context[:admin]

    raise GraphQL::ExecutionError, 'Not authorized'
  end
end

class PostType < GraphQL::Schema::Object
  field :id, ID, null: false
  field :user, UserType, null: false
end

class QueryType < GraphQL::Schema::Object
  field :user, UserType, null: false do
    argument :id, ID, required: true
  end

  field :post, PostType, null: false do
    argument :id, ID, required: true
  end

  def user(id:)
    USERS[id]
  end

  def post(id:)
    POSTS[id]
  end
end

class Schema < GraphQL::Schema
  query(QueryType)
end

query = <<-GRAPHQL
query($id: ID!) {
  post(id: $id) {
    user {
      id
      name
    }
  }
}
GRAPHQL

puts Schema.execute(query, context: { admin: false }, variables: { id: 1 }).to_h

このように漏れなく権限を考慮するとフィールド毎に権限チェックをする必要が出ます。

対策

  • フィールドレベルで権限管理する
    フィールドレベルで権限管理した時に全部のフィールドに対して1つずつ権限を定義していくのは大変なのでgrapqh-guradを使うと記述量は減らせます。

  • リクエスト時のクエリのネストとフィールドの上限を設定する
    根本的な対策ではないですが、クエリのネストする深さとリクエストするフィールド数に上限を設定することで、万が一権限の実装が誤ってた場合の被害を最小限にできます。

class Schema < GraphQL::Schema
  max_complexity 80
  max_depth 8
end

最後に

  • GraphQLで定義した型に対して、このフィールドはアクセスできるがこのフィールドはアクセスできないという権限制御を漏れなく行おうとするとフィールド単位で権限を付ける必要が出ます

  • 権限を実装する場合は関連経由だと取得できてしてしまわないかを気をつける必要があります


Supershipではプロダクト開発やサービス開発に関わる人を絶賛募集しております。
ご興味がある方は以下リンクよりご確認ください。
Supership株式会社 採用サイト
是非ともよろしくお願いします。

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1