🍘

Google OAuth2でログインしたユーザーがGoogleグループのメンバーかチェックする(Python,Streamlit)

2024/01/04に公開

はじめに

Python(というかStreamlit)で作ったアプリに認証をつけたく、Google OAuth2で認証をつけました。認証機能の実装はyag_aysさんの記事を参考にしたので、そちらを参照いただければと思います。
https://zenn.dev/yag_ays/articles/ac982910770010

さて、Google OAuth2で認証した場合、単純な設定で許可できるユーザーは以下のいずれかになります。

  • Googleのユーザーだったら誰でもOK
  • 自分の組織のユーザーのみOK

後者の組織の制限で十分な場合もあると思いますが、今回はより利用者を絞りたかったので特定のGoogleグループのメンバーのみを許可する機能が欲しくなりました。
ということで、Google OAuth2でログインしたユーザーが特定のGoogleグループに所属しているかどうかを判断する処理を実装しました。

使用するAPI

Googleグループを操作できるAPIを調べた所、次の2つが出てきました。

  • Directory API (Admin console)
  • Groups API (Cloud Identity)

今回使ったのは後者のCloud Identity Groups APIです。ログインしたユーザーの立場から該当のグループのメンバーかどうかだけが判断出来れば良いですので、管理者権限でグループを操作するDirectory APIの権限は過剰になります。また、Directory APIは使用する権限を得るのにGoogleの管理者権限が必要ですので、個人ならともかく組織では使用許可を取るのが面倒くさいです。

実際にやってみた

主にこちらの公式ドキュメントを参考に実装してみました。
https://cloud.google.com/identity/docs/how-to/query-memberships?hl=ja

前提

PythonのアプリでGoogle OAuth2の認証が実装できており、ログインしたユーザーの以下の情報が取得でできていることとします[1]

  • ログインしたユーザーのメールアドレス
  • ログインしたユーザーのアクセストークン

Cloud Identityを使えるようにする

Cloud Identity APIを有効化する

Google CloudのコンソールからCloud Identity APIを有効化してください。有効化するためのリンクを貼っておきます。
https://console.cloud.google.com/flows/enableapi?apiid=cloudidentity.googleapis.com&hl=ja

OAuthのScopeを追加する

Google CloudのコンソールのOAuth同意画面から、スコープにcloud-identity.groups.readonlyを追加してください[2]

https://console.cloud.google.com/apis/credentials/consent

コードを書く

必要なライブラリをインストールします。

pip install google-api-python-client google-auth

コードを書きます。先程貼った公式ドキュメントのほぼコピペで書けました。

from urllib.parse import urlencode
import google.oauth2.credentials
import googleapiclient.discovery

def check_user_group_membership(group_id: str, mail: str, access_token: str) -> bool:
    """
    指定したユーザーが指定したグループに所属しているかどうかを確認する
    :param group_id: グループID
    :param mail: メールアドレス
    :param access_token: アクセストークン
    :return: 所属しているかどうか(True or False)
    """
    try:
        # 認証情報を作成
        credentials = google.oauth2.credentials.Credentials(
            access_token,
        )
        service = googleapiclient.discovery.build(
            'cloudidentity', 'v1', credentials=credentials
        )
        query_params: str = urlencode(
            {"query": f"member_key_id == '{mail}'"}
        )

        request = service.groups().memberships().checkTransitiveMembership(
            parent=f"groups/{group_id}",
            query=query_params,
        )
        request.uri += "&" + query_params
        response = request.execute()
        return response['hasMembership']
    except Exception as e:
        return False

グループIDを調べる

前述のコードには唐突にグループIDが出てきています。これはGoogleグループのメールアドレスではなく、Googleグループに振られた固有IDになります。通常は表に出て来ませんので、調べる必要があります。

グループIDの調べ方を調べた所、次のコマンドで取得できることが分かりました。

gcloud identity groups describe (groupのメールアドレス) --project=(GoogleCloudのプロジェクトID)

コマンドで取得できたので試していませんが、pythonのコードでメールアドレスからGroupIDを取得することも出来るはずです。それっぽい記事を見つけたので貼っておきます。
https://zenn.dev/jcc/articles/0ef23e881cacb4#ラッパーを作る

GroupIDが分かれば、先程貼った関数にグループのGroupID、ユーザーのメールアドレスとアクセストークンを投げ込めば、そのグループに所属しているかどうかの判別が可能です。所属してなければ以降の処理を拒否し、ログアウトさせれば良いということになります。

以上です。

脚注
  1. 最初に参考として貼った記事の内容が実装されていれば取得できているはずです。 ↩︎

  2. 何故かAdmin APIの方はgroupで、Identity APIはgroupsです。なぞです。 ↩︎

NCDCエンジニアブログ

Discussion