エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

AWS AppSync を IAM 認証で使うための graphql-client のアダプター gem を公開しました

こんにちは、エムスリーエンジニアリンググループ 兼 QLife エンジニアの園田 (@ryoryoryohei) です。

AWS AppSync 使ってますか? サーバーレスで GraphQL のエンドポイントを提供できる強力なサービスです。

AppSync には認証方式が現状 4 つあり、そのうちの 1 つである IAM 認証を設定した場合、GraphQL の HTTP リクエストは AWS の 署名バージョン 4 で署名 されている必要があります。

GraphQL の代表的な Ruby ライブラリである Github の graphql-client は当然 IAM 認証には対応していないので、IAM 認証に対応させるためのアダプターライブラリを実装して公開しました。

rubygems.org

ソースファイルは 1 ファイルで 50 ステップほどしかないという超シンプル構成です。

require 'graphql/client/http'
require 'json'
require 'aws-sigv4'
require 'net/http'

module GraphQL
  class Client
    class Aws < GraphQL::Client::HTTP
      def initialize(uri, **opts, &block)
        if block_given?
          super(uri, &block)
        else
          super(uri)
        end
        appsync_signer_option = opts.merge(service: 'appsync')
        @signer = ::Aws::Sigv4::Signer.new(appsync_signer_option)
      end

      def execute(document:, operation_name: nil, variables: {}, context: {})
        request = Net::HTTP::Post.new(uri.request_uri)

        request['Accept'] = 'application/json'
        headers(context).each { |name, value| request[name] = value }

        request.body = build_body(document, operation_name, variables)

        request = sign!(request)

        response = connection.request(request)

        case response
        when Net::HTTPOK, Net::HTTPBadRequest
          JSON.parse(response.body)
        else
          { 'errors' => [{ 'message' => "#{response.code} #{response.message}" }] }
        end
      end

      def build_body(document, operation_name, variables)
        body = { 'query' => document.to_query_string }
        body['variables'] = variables if variables.any?
        body['operationName'] = operation_name if operation_name
        JSON.generate(body)
      end

      def sign!(request)
        signature = @signer.sign_request(http_method: request.method, url: uri, body: request.body)
        request['Host'] = signature.headers['Host']
        request['X-Amz-Date'] = signature.headers['x-amz-date']
        request['X-Amz-Security-Token'] = signature.headers['x-amz-security-token']
        request['X-Amz-Content-Sha256']= signature.headers['x-amz-content-sha256']
        request['Authorization'] = signature.headers['authorization']
        request['Content-Type'] = 'application/graphql'
        request
      end
    end
  end
end

GraphQL::Client::HTTP を継承して execute メソッドをオーバーライドしています。その中で署名を追加しているだけです。

Version 1.0.0 では Net::HTTP を継承したクラスを利用するようにしていたのですが、それだと webmock が認識してくれなかったため、 execute メソッドをオーバーライドするように変更しました。

使い方も非常に簡単です。Github の README をご覧ください。

以上、今回はこれだけです!

We are hiring

QLife では共にチーム一丸となってより良いものづくりにこだわれる仲間を募集中です! 小さいサービスが多いので新しい AWS のサービスの利用にも非常に積極的に取り組んでいます! カジュアル面談も行っていますので、興味がある方は entry@qlife.co.jp に「カジュアル面談希望」とメールをください!

www.qlife.co.jp

エムスリーでもエンジニアを随時募集しています!共に医療 × テクノロジーの未来を切り拓いてくれる仲間を募集中です! AWS 以外にも GCP や Firebase などのクラウドも活用しています!興味がある方はカジュアル面談やTechtalkにおこしください!!

jobs.m3.com