Last updated on

AWS Amplify を Federation 認証した場合に Storage で private 保存した画像を Functions から扱う


[point_maker type=“heading_icon” base_color=“apple_green” title_icon=“info-circle-solid” title_color_background=“true” title_color_border=“false” content_type=“text” content_color_background=“true” content_color_border=“false” block_editor=“true”]

こちらは、 “ゆるWeb勉強会@札幌 Advent Calendar 2020” の 4日目の記事です。

[/point_maker]

AWS Amplify で Federation 認証を使った場合のあれこれは、あまりドキュメント化されていません。(2020年11月29日現在)

なので、色々と苦労したことを残しておきたいと思います。

今回は、フロントエンドで Storage ライブラリを使って private に画像を保存したあとに、 Functions でどうやってアクセスするかについてです。

前提

$ amplify -v
 Scanning for plugins...
 Plugin scan successful
 4.34.0 
$

CLIでもろもろ操作できる状態の前提です。

今回はあまり影響しないと思いますが、フロントエンドは Vue.js での実装のみ確認しています。

Functions (Lambda) は Node.js 12.x を利用します。

Authentication の設定

https://docs.amplify.aws/lib/auth/social/q/platform/js

詳しいことは割愛します。

今回は Google Sign-in を使っています。

Storage を使ってファイルのアップロード

https://docs.amplify.aws/lib/storage/upload/q/platform/js

基本的にはここに書いてある通り、 Storage.put() を使うことで利用できます。

private にする(認証したユーザーのみ操作可能)場合は、 private level に書いている通りです。

Storage.put('test.txt', 'Private Content', {
     level: 'private',
     contentType: 'text/plain'
 })
 .then (result => console.log(result))
 .catch(err => console.log(err));

Functions

$ amplify function update
 ? Select which capability you want to update: Lambda function (serverless function)
 ? Select the Lambda function you want to update xxxxfunc
 General information
 | Name: xxxxfunc
 | Runtime: nodejs
 Resource access permission
 xxxxc6a22a59 (create, read, update, delete)
 xxxx (create, read, update, delete) 
 Scheduled recurring invocation
 | Not configured
 Lambda layers
 Not configured 
 ? Which setting do you want to update? Resource access permissions
 ? Select the category auth, storage
 Auth category has a resource called xxxxc6a22a59
 ? Select the operations you want to permit for xxxxc6a22a59 create, read, update, delete
 ? Storage has 4 resources in this project. Select the one you would like your Lambda to access xxxx
 ? Select the operations you want to permit for xxxx create, read, update, delete
 You can access the following resource attributes as environment variables from your Lambda function
         AUTH_XXXX6A22A59_USERPOOLID
         STORAGE_XXXX_BUCKETNAME
 ? Do you want to edit the local lambda function now? No

$

認証(Auth)とストレージ(Storage)にパーミッションを与えておきます。

いったんここでは create, read, update, delete を与えていますが、適切なものだけにしてください。
(Storageは全部、Authはread、など。)

あとは、基本的に AWS-SDK を使う手順になるのですが、認証情報をうまいこと Lambda へ渡してあげる必要があります。

S3 へのアクセスに必要な情報として、まず下記が必要です。

  • バケット名
  • キー名
  • 認証情報

バケット名はそのままなのですが、キー名と認証情報に工夫が必要です。

キー名

Storage で private に保存した場合に、特別なプレフィクスが付きます。

private/{CognitoIdentityId}/

この後ろに、アップロード時に Storage で指定したキー名が続きます。

このIDは、 AWS コンソールからだと

Cognito > IDプールの管理 > Amplify のプロジェクトで作成された IDプール > IDブラウザ

で確認することが可能です。(複数ユーザーが登録済みの場合は、作成日などで判別してください。)

これがわかれば、特定のユーザーのアップロードした S3 のオブジェクトのキー名が作れることになります。

認証情報

認証情報は、あまり良い情報がまとまっていなかったのですが、色々と探して見つけました。

https://docs.aws.amazon.com/cognito/latest/developerguide/getting-credentials.html#getting-credentials-1.javascript

ベースとなる情報は、まず上記になります。

AWS.CognitoIdentityCredentials() を使えばよさそうなことがわかったのですが、 Logins パラメータについては今回の条件に該当するものが記載ありません。( Google で直接認証するのではなく、 Cognito 経由なため。)

探した結果、 AWS のブログ記事に該当する箇所を見つけました。

https://aws.amazon.com/jp/blogs/mobile/accessing-your-user-pools-using-the-amazon-cognito-identity-sdk-for-javascript/

この記事の最後にあるコードをベースとすれば、今回の条件に当てはめられそうです。

AWS.config.credentials = new AWS.CognitoIdentityCredentials({
     IdentityPoolId: 'us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
     Logins: {
         'cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX': result.getIdToken().getJwtToken()
     }
 });
 AWS.config.credentials.get(function(err){
     if (err) {
         alert(err);
     }
 });

まずは IdentityPoolId から。

このIDは、 AWS コンソールからだと

Cognito > IDプールの管理 > Amplify のプロジェクトで作成された IDプール > サンプルコード

で確認することが可能です。

続いて Logins のパラメータですが、キーが Cognito の “エンドポイント/ユーザープールID” 、バリューがそそこから払い出された “トークン” 、となります。

エンドポイントは書かれたものを見てわかる通り cognito-idp.[リージョン].amazonaws.com となります。

ユーザープールIDは、 Fucntions を設定した際に権限を許可しているため、環境変数でアクセス可能です。
AUTH_XXXXC6A22A59_USERPOOLID のような名前になります。

最後のトークンについては、これは認証を実際に行なったフロントエンドから渡す必要があります。

試しにやる場合には、フロントエンドで下記の情報をコンソールに出力し、結果を Lambda のコードの中に埋め込むと利用できるはずです。

console.log((await Auth.currentUserPoolUser()).signInUserSession.idToken.jwtToken);

実際にアクセス

上記をまとめると、下記のようなコードでアクセスできるようになるはずです。

const AWS = require('aws-sdk')
const IDENTITY_POOL_ID = 'ap-northeast-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
const IDENTITY_ID = 'XXXXXX'
const BUCKET_NAME = process.env.STORAGE_XXXX_BUCKETNAME
const USER_POOL_ID = process.env.AUTH_XXXXC6A22A59_USERPOOLID
const KEY_PRIVATE_PREFIX = 'private'
const TOKEN = '...'

let S3 = null

exports.handler = async event => {
  const key = event.arguments.key

  const cognitoLogins = {}
  cognitoLogins[`cognito-idp.ap-northeast-1.amazonaws.com/${USER_POOL_ID}`] = TOKEN

  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: IDENTITY_POOL_ID,
    Logins: cognitoLogins,
  })

  S3 = new AWS.S3({signatureVersion: 'v4'})
  const objectKey = `${KEY_PRIVATE_PREFIX}/${IDENTITY_ID}/${key}`

  await S3.getObject({ Bucket: bucket, Key: objectKey })
    .promise()
    .then(data => {
      ...
    }

  return {
    statusCode: 200,
    body: 'end'
  }
}

まとめ

まずは、 AWS Amplify を Federation 認証 した場合に、 Storage で private 保存した画像を Functions から扱うための情報をまとめました。

このままだと、使いづらいというよりよくないコードになるので、次回は必要な情報類をフロントから渡したり、利用可能な環境変数として追加したりしてみます。