💬

AWS + Google Workspaceでの認可アイデア

2024/02/20に公開

記事の内容

AWS + Google Workspaceを活用した認可について考えてみました。

個人でGoogleアカウントを持っている持っているかたは、アプリのログイン画面でGoogleでログインというボタン見たことがあるかと思います。これはGoogleアカウントからGoogleに登録してある自身の情報を取得してログインできる機能です。Google Workspace上のアカウントでも同様に利用することができます。

このGoogle Workspace上のアカウントでログインできるということを活用すると、社内のGoogle Workspaceのメンバーだけアクセス可能なアプリを作成できると思います。

今回はAmazon CognitoとGoogle Workspace(Google Cloud)を連携させて、CognitoでログインしたユーザーでGoogle WorkspaceのAPIを実行できる構成を考えてみました。イメージとしては、社内ポータルアプリのような感じです。
Google Workspaceを社内のプラットフォームに使っている会社は需要あるのではないかと思います。

試してみたところ課題もあったので、共有できればと思います。

関係する技術・ツール

  • Google Cloud
  • Amazon Cognito
  • OAuth2

アーキテクチャ

今回のアーキテクチャのイメージ図です。
Google Workspaceには社内ユーザーの情報があり、Googleにログインしているユーザーの認証情報を使ってフロントエンドのアプリは認証と認可を行います。
認可が完了すると、CognitoからGoogle APIを実行できるアクセストークンが取得できるようになります。
このアクセストークンはGoogle Workspaceで管理しているGoogle DriveやFormにアクセスできるというわけです。
このアクセストークンの取得フローはOAuthのフローで行われます。

OAuth

簡単に確認しておくと、OAuthには大きく下記の2つの認可のフローがあります。アプリケーションの実行コンテクストに合わせて選択します。
基本的には、

  • 認可コード・・・フロントエンド
  • クライアントクレデンシャル・・・バックエンド

と捉えてもらえばよいかと思います。

  • 認可コードフロー

フロントエンド画面を介して、アプリ操作しているユーザーに対して認可の同意を求めて、ユーザーが合意をした場合に認可を行うフローです。
最もよくイメージされるOAuthの認可フローだと思います。下記のようなGoogleの画面を見たことがある方は多いと思います。

  • クライアントクレデンシャルフロー

クライアントIDとクライアントシークレットの情報を元に認可を行うフローです。イメージするなら、クライアントIDという名前とクライアントシークレットというパスワードが一致している認可リクエストに対して、認可を行うフローだと考えてもらえればよいかと思います。
これは例えば特定のユーザーが実行するわけではないバックエンドアプリのケースに選択します。クライアントIDとクライアントシークレットはバックエンドに設定しておくことが必要ですが、バックエンドであればセキュアにこの情報を管理することが可能です。

今回はフロントエンドの認可なので、認可コードフローを選択します。

流れ

  1. Google Cloudでアプリクライアントを作成
  2. Cognitoでユーザープールを作成
  3. Google側のアプリクライアントでCognitoのドメインを設定
  4. フロントエンドアプリでログイン確認

公式ドキュメントだと下記が参考になると思います。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-configuring-federation-with-social-idp.html#cognito-user-pools-social-step-1

GoogleCloudの設定

  1. Google Cloud環境の作成

Google Workspace配下にGoogle Cloud環境を作成します。
ワークスペースドメイン配下に作成できるかと思います。
作成すると下記のようにツリー上に表示されます。

  1. アプリクライアントを作成

環境を作成したら、「API とサービス」 => 「認証情報」を表示します。
「認証情報を作成」 => 「OAuthクライアントID」から作成を行います。


  1. アプリクライアントの設定

「承認済みの JavaScript 生成元」と「承認済みのリダイレクト URI」はこの時点では設定しません。
なのでそのままで作成をします。

作成すると、クライアントIDとクライアントシークレットが発行されます。この2つは控えておきます。
jsonとしてダウンロードでダウンロードしておくといいでしょう。

  1. Google APIを有効にする

APIコールの確認のために、Google APIを有効にしておきます。
確認のためであればどれでもいいのですが、ここではform APIにします。

「API とサービス」 => 「有効なAPI とサービス」を表示します。
「ENABLE APIS AND SERVICES」をクリックして、検索フォームからformを検索します。

Google formがヒットするのでこれを選択して「有効にする」を選択して有効にします。
これでアクセストークンを使用してGoogle Form APIをコールできるようになります。

いったんGoogle Cloud側での作業は以上となります。

Cognitoの設定

次にGognitoでの作業となります。
Amplifyで構築する場合はCLIで作成することになりますが、今回はAWSコンソールで設定ポイントを確認します。
変更する箇所のみピックアップしていきたいと思います。

1
Cognito ユーザープールのサインインオプション情報・・・「email」
フェデレーティッドサインインのオプション情報・・・「Google」

2
MFA・・・なし

3
カスタム属性

Google Cloudからトークンを引き受けるためにカスタム属性を追加します。

  • access_token_google
  • refresh_token_google

を設定します。いずれもタイプはStringです。

4
Eメール

CognitoでEメールを送信

5

フェデレーテッドアイデンティティプロバイダーを接続

ここで1でGoogleを選択したのでGoogleについての設定が表示されると思います。

  • クライアントIDとシークレット
    こちらで作成したアプリクライアントのIDとシークレットを設定します。

  • スコープ
    openid email profile https://www.googleapis.com/auth/drive

こちらに記載あるようにスコープはスペース区切りで入力します。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-pools-configuring-federation-with-social-idp.html#cognito-user-pools-social-step-2

  • Google とユーザープール間で属性をマッピング
    Googleから渡される属性をCognitoでどんな名前で扱うか設定します。
    先ほどカスタム属性で入力した値に加えて、emailとusernameについても下記のように設定します。

6

ユーザープール名・・・任意の名前
Cognito ドメイン・・・こちらも任意の名前でOKですが、既に使われているドメインは使用できません。設定した名前は後ほどGoogle Cloudで設定するんおで

7

アプリケーションクライアント名・・・任意の名前
許可されているコールバック・・・今回は確認のためlocalhostを設定します。実運用では、本番環境のフロントエンドアプリurlを設定します。

8
高度なアプリケーションクライアントの設定

OpenID Connect のスコープで下記を設定します。

  • 電話番号
  • E メール
  • プロファイル
  • aws.cognito.signin.user.admin

9
許可されているサインアウト URL・・・こちらも今回は確認のためlocalhostを設定します。実運用では、本番環境のフロントエンドアプリurlを設定します。

10

属性の読み取りおよび書き込み許可・・・カスタム属性で設定したaccess_token_google, refresh_token_googleをチェックします。

Google CloudでCognitoのドメインを設定する

作成したおいたアプリクライアントへ控えておいたCognitoのurlを設定します。

東京リージョンでCognitoを作成するとhttps://任意の名前.auth.ap-northeast-1.amazoncognito.comのようなドメインになると思います。

注意点としては、AWS公式ドキュメントを確認すると、承認済みのリダイレクト URIの末尾には/oauth2/idpresponseが必要のようです。

したがって、

  • 承認済みの JavaScript 生成元・・・https://任意の名前.auth.ap-northeast-1.amazoncognito.com

  • 承認済みのリダイレクト URI・・・https://任意の名前.auth.ap-northeast-1.amazoncognito.com/oauth2/idpresponse

を設定しましょう。

フロントエンドアプリからログイン確認

ここまでできたらフロントエンドアプリでログインして確認してみます。
Next.jsでlocalhost:3000/loginでログイン画面を作成してあるとします。
login.tsxは下記のような実装になります。

login.tsx
import React, { useEffect } from "react";
import { Auth } from "aws-amplify";
import { CognitoHostedUIIdentityProvider } from "@aws-amplify/auth";

const loginPage = () => {
  const redirectCognitoPage = () => {
    Auth.federatedSignIn({
      provider: CognitoHostedUIIdentityProvider.Google,
    });
  };

  useEffect(() => {
    redirectCognitoPage();
  }, []);

  return <></>;
};

export default loginPage;

Authモジュールを使うと、ログイン画面が簡単に実装できます。
ここでAmplifyのメリットが享受できるかと思います。

http://localhost:3000/loginにアクセスすると、下記のようなお馴染みのGoogleの認可画面が表示されると思います。

あらかじめGoogle WorkspaceのアカウントでGooglenへログインしていれば該当のアカウントが表示されると思いますので、クリックすると認可が実行されます。
無事認可が成功すればトークンと共にlocalhost:3000にリダイレクトされます。

リフレッシュトークンはマッピングできなかった

これで晴れてログインできました。
Cognitoで設定したカスタム属性、アクセストークンとリフレッシュトークンを取得してみましょう。
userinfoエンドポイントから取得できます。

https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/userinfo-endpoint.html#get-userinfo-request-header-parameters

取得すると下記のようなjsonが取得できます。

{
    "id": "ap-northeast-1:xxxxxxxxxxxxxxx",
    "username": "google_xxxxxxxxxxxxxxxx",
    "attributes": {
        "sub": "xxxxxxxxxxxxxxxxxxxxxxxx",
        "identities": "",
        "email_verified": false,
        "custom:access_token_google": "",
        "email": ""
    }
}

attributesの中にcustom:というプレフィックスである値がカスタム属性です。
確認してみると、なんとrefresh_tokenだけありません。。。

調べてみると、Google側の認可エンドポイントのパラメータにaccess_type=offlineが必要なようです。
https://developers.google.com/identity/protocols/oauth2/web-server?hl=ja#obtainingaccesstokens

しかし、Cognito側にはaccess_typeはパラメータとしてありません。。。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/authorization-endpoint.html

つまり、アクセストークンを取得しても、トークンの期限が切れた後に更新する術がないことになるため時間が経つとGoogle APIを実行できなくなります。再度ログインし直すなんてことはユーザー体験を
損なってしまうのでできません。

https://github.com/aws-amplify/amplify-js/issues/3619#issuecomment-882573967

少し調べてみると、同様の問題についてisuueがありました。
Google APIのsdkを使ってgoogleユーザーのIDを紐づけておくのが解決策のようです。

こちら今回は試せなかったのですが、また時間がある際に試して見れればと思います。

トークンの管理が課題

今回のようにGoogle Workspaceユーザーと連携しつつ、Cognitoを使って認可を実現したい場合、Google APIとCognitoのそれぞれのトークンの管理が課題になりそうだと感じました。
どちらが切れてしまったら、アプリとして動かなくなってしまうためです。

GoogleのOAuthだけに統一すれば解決しますが、Amplifyが使えるとアプリの開発効率も上がるので、できれば併用したいところです。

今回は以上となります。最後まで読んでいただきありがとうございました。

NCDCエンジニアブログ

Discussion