AmplifyのStorageと認証まわり

# AmplifyのStorageと認証まわり

## はじめに
サーバーレスWebアプリ開発を進めてくなかで、AmplifyのStorageと認証まわりの理解が進んできて、完全に理解したなと思ってJAWS-UG浜松で発表しようとしたら全然できませんでした。
できなかったというか、話し始めようとしたら話せるほど自分のなかで整理できてないことに気付いてギブアップしました。発表内容はこれだけじゃなかったとはいえ、まぁ失礼な話ですよね。本当すみません。
リベンジすべく、整理するために記事を書きます。

## Guest許可と認証ユーザーのみ
AmplifyのStorageをセットアップする際の質問のなかに以下のような質問があって、選択できます。

```
? Who should have access: (Use arrow keys)
  Auth users only 
  Auth and guest users 
```

* Auth user only  
  Storageへアクセスするためにユーザー認証(ログイン)が必要。
* Auth and guest users  
  ユーザー認証していない状態でもStorageへアクセスできる。

## ファイルアップロード(put)
Amplifyを利用してファイルをS3にアップロードするJavaScriptのコード例

```.js
  :
import { Storage } from 'aws-amplify';
  :
      Storage.put(filePath, image).then(result => {
        console.log(result);
      }).catch(err => console.log(err));
```

例えば`filePath = "a/b/c/test.jpg"`とした場合、
resultには `key : a/b/c/test.jpg` のような結果(要はS3 Object key)が返ってきます。
そしてバケットには以下のように`public/a/b/c/test.jpg`としてファイルがアップロードされます。
![Screenshot 2020-01-25 at 22.22.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/c0f63140-8dde-def2-94e5-45ff433864b5.png)
`public`が先頭についてますが、これはアクセスレベルです。

AmplifyのStorageには3つのアクセスレベルがあります。

* public : 全員が読み書きできる。
* protected : 全員が読めるが、書けるのは自分だけ。
* private : 自分しか読み書きできない。

protectedの場合は以下のように書きます。

```.js
      Storage.put(filePath, image, {
          level: 'protected'
      }).then(result => {
        console.log(result);
      }).catch(err => console.log(err));
```
privateの場合は、protectedをprivateにすればいいだけです。

protectedやprivateの場合、アップロードされるパスの先頭には、アクセスレベルに続き`Cognito Identity ID`が付きます。
![ink (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/497b94af-15f0-377b-172b-a75e1694e1b0.png)

なお、Guestでprotectedやprivate指定した場合、以下の例外とともに HTTP403(Forbidden(閲覧禁止))が返ってきました。
`AWSS3Provider - error uploading AccessDenied: Access Denied`

## ファイルダウンロード(アドレス取得)(get)
Amplifyを利用してS3にあるファイルの参照URLを取得するJavaScriptのコード例

```js
      Storage.get(s3key).then(result => {
        this.imageURL = result;
      }).catch(err => console.log(err));
```
`s3key = "a/b/c/test.jpg"`このように、s3keyにはアクセスレベル名やCognito Identity IDを付けないことに注意。

resultに以下のようなURLが取得できます。
`https://sample-vue-project-bucket-work.s3.ap-northeast-1.amazonaws.com/public/a/b/c/test.jpg?X-Amz-Algorithm=xxx&X-Amz-Credential=xxx&X-Amz-Date=xxx&X-Amz-Expires=900&X-Amz-Security-Token=xxx&X-Amz-Signature=xxx&X-Amz-SignedHeaders=host`
後ろに認証のための色々なパラメータが付いてますね。これがないとアクセスが拒否されます。
また、デフォルトで900秒(15分)という有効期限も付いています。

任意の有効期限を指定することもできます。

```js
      let s3key = "a/b/c/test.jpg"
      let dataExpireSeconds = (30 * 60);
      Storage.get(s3key, { expires: dataExpireSeconds }).then(result => {
        this.imageURL = result;
      }).catch(err => console.log(err));
```

適切な認証パラメータではなかったり、有効期限が切れた後にgetすると、HTTP403(Forbidden(閲覧禁止))とともにエラーが返ってきます。

```xml
<Error>
  <Code>AccessDenied</Code>
  <Message>Access Denied</Message>
  <RequestId>D43YC2E5080A822C</RequestId>
  <HostId>
    w7zzd5NRpm8Ih0DN3hmjZT8sDQT5hcV7FhqbUaW6ZG7SpsepV3UspzDUMDVKRkPesaUpyUSEGC0=
  </HostId>
</Error>

<Error>
  <Code>AccessDenied</Code>
  <Message>Request has expired</Message>
  <X-Amz-Expires>900</X-Amz-Expires>
  <Expires>2020-01-26T03:07:16Z</Expires>
  <ServerTime>2020-01-26T03:20:13Z</ServerTime>
  <RequestId>9F14A2CA61AE75F2</RequestId>
  <HostId>
    wru7usdhgiI2MXWsdFGh59x8JYl1TbOzPDMs2L5LZ/IwDD9tueInxK7bfh/rKftjJZCCf9CY7SE=
  </HostId>
</Error>
```

## AmplifyのAuthによる認証
ゲストを許可した場合は不要ですが、そうではない場合は認証をパスした状態でputやgetをする必要があります。

### ユーザーにサインインしてもらう
Amplifyを利用した一般的な実現方法としては、AmplifyのAuthの基本機能を利用して、ユーザーにアカウントを作ってもらったうえでサインインしてもらう形になるかと思います。
![Screenshot 2020-01-26 at 13.15.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/394775/da73a303-5260-9834-93b7-bffd0cbb22af.png)
こんな感じの画面でユーザー登録や認証の基盤を構築することがサクッと実現できちゃいます。
AmplifyでAuthをセットアップをする手順などの詳細は、この記事では割愛します。

### プログラムで自動的に認証する
ユーザーがログインしなくてもサービスを利用できるようにしたい場合、一般的にはゲストを許可する設定でセットアップして、ゲストとして処理してあげればいいと思います。
ただ、あえてゲストは利用せず、サービス側が予め用意したアカウントで自動的(ユーザーに意識させず)に認証をパスするということもできます。
細かな手順などは[こちらの記事](https://qiita.com/w2or3w/items/fb82544f001296087bd7)に書いてありますので、やはりこの記事では割愛します。
Cognitoユーザーを作成したうえで、JavaScriptで自動的にログインをします。

## あとがき
まずは何か動くものを作る。座学よりも効率的に学ぶことができると思っています。
それを人に説明しようとした場合、やはりある程度は整理してまとめるという作業が必要になってきますね。改めて実感しました。
これからはちゃんと準備してから話をしようと思います。

さて今回はStorage観点での認証の話でしたが、次は認証そのもの、その中でも特にソーシャルログインについて学びたいと考えています。
アカウント作成って心理的ハードルが高く、また、手間がかかると思っていて、とはいえユーザー毎の情報を保護することも必要だと思ってます。
多少でも心理的ハードルを下げ、また、ユーザー認証の手間を少なくしてくれるソーシャルログインは、サービスにとって必須の機能ではないでしょうか。と、思うわけなんです。