Googleでログイン 実践ガイド - gosaaan1/hokulea-garage GitHub Wiki

Googleでログイン実装ガイド

目次

  1. Googleでログインとは
  2. OAuthとGoogle OpenID Connectとの比較
  3. 認証の流れ
  4. サンプルコード

Googleでログインとは

「Googleでログイン」は、ウェブサイトやアプリケーションがユーザーに対して、Googleアカウントを使用してログインする機能を提供するサービスです。これにより、ユーザーは新しいアカウントを作成する必要なく、既存のGoogleアカウントを使用して簡単かつ安全にサービスにアクセスできます。

この機能は、開発者にとっては認証プロセスの実装を簡素化し、ユーザーにとってはパスワードの管理を減らすことができるため、双方にとって利便性が高いソリューションとなっています。

引用元: https://developers.google.com/identity/sign-in/web

OAuthとGoogle OpenID Connectとの比較

GoogleでログインはOAuth 2.0プロトコルとOpenID Connect(OIDC)を基盤としています。これらの技術の比較は以下の通りです:

  1. OAuth 2.0:

    • 主に認可(Authorization)のためのプロトコル
    • リソースへのアクセス権を付与する
    • ユーザーの身元確認は行わない
  2. OpenID Connect:

    • OAuth 2.0を拡張した認証(Authentication)のためのプロトコル
    • ユーザーの身元確認を行う
    • OAuth 2.0の上に構築され、IDトークンという概念を導入

Google OpenID Connectは、これらの技術を組み合わせて使用しています:

  • OAuth 2.0を使用してアプリケーションにGoogleリソースへのアクセス権を付与
  • OIDCを使用してユーザーの身元を確認し、ユーザー情報を取得

この組み合わせにより、「Googleでログイン」は単なるアクセス権の付与だけでなく、ユーザーの身元確認も同時に行うことができます。

引用元:

認証の流れ

「Googleでログイン」の認証フローは以下の通りです:

sequenceDiagram
    participant User
    participant Frontend
    participant API
    participant Google

    User->>Frontend: 1. Googleでログインをクリック
    Frontend->>Google: 2. 認証リクエスト
    Google->>User: 3. ログイン画面表示
    User->>Google: 4. Googleアカウントでログイン
    Google->>User: 5. 同意画面表示
    User->>Google: 6. アクセス権限を承認
    Google->>Frontend: 7. 認可コード送信
    Frontend->>API: 8. 認可コード転送
    API->>Google: 9. トークンリクエスト(認可コード使用)
    Google->>API: 10. アクセストークン・IDトークン送信
    API->>Google: 11. ユーザー情報リクエスト(アクセストークン使用)
    Google->>API: 12. ユーザー情報送信
    API->>API: 13. JWTトークン生成
    API->>Frontend: 14. JWTトークン送信
    Frontend->>User: 15. ログイン完了・サービス提供

    Note over User,Frontend: 保護されたAPIへのアクセス

    User->>Frontend: 16. 保護されたリソースにアクセス
    Frontend->>API: 17. リクエスト(JWTトークン付加)
    API->>API: 18. JWTトークン検証
    API->>Frontend: 19. 保護されたデータ送信
    Frontend->>User: 20. データ表示
Loading
  1. ユーザーがフロントエンドで「Googleでログイン」ボタンをクリックします。
  2. フロントエンドがGoogleに直接認証リクエストを送信します。
  3. Googleがユーザーにログイン画面を表示します。
  4. ユーザーがGoogleアカウントでログインします。
  5. Googleがユーザーに同意画面を表示します。
  6. ユーザーがアプリケーションへのアクセス権限を承認します。
  7. Googleがフロントエンドに認可コードを送信します。
  8. フロントエンドが受け取った認可コードをバックエンド(API)に転送します。
  9. APIが認可コードを使用してGoogleにトークンリクエストを送信します。
  10. GoogleがAPIにアクセストークンとIDトークンを送信します。
  11. APIがアクセストークンを使用してGoogleにユーザー情報リクエストを送信します。
  12. GoogleがAPIにユーザー情報を送信します。
  13. APIがJWTトークンを生成します。
  14. APIがフロントエンドにJWTトークンを送信します。
  15. フロントエンドがログイン完了をユーザーに通知し、サービスを提供します。
  16. ユーザーが保護されたリソースにアクセスを試みます。
  17. フロントエンドは、リクエストヘッダーにJWTトークンを付加してAPIにリクエストを送信します。
  18. APIはJWTトークンを検証し、ユーザーの認証を確認します。
  19. 認証が成功した場合、APIは要求された保護されたデータをフロントエンドに送信します。
  20. フロントエンドは受け取ったデータをユーザーに表示します。

引用元: https://developers.google.com/identity/protocols/oauth2/openid-connect

サンプルコード

フロントエンド

vue3-google-loginパッケージを使用したVue 3でのGoogleログインのサンプルコードを以下に示します。

<template>
  <div>
    <h1>Google Login Demo</h1>
    <GoogleLogin :callback="callback" />
    <div v-if="userData">
      <h2>User Data:</h2>
      <pre>{{ userData }}</pre>
    </div>
    <button v-if="isLoggedIn" @click="fetchProtectedData">Fetch Protected Data</button>
    <div v-if="protectedData">
      <h2>Protected Data:</h2>
      <pre>{{ protectedData }}</pre>
    </div>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
import { GoogleLogin } from 'vue3-google-login'

export default {
  name: 'App',
  components: {
    GoogleLogin
  },
  setup() {
    const userData = ref(null)
    const jwtToken = ref(null)
    const protectedData = ref(null)

    const isLoggedIn = computed(() => !!jwtToken.value)

    const callback = async (response) => {
      try {
        const res = await fetch('http://your-backend-api/auth/google', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ code: response.code }),
        })
        const data = await res.json()
        jwtToken.value = data.access_token
        userData.value = data.user_info
      } catch (error) {
        console.error('Error:', error)
      }
    }

    const fetchProtectedData = async () => {
      try {
        const res = await fetch('http://your-backend-api/protected', {
          headers: {
            'Authorization': `Bearer ${jwtToken.value}`
          }
        })
        protectedData.value = await res.json()
      } catch (error) {
        console.error('Error fetching protected data:', error)
      }
    }

    return { 
      userData, 
      protectedData, 
      isLoggedIn, 
      callback, 
      fetchProtectedData 
    }
  }
}
</script>

このコードを使用するには、まずvue3-google-loginパッケージをインストールする必要があります:

npm install vue3-google-login

そして、Vueアプリケーションのメインファイル(通常はmain.js)で以下のように設定します:

import { createApp } from 'vue'
import App from './App.vue'
import vue3GoogleLogin from 'vue3-google-login'

const app = createApp(App)

app.use(vue3GoogleLogin, {
  clientId: 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com'
})

app.mount('#app')

引用元: https://github.com/yobaji/vue3-google-login

バックエンド

FastAPIを使用したバックエンドのサンプルコードを以下に示します。このコードには、JWTを使用した認証と保護されたAPIエンドポイントが含まれています。

from fastapi import FastAPI, HTTPException, Depends, Security
from fastapi.security import OAuth2AuthorizationCodeBearer, SecurityScopes
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from google.oauth2 import id_token
from google.auth.transport import requests
import google.auth.transport.requests
import google.oauth2.credentials
from jose import JWTError, jwt
from datetime import datetime, timedelta
import os

app = FastAPI()

# CORSミドルウェアを追加
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 本番環境では適切に設定してください
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 環境変数から秘密鍵を取得(本番環境では適切に管理してください)
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

CLIENT_ID = "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"
CLIENT_SECRET = "YOUR_GOOGLE_CLIENT_SECRET"

oauth2_scheme = OAuth2AuthorizationCodeBearer(
    authorizationUrl="authorize",
    tokenUrl="token",
)

class GoogleAuthRequest(BaseModel):
    code: str

class Token(BaseModel):
    access_token: str
    token_type: str

class UserInfo(BaseModel):
    email: str
    name: str
    picture: str

def create_access_token(data: dict, expires_delta: timedelta = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

async def get_current_user(security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)):
    if security_scopes.scopes:
        authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
    else:
        authenticate_value = "Bearer"
    credentials_exception = HTTPException(
        status_code=401,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": authenticate_value},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        email: str = payload.get("sub")
        if email is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    return email

@app.post("/auth/google", response_model=Token)
async def google_auth(request: GoogleAuthRequest):
    try:
        # 認証コードを使ってトークンを取得
        flow = google.auth.transport.requests.Request()
        credentials = google.oauth2.credentials.Credentials.from_authorization_code(
            request.code,
            client_id=CLIENT_ID,
            client_secret=CLIENT_SECRET,
            scopes=['https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile'],
            redirect_uri='postmessage'
        )

        # IDトークンを検証し、ユーザー情報を取得
        id_info = id_token.verify_oauth2_token(
            credentials.id_token, requests.Request(), CLIENT_ID)

        # JWTトークンを生成
        access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
        access_token = create_access_token(
            data={"sub": id_info["email"]}, expires_delta=access_token_expires
        )
        return {"access_token": access_token, "token_type": "bearer"}

    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.get("/protected", response_model=UserInfo)
async def protected_route(current_user: str = Depends(get_current_user)):
    # この関数は認証されたユーザーのみがアクセスできます
    # 実際のアプリケーションでは、ここでユーザー情報をデータベースから取得するなどの処理を行います
    return UserInfo(email=current_user, name="Sample User", picture="https://example.com/sample.jpg")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.
⚠️ **GitHub.com Fallback** ⚠️