Implementing Google Login with JavaScript and FastAPI
This article is based on a recent implementation of Google Auth for one of my projects. See in action here: https://zima.farawaytech.com/.
The implementation uses plain JavaScript on the frontend and FastAPI on the backend. It uses modern Google Identity Services library to handle user authentication on the frontend, as the previous library was deprecated some time ago. On the backend, FastAPI verifies the ID token and issues JWT authentication cookie to manage the session.
Frontend Setup
Add Google Identity Services to Your Project
Include the Google Identity Services JavaScript library in your HTML file by adding the following script tag:
<script src="https://www.gstatic.com/identity/one-tap/js/sdk.js"></script>
Create a Login Button
Add a login button to your HTML file:
<div id="g_id_onload"
data-client_id="YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"
data-context="signin"
data-ux_mode="popup"
data-callback="handleGoogleUserSignIn"
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-logo_alignment="center"
data-width="342">
</div>
Replace YOUR_GOOGLE_CLIENT_ID
with your actual Google client ID. The handleGoogleUserSignIn
function will be called when the user successfully signs in with Google. Full reference of all available parameter can be found here: https://developers.google.com/identity/gsi/web/reference/html-reference
Send ID Token to Backend for Verification
Add handler for authentication response from Google: on success it will send the ID token to the FastAPI backend for verification and authentication, and then set the authentication cookie:
function decodeJwtResponse(token) {
let base64Url = token.split('.')[1];
let base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
let jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
function handleGoogleUserSignIn(response) {
const id_token = response.credential;
// Send the id_token to your server for verification and authentication
// Handle successful sign-in as needed
const responsePayload = decodeJwtResponse(response.credential);
console.log("ID: " + responsePayload.sub);
console.log("Image URL: " + responsePayload.picture);
console.log("Email: " + responsePayload.email);
// Send the ID token to your backend API for verification and authentication
fetch('https://your-backend-api.example.com/auth/google', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({id_token: id_token}),
credentials: 'include' // Include this line to send and receive cookies
}).then(response => response.json())
.then(data => {
if (data.status === 'success') {
// The authentication was successful, and the cookie has been set
console.log('Authentication successful');
} else {
// There was an error during the authentication process
console.log('Authentication error:', data.message);
}
}).catch(error => {
console.error('Error:', error);
});
}
Replace https://your-backend-api.example.com/auth/google
with your FastAPI backend's endpoint for handling Google authentication.
Backend Setup
Install Required Libraries
Install the required libraries for your FastAPI backend:
pip install fastapi[all] pyjwt google-auth google-auth-oauthlib
Configure CORS in FastAPI
Don't forget to add CORS middleware to your FastAPI application to allow credentials:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:8080"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Create an Endpoint for Google Authentication
Create a FastAPI endpoint to handle Google authentication and verify the ID token:
from fastapi import FastAPI, Request, Response, Depends
from google.oauth2 import id_token
from google.auth.transport import requests
from pydantic import BaseModel
import jwt
app = FastAPI()
GOOGLE_CLIENT_ID = "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"
COOKIE_SECRET_KEY = "SECRET_KEY" # read from env
class Token(BaseModel):
id_token: str
@app.post("/auth/google")
async def authenticate_google_user(token: Token, response: Response):
try:
# Verify the ID token
idinfo = id_token.verify_oauth2_token(token.id_token, requests.Request(), GOOGLE_CLIENT_ID)
# ID token is valid
user_id = idinfo["sub"]
# Issue an authentication cookie
auth_cookie = jwt.encode({"user_id": user_id}, COOKIE_SECRET_KEY, algorithm="HS256")
response.set_cookie(key="auth_cookie", value=auth_cookie)
return {"status": "success"}
except ValueError as e:
# Invalid ID token
return {"status": "error", "message": str(e)}
Replace YOUR_GOOGLE_CLIENT_ID
string with your actual Google client ID and COOKIE_SECRET_KEY
with your own secret key for encoding the JWT authentication cookie.
Fetch User Data in the Backend
Now let's see how to use the authentication cookie we set to access private user data:
from fastapi import Cookie
async def get_user_data(auth_cookie: Optional[str] = Cookie(None)):
if auth_cookie:
try:
payload = jwt.decode(auth_cookie, COOKIE_SECRET_KEY, algorithms=["HS256"])
user_id = payload.get("user_id")
# Fetch user data from your database or other data sources using the user_id
return {"status": "success", "data": "user data"}
except jwt.exceptions.InvalidTokenError:
return {"status": "error", "message": "Invalid token"}
else:
return {"status": "error", "message": "No token provided"}
@app.get("/user-data")
async def user_data_route(user_data: dict = Depends(get_user_data)):
return user_data
Replace COOKIE_SECRET_KEY
with the secret key used to encode the JWT authentication cookie.
Fetch User Data on the Frontend
Use fetch()
to send a request to the backend to fetch user data:
function fetchUserData() {
fetch('https://your-backend-api.example.com/user/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(response => response.json())
.then(data => {
if (data.status !== "error") {
console.log('User data:', data);
} else {
console.log('Error fetching user data:', data.message);
}
})
.catch(error => {
console.error('Error:', error);
});
}
Now we have a complete setup for implementing Google authentication with plain JavaScript on the frontend and FastAPI on the backend. Users can sign in with their Google accounts, and you can fetch their data from the backend using JWT authentication cookies.