AWS cognitoでAzureADの認証サービスを使って画面を作った時の話し

# AWS cognitoでAzureADの認証サービスを使って画面を作った時の話し
## はじめに
とある案件の話し、
それぞれの特性があるクラウド業界で、単一のクラウドプラットフォームだけを選択している企業は多くはなく、
複数のクラウドを用途によって使い分けるマルチクラウド型での運用をする企業も多い。

今回はAzureでユーザー管理をしており、AWSでサービス開発をしている企業への案件があり、
AWSにサーバーレス方式で認証も兼ね備えたWEBアプリケーションを作ることとなった。
　※こんな要望は確かに多そうだと感じる。。

## 環境
クライアント - vuejs(S3 静的ファイルWEBホスティング)
認証サービス = AWS Cognito
認証元 - Azure AD
SDK - AWS Ampilify

## WEBアプリ概要
・AWS S3のWEBホスティングでサーバーレス構成
・ログイン画面 → Azure認証（権限により制御可能）
・メイン画面 → S3からオブジェクトを取得して表示する（音声データ、文章データ、画像データ の表示）
・統計画面 → ログモニタリング

## 実装方式
### AWS 設定①
AWS Cognitoのサービスを作成する。　※ユーザープール等を作成する際は場所に注意（場所毎にユーザープールを作成する為）
* ユーザープールを作成して以下の情報をメモ（Azure側で使う）
・UserPoolID：ユーザープールのプールID
・yourDomainPrefix：ドメイン名から ドメインのプレフィックス

Azure側で設定する値   

```
プールID: urn:amazon:cognito:sp:UserPoolID
ドメイン: https://yourDomainPrefix.auth.yourRegion.amazoncognito.com/saml2/idpresponse
```

### Azure 設定
まず、Azure側でSAML認証の設定をする必要があります。
1. Azure Active Directory → エンタープライズアプリケーションを選択し、New applicationからNon-galleryのアプリを作成。（独自のアプリケーション）
2. 作成したアプリのブレードからシングルサインオン → SAMLを選択
3. SAML構成の編集をする。 （AWS設定でメモした値を入力）
4. SAML 署名証明書のアプリのフェデレーション メタデータ URLはAWS側設定で使うのでメモ
5. ユーザーとグループでユーザーを追加（追加しないとログイン出来ないので注意）

### AWS 設定②
Cognito側へもSAML認証のURLを設定していきます。
1. Cognito ユーザープールからIDプロバイダー → SAMLを選択して以下を入力
プロバイダー名：AzureAD
識別子：先ほどコピーのURL
2. 属性マッピングからSAMLを選びSAML属性を設定

```
SAML属性：http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
ユーザープール属性：Email
```

※SAML属性はAzureに記載あり… おそらく同じもので可

3. アプリクライアントの設定
有効なIDプロバイダーをAzureADに選択

```
コールバックURL：http://localhost:8000/
サインアウトURL：http://localhost:8000/
```

※クライアントを自前で作る際は、コールバックURLにそのクライアントURLを入力する

```
許可されている OAuth フロー  ：Authorization code grant
許可されている OAuth スコープ  ：email, openid, profile
```

ここまで出来たら一度テストを行う
AWS側でアプリUIを用意してくれている
作成したアプリクライアントからホストされたUIを起動を選択。

localhost → Azureログインページ → サインイン → localhost（動いてないのでアクセスできませんとでる）
となればうまく機能していることになる。※URLパラメータに認可コードが付与されている事を確認

ユーザーとグループを確認し、EXTERNAL_PROVIDERでログインのユーザーが登録されていることを確認。

### WEBアプリ実装
サービスの機能が確認出来た後は、AmplifyとVueでクライアントアプリを作っていきます。
ただ、Ampilifyを使っても認可コードを得たリダイレクト後の処理は自前で作成する必要があります。

ある程度簡素化してご紹介しますので、足りない部分は補完して読み進めてください。

#### Amplify Vueスターターを起動
[aws-amplify-vue](https://github.com/aws-samples/aws-amplify-vue.git)
こちらのソースをベースに作っていきます。

#### ログイン画面の実装
ホストUIのを参考にログインボタンの画面を作成。

```html
  <div>
    <a class="btn-square" :href=" COGNITO_BASE_URL + '/oauth2/authorize?identity_provider=AzureAD&amp;redirect_uri=' + COGNITO_REDIRECT_URL + '&amp;response_type=CODE&amp;client_id=' + USER_POOL_WEB_CLIENT_ID + '&amp;scope=aws.cognito.signin.user.admin email openid profile' "
    >Azure サインイン</a>
  </div>
```

#### 認証処理
src/router/index.js

まず認可コードの受け取り後、それをsignIn処理へ送る処理を作成

```js
router.beforeResolve(async (to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    const url_obj = getSearchObj(location.search); // URLクエリ文字列の分解をしている
    if (url_obj.hasOwnProperty('code')) {
      user = await signIn(url_obj.code)
    } else {
      user = await getUser();
    }
    if (!user) {
      return next({
        path: '/auth',
        query: {
          redirect: to.fullPath,
        }
      });
    }
    return next()
  }
  return next()
})
```

サインインでは、認可コードが正しい事を確認する処理をおこなう。
Azure認可コード → AWSoauth2トークン → ユーザー情報取得
上の流れでユーザー情報を取得した後、下で記述したローカルストレージに認証情報を書き込む処理を行い、認証が完了すればログイン先の画面へ遷移される処理となっている。

```js
function signIn(code) {
  return new Promise(function(resolve, reject) {
  let params = new URLSearchParams();
  params.append('grant_type', 'authorization_code')
  params.append('redirect_uri', config.REDIRECT_URL)
  params.append('identity_provider', config.IDENTITY_PROVIDER)
  params.append('code', code)
  params.append('client_id', config.CLIENT_ID)

  axios.post(config.COG_URL + '/oauth2/token', params , {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      }
  })
    .then(token => {
      const bearer = 'Bearer ' + token.data.access_token
      axios.post(config.COG_URL + '/oauth2/userInfo', {}, {
          headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': bearer
          }
        })
        .then(userInfo => {
          // localStorageを書き換え
          localStorage.setItem('CognitoIdentityServiceProvider.' + config.CLIENT_ID + '.' + userInfo.data.sub + '.accessToken', token.data.access_token)
          localStorage.setItem('CognitoIdentityServiceProvider.' + config.CLIENT_ID + '.' + userInfo.data.sub + '.idToken', token.data.id_token)
          localStorage.setItem('CognitoIdentityServiceProvider.' + config.CLIENT_ID + '.' + userInfo.data.sub + '.refreshToken', token.data.refresh_token)
          localStorage.setItem('CognitoIdentityServiceProvider.' + config.CLIENT_ID + '.' + userInfo.data.sub + '.clockDrift', 0)
          localStorage.setItem('CognitoIdentityServiceProvider.' + config.CLIENT_ID + '.LastAuthUser', userInfo.data.sub)
          resolve(userInfo.data)
        })
        .catch(error => {
          console.log(error)
          reject(null)
          // error 処理
        })
    })
    .catch(error => {
      console.log(error)
      reject(null)
      // error 処理
    })
  })
  }
```

ここまでで、一通りの認証から画面表示までのアプリが作れると思います。
私が作成した完成版アプリでは、遷移元からS3オブジェクトの識別子をURLに加えて、認証を経てその識別子をもとにS3オブジェクトを表示する処理を行っています。 


## さいごに
APIファーストの恩恵を受け、労力・時間もかからずにこちらの認証アプリを開発することができた。
このサーバーレス構成は、クラウドを最適に用いることによりサーバー維持コストや、運用コストなどが低い「優れた費用対効果」やサービス組み合わせることで「開発にかかる速度が短縮」されるメリットが挙げられる。
デメリットは、ベンダーサービスに依存する



## 参考
[Amplify 公式DOC](https://aws-amplify.github.io/docs/js/vue)
[AmplifyとVueを使ってS3をラップしたアプリケーションを作ってみた](https://dev.classmethod.jp/articles/amplify-s3-upload/)
[Azure ADをAWS Cognito Federated IdPに追加して、Amplify+Vueアプリでログインする（模索中）](https://qiita.com/ksukenobe/items/a80e0e4435bcddd5e379)
[【AWS】CognitoでGoogleソーシャルログイン。Amplifyと連携してVue.jsアプリケーションに組み込む](https://qiita.com/chitamano/items/d28f205ece7cbe46c02a)