I have a React PWA created with create-react-app.
The app has Google authentication.
This is the Google sign in button onClick
event listener:
onClick={(e) => {
e.preventDefault();
const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === "[::1]" ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
if (isLocalhost) {
window.location.href =
"http://localhost:5000/api/users/auth/google";
} else {
window.location.href =
"http://example.com/api/users/auth/google";
}
}}
When the user clicks on it, the browser url becomes:
And this backend endpoint is supposed to be fetched from the server:
// @route GET api/users/auth/google
// @desc Authenticate user using google
// @access Public
router.get(
"/auth/google",
passport.authenticate("google", {
scope: [
"email",
"profile",
"https://www.googleapis.com/auth/user.birthday.read",
"https://www.googleapis.com/auth/user.gender.read",
"https://www.googleapis.com/auth/user.addresses.read",
],
approvalPrompt: "force",
})
);
This worked perfectly fine until I configured the app to be PWA.
Now, when the user clicks on the button and the auth url is loaded or I manually enter it in the browser address bar, I think that the service worker is preventing fetching that endpoint from the server and is instead loading a cached version of the app.
Which breaks the Google authentication flow as I explained before.
For days, I have tried all sorts of ways to prevent loading a cached version when the auth url is loaded. As suggested here, this:
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// Do not handle non-GET requests.
if (event.request.method !== 'GET') {
return;
}
// Ignore /api/users/auth/google route.
if (url.pathname.startsWith('/api/users/auth/google')) {
return;
}
// Here, you can add your logic to handle other requests and caching.
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
if (cachedResponse) {
return cachedResponse;
}
return caches.open(CACHE_NAME)
.then(cache => {
return fetch(event.request).then(response => {
return cache.put(event.request, response.clone()).then(() => {
return response;
});
});
});
})
);
});
or this:
self.addEventListener('fetch', event => {
// Check if the request is for the specific API endpoint
if (event.request.url.endsWith('/api/users/auth/google')) {
// Respond to this request with a network fetch, bypassing the cache
event.respondWith(fetch(event.request));
return;
}
// Handle other requests here, possibly using caches
});
don't work because the fetch event listener works for requests that get triggered AFTER the page is loaded.
So whatever is entered in the browser address bar doesn't trigger the service worker fetch event listener.
I hopelessly tried this as well, clearing the cache and reloading the page if the url is the auth url in an attempt to force the browser to fetch the endpoint from the server. I added this in componentDidMount in App.js:
// Clear cache and reload when the specific URL is accessed
const is_google_auth_route =
window.location.pathname === "/api/users/auth/google";
if (is_google_auth_route) {
const is_caches_in_window = "caches" in window;
if (is_caches_in_window) {
caches
.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
return caches.delete(cacheName);
})
);
})
.then(() => {
window.location.reload();
});
}
}
But, that kept infinitely reloading the page.
The thing is, at this stage of my app, caching is not important for me. I need other features of the PWA (Installation, push notifications...etc).
So I'm okay if I disable caching altogether, since I couldn't find a way to disable it for the specific auth url.
Right now, I had to unregister the service worker, and make the app normal again and lose all its PWA features.