Googleでログイン 実践ガイド - gosaaan1/hokulea-garage GitHub Wiki
「Googleでログイン」は、ウェブサイトやアプリケーションがユーザーに対して、Googleアカウントを使用してログインする機能を提供するサービスです。これにより、ユーザーは新しいアカウントを作成する必要なく、既存のGoogleアカウントを使用して簡単かつ安全にサービスにアクセスできます。
この機能は、開発者にとっては認証プロセスの実装を簡素化し、ユーザーにとってはパスワードの管理を減らすことができるため、双方にとって利便性が高いソリューションとなっています。
引用元: https://developers.google.com/identity/sign-in/web
GoogleでログインはOAuth 2.0プロトコルとOpenID Connect(OIDC)を基盤としています。これらの技術の比較は以下の通りです:
-
OAuth 2.0:
- 主に認可(Authorization)のためのプロトコル
- リソースへのアクセス権を付与する
- ユーザーの身元確認は行わない
-
OpenID Connect:
- OAuth 2.0を拡張した認証(Authentication)のためのプロトコル
- ユーザーの身元確認を行う
- OAuth 2.0の上に構築され、IDトークンという概念を導入
Google OpenID Connectは、これらの技術を組み合わせて使用しています:
- OAuth 2.0を使用してアプリケーションにGoogleリソースへのアクセス権を付与
- OIDCを使用してユーザーの身元を確認し、ユーザー情報を取得
この組み合わせにより、「Googleでログイン」は単なるアクセス権の付与だけでなく、ユーザーの身元確認も同時に行うことができます。
引用元:
- OAuth 2.0: https://oauth.net/2/
- OpenID Connect: https://openid.net/connect/
「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. データ表示
- ユーザーがフロントエンドで「Googleでログイン」ボタンをクリックします。
- フロントエンドがGoogleに直接認証リクエストを送信します。
- Googleがユーザーにログイン画面を表示します。
- ユーザーがGoogleアカウントでログインします。
- Googleがユーザーに同意画面を表示します。
- ユーザーがアプリケーションへのアクセス権限を承認します。
- Googleがフロントエンドに認可コードを送信します。
- フロントエンドが受け取った認可コードをバックエンド(API)に転送します。
- APIが認可コードを使用してGoogleにトークンリクエストを送信します。
- GoogleがAPIにアクセストークンとIDトークンを送信します。
- APIがアクセストークンを使用してGoogleにユーザー情報リクエストを送信します。
- GoogleがAPIにユーザー情報を送信します。
- APIがJWTトークンを生成します。
- APIがフロントエンドにJWTトークンを送信します。
- フロントエンドがログイン完了をユーザーに通知し、サービスを提供します。
- ユーザーが保護されたリソースにアクセスを試みます。
- フロントエンドは、リクエストヘッダーにJWTトークンを付加してAPIにリクエストを送信します。
- APIはJWTトークンを検証し、ユーザーの認証を確認します。
- 認証が成功した場合、APIは要求された保護されたデータをフロントエンドに送信します。
- フロントエンドは受け取ったデータをユーザーに表示します。
引用元: 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.