I am trying to create a chrome extension using react and allows users to login with google to my backend server that works with my webapplication front end server. I am able to prompt the user to login using google and retrieve the code using oauth2 flow. However, when I try to fetch my backend route, I am getting the following error:
TypeError: Failed to fetch at background.js:20:7
Here is my chrome extension code: manifest.json
{
"name": "my chrome extension",
"description": "my chrome extension description",
"version": "1.0.0",
"manifest_version": 3,
"author": "team creators",
"icons": {
"16" : "favicon.ico",
"48" : "favicon.ico",
"128" : "favicon.ico"
},
"action": {
"default_popup": "index.html",
"default_title": "my chrome extension"
},
"background": {
"service_worker": "background.js"
},
"oauth2": {
"client_id": "my client id",
"scopes": [
"openid",
"profile",
"email",
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/documents",
"https://www.googleapis.com/auth/contacts",
"https://www.googleapis.com/auth/contacts.readonly",
"https://www.googleapis.com/auth/contacts.other.readonly",
"https://mail.google.com/"
]
},
"content_security_policy": {
"script-src": ["'self'", "https://accounts.google.com", "'wasm-unsafe-eval'", "'inline-speculation-rules'", "http://localhost:*", "http://127.0.0.1:*"]
},
"permissions": [
"identity",
"nativeMessaging",
"tabs"
],
"host_permissions": [
"https://localhost:*/*",
"http://localhost:*/*"
]
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
<script type="text/javascript" src="oauth2.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
oauth2.js
let clientId = 'my client id'
let redirectUri = `https://${chrome.runtime.id}.chromiumapp.org/`
let nonce = Math.random().toString(36).substring(2, 15)
function addClickEventToButton() {
const button = document.getElementById('google-login-button');
if (button) {
console.log("we get here 3");
button.addEventListener('click', function() {
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
authUrl.searchParams.set('client_id', clientId);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('redirect_uri', redirectUri);
var manifest = chrome.runtime.getManifest();
authUrl.searchParams.set('scope', manifest.oauth2.scopes.join(' '));
// authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('nonce', nonce);
// Show the consent screen after login.
authUrl.searchParams.set('prompt', 'consent');
chrome.identity.launchWebAuthFlow(
{
url: authUrl.href,
interactive: true,
},
async (redirectUrl) => {
if (redirectUrl) {
const codeRegex = /code=([^&]+)/;
const codeMatch = redirectUrl.match(codeRegex);
const code = codeMatch ? codeMatch[1] : null;
chrome.runtime.sendMessage({contentScriptQuery: "login", code: code}, function(response) {
console.log("reponse", response);
});
}
},
);
});
} else {
setTimeout(addClickEventToButton, 500);
}
}
document.addEventListener('DOMContentLoaded', function() {
addClickEventToButton();
});
background.js
chrome.action.onClicked.addListener(function() {
chrome.tabs.create({url: 'index.html'});
console.log('action clicked');
});
chrome.runtime.onMessage.addListener(
async function(request, sender, sendResponse) {
if (request.contentScriptQuery == "login") {
console.log("getting request", request);
const url = "https://localhost:4000/google/auth/login";
fetch(url, { // errors here
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code: request.code, isExtensionLogin: true }),
}).then(response => response.json())
.then(response => sendResponse(response))
.catch((err) => console.log("err", err))
return true;
}
});
Here is my backend server.js with relevant information
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "http://localhost:3000,https://my-extension-id.chromiumapp.org/,chrome-extension://my-extension-id/"); // update to match the extension domain you will make the request from
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Access-Control-Allow-Methods", "GET,HEAD,OPTIONS,POST,PUT,PATCH,DELETE");
res.setHeader(
"Access-Control-Allow-Headers",
"Access-Control-Allow-Headers, Origin, Accept, X-Requested-With, Content-Type, Authorization, Access-Control-Request-Method, Access-Control-Request-Headers"
);
next();
});
route: "https://localhost:4000/google/auth/login"
const handleGoogleSignin = async (req, res) => {
try {
const isExtensionLogin = req.body.isExtensionLogin;
const { tokens } = await oauth2Client.getToken(req.body.code);
const { sub } = jwt.decode(tokens.id_token);
// check if user (google login user) exists
const googleIntegrated = await ThirdParty.findOne({
thirdPartyId: sub,
});
if (!googleIntegrated)
return res.status(404).json({ message: "Google Account not in DB" });
if (!googleIntegrated.isSignup)
return res.status(404).json({
message: "Google Account not associated with a login account",
});
// update google tokens
...
const user = await User.findById(googleIntegrated.userId);
const userToken = jwt.sign({ id: user._id }, process.env.JWT_SECRET);
res.status(200).json({ token: userToken, user });
} catch (err) {
res.status(409).json({ message: err.message });
}
};
I found several questions pertaining this topic, but they haven't helped me out Getting "TypeError: Failed to fetch" when the request hasn't actually failed POST API call returns CORS error without https and ERR_CONNECTION_CLOSED without Manifest v3 fetch data from external API How to make a cross-origin request in a content script (currently blocked by CORB despite the correct CORS headers)?
Answering my own question:
I should be "http://localhost:4000/google/auth/login" not "https://localhost:4000/google/auth/login"