I am building a ReactJS application that uses Google Workspace and a Python backend for logging in users.
The flow is as following
useEffect()
loads a Google sign in button- On frontend the users click "Sign in with Google"
- The frontend calls the function handleCallbackResponse() which prints the Google token.
- The
handleCallbackResponse()
should send the Google token to the Python backend. The function needs to wait until a response from backend is given. The call to backend happens inloginBackend
. - My problem is that the variable
const backendToken
is set to "[object Promise]". However I need it to be set to the response from the backend.
I created a drawing of the flow:
index.html
I added Google client script
<script src="https://accounts.google.com/gsi/client" async defer></script>
App.js
import React from 'react';
import { BrowserRouter, Routes ,Route } from 'react-router-dom';
import './App.css';
import Login from './pages/Login/Login';
import useToken from './useToken';
import Layout from "./pages/Layout";
import Home from "./pages/Home";
import About from "./pages/About";
import NoPage from "./pages/NoPage";
function App() {
const { token, setToken } = useToken();
if(!token) {
return <Login setToken={setToken} />
}
return (
<div className="wrapper">
<h1>Application</h1>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="*" element={<NoPage />} />
</Route>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
useToken.js
import { useState } from 'react';
export default function useToken() {
const getToken = () => {
const tokenString = localStorage.getItem('token');
const userToken = JSON.parse(tokenString);
return userToken?.token
};
const [token, setToken] = useState(getToken());
const saveToken = userToken => {
localStorage.setItem('token', JSON.stringify(userToken));
setToken(userToken.token);
console.log(userToken.token)
};
return {
setToken: saveToken,
token
}
}
pages/Login/Login.js
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import './Login.css';
import jwt_decode from "jwt-decode"
const google = window.google;
export default function Login({ setToken }) {
// Handle response from Google login
function handleCallbackResponse(response){
console.log("Google token: " response.credential)
var userObject = jwt_decode(response.credential)
var userEmail = userObject.email;
console.log("userEmail=" userEmail)
document.getElementById("sign_in_form_google").hidden = true;
// Call login on backend async async e => {loginBackend(response.credential)};
const backendToken = loginBackend(response.credential)
console.log("Backend token: " backendToken)
setToken(backendToken);
}
// Login to backend
async function loginBackend(credentials){
console.log("Login.js :: loginBackend():: Logging in with " credentials)
var backendToken = fetch('https://localhost:5000/api/login', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' credentials
}
}).then(data => data.json())
console.log("Login.js :: loginBackend() :: Response is " backendToken)
return backendToken
}
// Google Login
useEffect(() => {
/* global google */
google.accounts.id.initialize({
client_id: "xxx-yyy.apps.googleusercontent.com",
callback: handleCallbackResponse
});
google.accounts.id.renderButton(
document.getElementById("sign_in_button_google"),
{ theme: "outline", size: "large" }
)
google.accounts.id.prompt();
}, []);
return(
<div className="body_bg">
<div className="wrapper">
{/* User login header */}
<header>
<div className='header_left'>
<p>My website</p>
</div>
<div className='header_right'>
<img src="gfx/login/logo_100x49.png" alt="Logo" />
</div>
</header>
{/* User login main */}
<main>
<h1>Please Log In</h1>
{/* Login form */ }
<div id="sign_in_form_google">
<p>Use Google to login below.</p>
<div id="sign_in_button_google"></div>
</div>
</main>
{/* User login footer */}
<footer>
<p>© 2022 Me</p>
</footer>
</div>
</div>
)
}
Login.propTypes = {
setToken: PropTypes.func.isRequired
};
pages/Layout.js:
import { Outlet, Link } from "react-router-dom";
const Layout = () => {
return (
<>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
<Outlet />
</>
)
};
export default Layout;
pages/Home.js:
import { getValue } from "@testing-library/user-event/dist/utils";
import { useContext, useEffect, useState, useMemo } from 'react';
import { UserContext } from "../useToken";
import jwt_decode from "jwt-decode"
export function Home() {
return (
<div>
<h1>Home</h1>
</div>
);
}
export default Home;
paegs/About.js:
import { useContext } from "react";
export function About() {
return (
<div>
<h1>About</h1>
</div>
);
}
export default About;
pages/NoPage.js:
const NoPage = () => {
return <h1>404</h1>;
};
export default NoPage;
Console output:
Login.js:11 Google token: eyxxx
Login.js:14 [email protected]
Login.js:26 Login.js :: loginBackend():: Logging in with eyxxx
Login.js:35 Login.js :: loginBackend() :: Response is [object Promise]
Login.js:19 Backend token: [object Promise]
useToken.js:15 undefined
CodePudding user response:
Make handleCallbackResponse
async and await loginBackend
:
async function handleCallbackResponse(response){
console.log("Google token: " response.credential)
var userObject = jwt_decode(response.credential)
var userEmail = userObject.email;
console.log("userEmail=" userEmail)
document.getElementById("sign_in_form_google").hidden = true;
// Call login on backend async async e => {loginBackend(response.credential)};
const backendToken = await loginBackend(response.credential)
console.log("Backend token: " backendToken)
setToken(backendToken);
}