🏢

AppSyncのLambda認証を試してみる

2021/09/18に公開

追記(2021年11月10日)

Serverless Frameworkでの挙動も試してみました。こちらも参考にどうぞ。
https://zenn.dev/merutin/articles/d03af0ee2c1e51

概要

ちょっと前のことにはなりますが、AppSyncの認証方式にlambdaが増えました
https://aws.amazon.com/jp/blogs/mobile/appsync-lambda-auth/

今まではcognito、IAM、API_KEYのみだったので権限付与は〇か×かの二択のみでした。lambdaで実装することで柔軟な認証が可能になります。

  • DynamoDBやRDSに認証情報をもち、それによってユーザ認証する
  • 同じ認証タイプでもユーザーによっての権限を共通的に入れられる
    • 閲覧権限のユーザーはmutationをすべてNGにする

チュートリアルを参考にしつつ、AWSコンソールでlambda認証を実装していきます。
(serverlessはまたの機会に)

実装

AppSyncの作成

  • デフォルトの認証モードでAWS lambdaを選択します。

認証に利用するlambdaの作成

index.js
exports.handler = async (event) => {
  console.log(`event >`, JSON.stringify(event, null, 2));
  const {
    authorizationToken,
    requestContext: { apiId, accountId },
  } = event;

  const response = {
    // ここがfalseの場合は認証失敗になる
    isAuthorized: true,
    // 後続処理に渡せる情報。vtlやリゾルバーに渡す値として利用できる
    // ここで権限チェック系の共通処理の結果を渡してあげるとよさそう
    resolverContext: {
      userid: "test-user-id",
      info: "contextual information A",
      more_info: "contextual information B",
    },
    // 拒否するfieldを選択できる
    deniedFields: [
      // 特定のtypeのフィールドのみを禁止したい場合
      `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Post/fields/comments`,
      // 特定のmutationを禁止したい場合
      `Mutation.createEvent`,
    ],
    ttlOverride: 10,
  };
  console.log(`response >`, JSON.stringify(response, null, 2));
  return response;
};

AppSyncでSchemaの作成

  • Appsyncのコンソールに戻り、該当のAPIを選択後、スキーマを選択してスキーマの編集をします

  • graphqlはデフォルトでコメントアウトされたものにcommentを追加しています。

schema {
    query: Query
    mutation: Mutation
}
type Query {
    # Get a single value of type 'Post' by primary key.
    singlePost(id: ID!): Post @aws_lambda
}
type Mutation {
    # Put a single value of type 'Post'.
    # If an item exists it's updated. If it does not it's created.
    putPost(id: ID!, title: String!): Post
}
type Post @aws_lambda {
    id: ID!
    title: String!
    comment: String
}

resolverの設定

  • 設定した値の動きを試すために、resolverをlambdaにしてみます。
  • Query - singlePostのResolverに「アタッチ」を選択して、lambdaを設定します
    • 厳密にはlambdaで関数を作成後、AppSyncのデータソースに追加が必要です。
    • 詳細は AppSyncでLambdaを呼び出す などを参考にしてみてください
  • lambdaの中身は以下のように設定します
index.js
exports.handler = async (event) => {
    console.log("event", JSON.stringify(event));
    
    return {
        id: 1,
        title: 'lamnda auth test',
        comments: "hello"
    };
};
 

実行

  • AppSyncのクエリを選択して実行します。
  • なお、Authorization Tokenに何かしらの値を入れないと Request failed with status code 401 というエラーになります。
query MyQuery {
  singlePost(id: "1") {
    id
    title
  }
}

  • cloud watchからlambdaの実行ログを見てみます(一意のIDっぽいのはxxxなど入れています)。
  • identity - resolverContextに認証のlambdaで入れた値が入っていることが確認できます。やっぱり便利そう。
{
    "arguments": {
        "id": "1"
    },
    "identity": {
        "resolverContext": {
            "more_info": "contextual information B",
            "userid": "test-user-id",
            "info": "contextual information A"
        }
    },
    "source": null,
    "request": {
        "headers": {
            "x-forwarded-for": "xxx.149.81.xxx, 52.46.61.xxx",
            "cloudfront-viewer-country": "JP",
            "cloudfront-is-tablet-viewer": "false",
            "dnt": "1",
            "x-amzn-requestid": "1199d677-5c0b-4461-b665-xxx",
            "via": "2.0 xxx.cloudfront.net (CloudFront)",
            "cloudfront-forwarded-proto": "https",
            "origin": "https://ap-northeast-1.console.aws.amazon.com",
            "content-length": "126",
            "x-forwarded-proto": "https",
            "accept-language": "ja,en-US;q=0.7,en;q=0.3",
            "host": "xxx.appsync-api.ap-northeast-1.amazonaws.com",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0",
            "cloudfront-is-mobile-viewer": "false",
            "accept": "application/json, text/plain, */*",
            "cloudfront-is-smarttv-viewer": "false",
            "accept-encoding": "gzip, deflate, br",
            "referer": "https://ap-northeast-1.console.aws.amazon.com/",
            "content-type": "application/json",
            "sec-fetch-mode": "cors",
            "x-amz-cf-id": "Utq-xxx-8a_gHVXmELA==",
            "x-amzn-trace-id": "Root=1-61451c64-xxx",
            "authorization": "xx",
            "sec-fetch-dest": "empty",
            "x-amz-user-agent": "AWS-Console-AppSync/",
            "cloudfront-is-desktop-viewer": "true",
            "sec-fetch-site": "cross-site",
            "x-forwarded-port": "443"
        }
    },
    "prev": null,
    "info": {
        "selectionSetList": [
            "id",
            "title"
        ],
        "selectionSetGraphQL": "{\n  id\n  title\n}",
        "parentTypeName": "Query",
        "fieldName": "singlePost",
        "variables": {}
    },
    "stash": {}
}

権限のないクエリを実行

  • 認証のlambdaでcommentsのフィールドに制限をかけました。
  • commentsを含むクエリを実行した場合、どのような挙動になるのか確認します。
      // 特定のtypeのフィールドのみを禁止したい場合
      `arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Post/fields/comments`,
query MyQuery {
  singlePost(id: "1") {
    id
    title
    comments
  }
}
  • commentsがnullになり、errorsに値が入っています。
  • 設定したとおりに権限による拒否ができていることが確認できました。
{
  "data": {
    "singlePost": {
      "id": "1",
      "title": "lamnda auth test",
      "comments": null
    }
  },
  "errors": [
    {
      "path": [
        "singlePost",
        "comments"
      ],
      "data": null,
      "errorType": "Unauthorized",
      "errorInfo": null,
      "locations": [
        {
          "line": 5,
          "column": 5,
          "sourceName": null
        }
      ],
      "message": "Not Authorized to access comments on type Post"
    }
  ]
}
  • lambda側のlogには特に変化がなかったので、実装側は権限を意識することなく実装することができます。
  • 今回は簡単な例ですが、ユーザー情報からパスワードを取得できないようにするなど、ユースケースは色々とありそうです。

終わりに

  • AppSyncのlambda認証を簡単に試してみました。
  • 自分で認証周りを書けるようになったので、権限によってAppSyncのAPIを分けることをせずにクエリを安全に実行できるようになりました。
    • 従来の場合に同じことをしようと思ったら、認証は通しておいて、lambdaを経由で権限チェックする必要があった。
  • 圧倒的に表現力があがったので、プロダクションでも使っていたいところです。
  • serverless frameworkのappsync pluginでもlabmda認証に対応しているみたいなので試してみようと思います。

Discussion