I'm a beginner implementing Google OAuth2 login in a React frontend with FastAPI as my backend. However, I'm receiving the following error when exchanging the authorization code for tokens:
Token data received from Google: {'error': 'invalid_grant', 'error_description': 'Malformed auth code.'}
My backend handles the authorization code exchange as follows:
from fastapi import FastAPI, HTTPException
import requests as req
import os
import jwt
from urllib.parse import unquote
from google.auth.transport import requests
from google.oauth2 import id_token
app = FastAPI()
@app.post("/auth/google")
async def google_auth(data: TokenData):
try:
unquoted_token = unquote(data.token) # Decode the token if needed
print("Received Authorization Code (Backend):", unquoted_token) # Debugging
token_request_payload = {
"client_id": GOOGLE_CLIENT_ID,
"client_secret": os.getenv("GOOGLE_CLIENT_SECRET"),
"code": unquoted_token,
"grant_type": "authorization_code",
"redirect_uri": "http://127.0.0.1:5173", # Matches frontend setup
}
response = req.post(";, data=token_request_payload)
token_data = response.json()
logger.info("Token data received from Google: %s", token_data)
if "id_token" in token_data:
user_info = id_token.verify_oauth2_token(token_data["id_token"], requests.Request(), GOOGLE_CLIENT_ID)
email = user_info["email"]
else:
raise HTTPException(status_code=400, detail="No ID token received from Google")
return {"message": "User authenticated", "token": session_token, "user": user_info}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
Frontend
import { GoogleLogin } from "@react-oauth/google";
const Login = () => {
const handleLoginSuccess = async (response: any) => {
const credential = response.credential;
console.log("Raw authorization code", credential);
try {
const res = await fetch("http://127.0.0.1:8000/auth/google", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: credential.trim() }),
});
const data = await res.json();
console.log("Backend Response:", data);
} catch (error) {
console.error("Error sending token to backend:", error);
}
};
return (
<GoogleLogin
onSuccess={handleLoginSuccess}
onError={() => console.log("Login Failed")}
useOneTap
access_type="offline"
prompt="consent"
response_type="code" // Requesting an authorization code
scope="openid email profile"
/>
);
};
export default Login;
Debugging Steps Taken
Confirmed the authorization code is received: The frontend prints a long authorization code, and the backend logs show it is being received.
Checked the redirect URI: It matches in both frontend setup and backend request (http://127.0.0.1:5173).
Verified the client ID and secret: They are correctly set in the backend.
Used unquote to decode the token: No change, still getting invalid_grant error.
Tried response_type="code" in @react-oauth/google: Still receiving the error.
Questions:
- Why is Google returning invalid_grant with Malformed auth code.?
- Is @react-oauth/google providing an ID token instead of an authorization code despite setting response_type="code"?
- How can I correctly exchange the code for an access token in FastAPI? Any guidance would be appreciated!