Google Auth & OAuth2
Most applications need user authentication. Google OAuth2 lets users sign in with their Google account, saving them from creating yet another password. Service accounts provide machine-to-machine authentication for accessing Google APIs (Drive, Sheets, Cloud Storage) programmatically.
OAuth2 Flow
OAuth2 allows your app to access a user's Google data without ever seeing their password:
User Your App Google
| | |
| Click | |
| "Sign in | |
| with | |
| Google" | |
|------------>| |
| | Redirect to |
| | Google login |
| |-------------->|
| | |
| User logs in & authorizes |
| |<--------------|
| | Auth code |
| | |
| | Exchange code |
| | for tokens |
| |-------------->|
| | Access token |
| | + ID token |
| |<--------------|
| | |
| User | |
| logged in | |
|<------------| |
Setting Up Google OAuth2
1. Create OAuth Credentials
- Go to Google Cloud Console
- Create a new project or select an existing one
- Navigate to APIs & Services → Credentials
- Click Create Credentials → OAuth client ID
- Configure:
- Application type: Web application
- Authorized JavaScript origins:
http://localhost:3000 - Authorized redirect URIs:
http://localhost:3000/auth/callback
- Note the Client ID and Client Secret
2. Implement OAuth in FastAPI
# auth.py
from fastapi import FastAPI, HTTPException, Depends
from fastapi.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
from authlib.integrations.starlette_client import OAuth
import os
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=os.urandom(32).hex())
oauth = OAuth()
oauth.register(
name="google",
client_id=os.environ["GOOGLE_CLIENT_ID"],
client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
client_kwargs={"scope": "openid email profile"},
)
@app.get("/auth/login")
async def login(request):
redirect_uri = request.url_for("auth_callback")
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.get("/auth/callback")
async def auth_callback(request):
token = await oauth.google.authorize_access_token(request)
user = token.get("userinfo")
if not user:
raise HTTPException(status_code=400, detail="Authentication failed")
# Store user info in session or create a JWT
return {
"email": user["email"],
"name": user["name"],
"picture": user["picture"],
}
@app.get("/auth/logout")
async def logout(request):
request.session.pop("user", None)
return RedirectResponse(url="/")
Never hardcode GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET in your code. Store them in .env locally and configure them as environment variables in your deployment platform.
Service Accounts
Service accounts are for server-to-server communication. They let your application access Google APIs without user interaction.
Creating a Service Account
- In Google Cloud Console, go to IAM & Admin → Service Accounts
- Click Create Service Account
- Give it a name and description
- Grant the necessary roles (e.g., "Storage Object Viewer" for reading GCS)
- Click Create Key → JSON → Download the key file
Using Service Accounts
# service_account.py
from google.oauth2 import service_account
from google.cloud import storage
import json
import os
# Load credentials from environment variable (recommended)
credentials_info = json.loads(os.environ["GOOGLE_SERVICE_ACCOUNT_JSON"])
credentials = service_account.Credentials.from_service_account_info(
credentials_info,
scopes=["https://www.googleapis.com/auth/cloud-platform"],
)
# Use credentials with a Google Cloud client
storage_client = storage.Client(credentials=credentials)
bucket = storage_client.bucket("my-data-bucket")
blob = bucket.blob("data/latest_model.pkl")
blob.download_to_filename("model.pkl")
print("Model downloaded successfully")
Accessing Google Sheets
import gspread
# Authenticate with service account
gc = gspread.service_account_from_dict(credentials_info)
# Open a spreadsheet by key
spreadsheet = gc.open_by_key("1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms")
# Read data from the first worksheet
worksheet = spreadsheet.sheet1
records = worksheet.get_all_records()
print(f"Found {len(records)} rows")
for row in records[:3]:
print(row)
Treat service account JSON keys like passwords. Never commit them to Git. In production, use Workload Identity (GKE) or Application Default Credentials instead of key files. On your local machine, set GOOGLE_APPLICATION_CREDENTIALS as an environment variable pointing to the key file path.
Token Validation
For API endpoints that require authentication, validate the Google ID token:
from google.oauth2 import id_token
from google.auth.transport import requests as google_requests
async def get_current_user(authorization: str = Header(...)):
"""Validate Google ID token from Authorization header."""
try:
token = authorization.replace("Bearer ", "")
idinfo = id_token.verify_oauth2_token(
token,
google_requests.Request(),
os.environ["GOOGLE_CLIENT_ID"],
)
return {
"email": idinfo["email"],
"name": idinfo["name"],
"sub": idinfo["sub"],
}
except ValueError:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/api/me")
async def get_me(user: dict = Depends(get_current_user)):
return user