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

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

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 の設定

Amplify Framework Documentation
Amplify Framework Documentation docs.amplify.aws
Amplify Framework Documentation

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

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

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

Amplify Framework Documentation
Amplify Framework Documentation docs.amplify.aws
Amplify Framework Documentation

基本的にはここに書いてある通り、 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 のオブジェクトのキー名が作れることになります。

認証情報

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

Getting Credentials - Amazon CognitoYou can use Amazon Cognito to deliver temporary, limited-privilege credentials to your application, so that your users can access AWS resources. This section describes how to get credentials and how to retrieve an Amazon Cognito identity from an identity pool.
Getting Credentials - Amazon Cognito docs.aws.amazon.com
Getting Credentials - Amazon Cognito

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

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

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

Accessing Your User Pools using the Amazon Cognito Identity SDK for JavaScript | Amazon Web ServicesIntroduction In April, we launched the beta version of a new Amazon Cognito feature called Your User Pools. Among other functionality, the User Pools feature makes it easy for developers to add sign-up and sign-in functionality to web apps. AWS Mobile SDKs for Android, JavaScript, and iOS are available with this beta launch. In this […]
Accessing Your User Pools using the Amazon Cognito Identity SDK for JavaScript | Amazon Web Services aws.amazon.com
Accessing Your User Pools using the Amazon Cognito Identity SDK for JavaScript | Amazon Web Services

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

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 から扱うための情報をまとめました。

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

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください