AWS LambdaでGoogle認証

背景や目的


ログインの安全性は欠かせないセキュリティ要素です。

私たちのチームは、以前はIP制限でアクセスを管理していましたが、今回は本番のRAG型チャットボットシステムに組み込むため、Google認証(GoogleAuth)によるログイン画面の実装に挑戦しました。

本記事では、Google認証を組み込むための実装手順を紹介します。

実現したい事


アプリとしては、AI社内チャットボットです。

下記ログイン画面をGoogleAuthで実装します。

構築手順サマリー


手順のサマリーとしては以下の通りです。

1.Google Cloud プロジェクトの作成
2.OAuthクライアントIDの作成と設定
3.Lambda関数に認証処理を追加

順番に紹介してゆきます。

構築1)Google Cloud プロジェクトの作成


Google認証を利用するため、まずGoogle Cloudでプロジェクトを作成します。
※プロジェクト:Google Cloudでリソースや設定をまとめて管理する単位です。

Google Cloud Console にアクセスし、Googleアカウントでサインインします。

・サインイン後、「Console」ボタンをクリックするとコンソールのメイン画面が表示されます。

・他の何かの案件で既にプロジェクトが存在する場合は、それを選択します。
上部GoogleCloudの右側「プロジェクト選択」→「プロジェクト名」をクリックします。

・新プロジェクトを作成したい場合は「新しいプロジェクト」をクリックしてください。

・プロジェクト名を入力し、組織を選択します。最後に「作成」を押したら作成完了です。

次は作成したプロジェクトに紐づく形で、OAuthクライアントIDを設定していきます。
※OAuthクライアントID:Googleログインを使うための認証IDです。

構築2)OAuthクライアントIDの作成と設定


Google認証を利用するために、Webアプリ用のOAuthクライアントIDとクライアントシークレットを作成します。
※クライアントシークレット:OAuthクライアントIDと対になるパスワードのような情報です。

・Google Cloudのメイン画面で「APIとサービス」をクリックし、そこへ遷移します。

・「APIとサービス」画面で、左メニューの「認証情報」をクリックして進みます。

・その画面が表示されたら、「+認証情報を作成」をクリックして認証情報を作成します。

・「+認証情報を作成」を押した後の選択肢から、「OAuthクライアントID」を選択します。

・表示された画面で、アプリケーションの種類は「ウェブアプリケーション」を選択します。

・選択後に表示される「名前」欄に、任意のクライアント名を入力します。※お好きな名称で

・次に、「承認済みのJavaScript生成元」と「承認済みのリダイレクトURI」を設定します。
これは、Googleログイン後にユーザーがリダイレクトされるURLや、JavaScriptが実行される元のページのURLを登録するものです。

・「+URIを追加」を押下、以下の表値を入力します。

設定項目
承認済みのJavaScript生成元→https://****.lambda-url.ap-northeast-1.on.aws  
※関数URLの最後の「/」は付けずに、ドメインまでを指定します。
承認済みのリダイレクトURI→https://****.lambda-url.ap-northeast-1.on.aws/  
※関数URLそのまま

※AWS Lambdaを使用している場合は、関数URLを指定します。

・入力が完了したら、最後に「作成」を押すと、作成完了画面が表示されます。

・表示された「クライアントID」と「クライアントシークレット」は、後でコードに組み込むため控えておきます。
これでOAuthクライアントIDの作成は完了です。

続いて、この情報を使用してAWSLambdaのコードに組み込んでいきます。

構築3)Lambda関数に認証処理を追加


先ほど作成したクライアントIDをLambda関数に組み込んでみます。

・Lambda関数に認証処理を追加するため、必要なコードを追加・編集していきます。

・まず、Lambda関数をまだ作成していない場合は、以下の手順で作成してください。
  - AWSコンソールでLambdaメニューに遷移し、トップ画面で「関数の作成」を押します。
   (※AWSアカウントの作成など基本設定はここでは割愛します)
  - 画面「関数の作成」では、以下の設定値で、作成します。

設定項目
関数名chatbot_login_trial ※お好きな名称で
ランタイムpython3.11
アーキテクチャx86_64(デフォルト)
実行ロールlambda_role_for_user ※お手元の環境で任意に設定
関数 URL を有効化チェック
認証タイプNONE

・続いて、Google認証を組み込んだLambda関数の中身となるlambda_function.pyのコードを記述します。
以下はそのコードで、ログイン画面とチャット画面を切り替える処理を行います。

lambda_function.py
import json
import urllib.parse
import urllib.request
import base64
import uuid

CLIENT_ID = "****"
CLIENT_SECRET = "****"
REDIRECT_URI = "https://****.lambda-url.ap-northeast-1.on.aws/"
COOKIE_KEY = "admin_session"
GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"

def get_google_token(code):
    payload = {
        "code": code,
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "redirect_uri": REDIRECT_URI,
        "grant_type": "authorization_code",
    }
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data = urllib.parse.urlencode(payload).encode("utf-8")
    req = urllib.request.Request(GOOGLE_TOKEN_URL, data=data, headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req) as response:
            return json.loads(response.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        print("Token Error:", e.read().decode())
        return None

def decode_jwt(jwt_token):
    try:
        parts = jwt_token.split(".")
        base64_url = parts[1]
        padded = base64_url + "=" * ((4 - len(base64_url) % 4) % 4)
        decoded = base64.urlsafe_b64decode(padded).decode("utf-8")
        return json.loads(decoded)
    except Exception as e:
        print("JWT Decode Error:", e)
        return {}

def get_html_template(filename):
    with open(filename, "r", encoding="utf-8") as f:
        return f.read()

def lambda_handler(event, context):
    path = event.get("rawPath", "/")
    headers = {"Content-Type": "text/html"}

    # 1. 認証コールバック処理
    if path == "/callback":
        query_params = event.get("queryStringParameters", {}) or {}
        code = query_params.get("code")
        if not code:
            return {"statusCode": 400, "body": "No code provided"}

        token_data = get_google_token(code)
        if not token_data or "id_token" not in token_data:
            return {"statusCode": 400, "body": "Token exchange failed"}

        user_info = decode_jwt(token_data["id_token"])
        user_email = user_info.get("email", "")
        print("Google Login Success:", user_email)

        session_id = str(uuid.uuid4())
        cookie_header = f"{COOKIE_KEY}={session_id}; Secure; Path=/;"
        return {
            "statusCode": 302,
            "headers": {
                "Set-Cookie": cookie_header,
                "Location": "/"
            },
            "body": ""
        }

    # 2. HTML表示処理(login or chat)
    cookies = event.get("headers", {}).get("cookie", "")
    admin_cookie = None
    for cookie in cookies.split(";"):
        if cookie.strip().startswith(f"{COOKIE_KEY}="):
            admin_cookie = cookie.strip().split("=")[1]
            break

    page_template = "chat.html" if admin_cookie else "login.html"
    return {
        "statusCode": 200,
        "headers": headers,
        "body": get_html_template(page_template)
    }

・上のコードが参照するログイン画面(login.html)のHTMLコードは以下の通りです。
 重要なのはGoogle認証URL部分です。

HTML (login.html)
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>ログイン</title>
  <style>
    body {
      font-family: sans-serif;
      background-color: #ffb40d;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      margin: 0;
    }
    .login-container {
      background: white;
      padding: 30px;
      border-radius: 8px;
      text-align: center;
      box-shadow: 0 2px 5px rgba(0,0,0,0.1);
      width: 320px;
    }
    #google-login-btn {
      background-color: #00a9e4;
      color: white;
      border: none;
      padding: 10px;
      font-size: 16px;
      font-weight: bold;
      border-radius: 5px;
      cursor: pointer;
      width: 100%;
    }
    #google-login-btn:hover {
      background-color: #357ae8;
    }
  </style>
</head>
<body>
<div class="login-container">
    <h2>ログイン画面</h2>
    <button id="google-login-btn">
        Googleでログイン
    </button>
</div>

<script type="text/javascript">
    //Google認証URL ★★
    document.getElementById('google-login-btn').addEventListener('click', function() {
        const googleAuthUrl = "https://accounts.google.com/o/oauth2/v2/auth"
            + "?client_id=****.apps.googleusercontent.com" //先ほど作成したOAuthクライアントID
            + "&redirect_uri=https://****.lambda-url.ap-northeast-1.on.aws/"
            + "&response_type=code"
            + "&scope=openid%20email%20profile"
            + "&state=random_state_string"
            + "&prompt=consent";
        window.location.href = googleAuthUrl;
    });
</script>

</body>
</html>

Google認証URLパラメータの詳細は下表の通りです。

パラメータ説明
client_id先ほどGoogle Cloudで発行しOAuthクライアントIDのことです。
その値(**.apps.googleusercontent.com)を指定します。
redirect_uriログイン後にGoogleが戻ってくるURLです。Lambda関数のURLを指定します。
response_typeGoogle認証後に認可コード(code)を受け取るための方式を指定する項目です。「code」を選択します。
scopeアプリで取得したいユーザー情報の種類を示します。(メール・名前など)
「openid%20email%20profile」を指定します。
%20はURL内で使えないスペースを表す記号(エンコード)です。
stateセキュリティのために使う確認用のランダムな文字列です。「random_state_string」を指定します。
prompt毎回同意画面を表示させたい場合に使用します。「consent」を指定します。

これで、Lambda関数へのGoogle認証処理の組み込みが完了しました。関数をデプロイして、関数URLをメモして終了です。

試用してみましょう!


画面は出来たので早速テストをしてみます。

・Lambda関数URLをアクセスした時は以下の画面が表示。

・「Googleでログイン」ボタン押下時GoogleAuthの画面が表示された。

・そこでメールIDとパスワードを入力してログイン成功したらチャット画面が表示された。

成功です!

まとめ


Google認証の組み込みに成功し、チャット画面のログインセキュリティを強化できました。初めての実装でしたが、多くを学び、無事に動作して嬉しく思います。

当初、コード内で「prompt」を指定しなかったため、ログイン済みのブラウザでアカウント選択画面が表示されず、自動ログインされ驚きました。必要に応じて設定をしてください。

今後もこの仕組みを活用していきたいと考えています。アドバイスなどありましたら、ぜひ以下のご連絡フォームからお知らせいただけると嬉しいです。

ಮುಂದಿನ ಲೇಖನಕ್ಕಾಗಿ ನಿರೀಕ್ಷಿಸಿ
(次の記事をお楽しみに)

ご連絡フォーム


フィードバックを是非お願いします。
本記事の方法での問題点や、よりよい方法のアイデアを頂けると大変助かります。

この記事に関して

その他のご連絡

DevAIsをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む