Django, Djoser social auth : State could not be found in server-side session data. status_code 400 - reactjs

I'm implementing an auth system with django and react. The two app run respectively on port 8000, 3000. I have implemented the authentication system using the Djoser package. This package uses some dependencies social_core and social_django. Everything seems to be configured ok. I click on login google button...I'm redirected to the google login page and then back to my front-end react app at port 3000 with the state and code parameters on the url.
At this point I'm posting those parameters to the backend. The backend trying to validate the state checking if the state key is present in the session storage using the code below from (social_core/backends/oauth.py)
def validate_state(self):
"""Validate state value. Raises exception on error, returns state
value if valid."""
if not self.STATE_PARAMETER and not self.REDIRECT_STATE:
return None
state = self.get_session_state()
request_state = self.get_request_state()
if not request_state:
raise AuthMissingParameter(self, 'state')
elif not state:
raise AuthStateMissing(self, 'state')
elif not constant_time_compare(request_state, state):
raise AuthStateForbidden(self)
else:
return state
At this point for some reasons the state session key is not there..and I receive an error saying that state cannot be found in session data ( error below )
{"error":["State could not be found in server-side session data."],"status_code":400}
I recap all the action I do:
Front-end request to backend to generate given the provider google-oauth2 a redirect url. With this action the url is generated also the state key is stored on session with a specific value ( google-oauth2_state ).
Front-end receive the url and redirect to google auth page.
Authentication with google and redirection back to the front-end with a state and code parameters on the url.
Front-end get the data form url and post data to back-end to verify that the state received is equal to the generated on the point (1).
For some reasons the state code is not persisted... Any ideas and help will be really appreciated.
Thanks to all.

ok so this is a common problem while you are working with social auth. I had the same problem for so many times.
The flow:
make a request to http://127.0.0.1:8000/auth/o/google-oauth2/?redirect_uri=http://localhost:3000/ (example)
you will get a authorization_url. if you notice in this authorization_url there is a state presented . this is the 'state of server side'.
now you need to click the authorization_url link.Then you will get the google auth page.After that you will be redirect to your redirect url with a state and a code. Remember this state should be the same state as the server side state .(2)
make post req to http://127.0.0.1:8000/auth/o/google-oauth2/?state=''&code=''.
if your states are not the same then you will get some issue.
everytime you wanna login , you need to make a request to http://127.0.0.1:8000/auth/o/google-oauth2/?redirect_uri=http://localhost:3000/
and then to http://127.0.0.1:8000/auth/o/google-oauth2/?state=''&code='' thus you will get the same state.

Without necessary detailed information, I can only tell 2 possible reasons:
You overrode backend with improper session operations(or the user was logged out before auth was finished).
Front-end used incorrect state parameter
You could test social login without front-end, let's say if you're trying to sign in with Google:
Enter the social login URL in browser, like domain.com:8000/login/google-oauth2/
Authorize
See if the page redirected to your default login page correctly
If yes, then probably you need to check your front-end code, and if no, then check your backend code.
At the end, if you're not so sensitive to the potential risk, you could also override GoogleOAuth2 class as following to disable state check:
from social_core.backends import google
class GoogleOAuth2(google.GoogleOAuth2):
STATE_PARAMETER = False

I think you may need some changes in you authorizing flow in step NO.3 and 4.
3.Authentication with google and redirection back to the front-end with a state and code parameters on the url.
4.Front-end get the data form url and post data to back-end to verify that the state received is equal to the generated on the point (1).
maybe you should redirect back to server side after google's authorization.
then at the server side, do the check! validate the state and code (maybe do more things).
then let server redirect to the front-end site you wanted to before.
for some reason, redirect to front-end directly will miss the param.. :-)

Finally, I reach a point where everything is working 200 percent fine, on local as well as production.
The issue was totally related to the cookies and sessions:
So rite answer typo is
make it look to your backend server as if the request is coming from localhost:8000, not localhost:3000,
means the backend domain should be the same always.
For making it possible you have two ways:
1: server should serve the build of the frontend then your frontend will always be on the same domain as the backend.
2: make a simple view in django and attach an empty template to it with only a script tag including logic to handle google auth. always when you click on signing with google move back you you're that view and handle the process and at the end when you get back your access token pass it to the frontend through params.
I used 2nd approach as this was appropriate for me.
what you need to do is just make a simple View and attach a template to it so on clicking on signIN with google that view get hit. and other process will be handled by the view and on your given URL access token will be moved.
View Code:
class GoogleCodeVerificationView(TemplateView):
permission_classes = []
template_name = 'social/google.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["redirect_uri"] = "{}://{}".format(
settings.SOCIAL_AUTH_PROTOCOL, settings.SOCIAL_AUTH_DOMAIN)
context['success_redirect_uri'] = "{}://{}".format(
settings.PASSWORD_RESET_PROTOCOL, settings.PASSWORD_RESET_DOMAIN)
return context
backend script code:
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
function redirectToClientSide(success_redirect_uri) {
window.location.replace(`${success_redirect_uri}/signin/`);
}
function getFormBoday(details) {
return Object.keys(details)
.map(
(key) =>
encodeURIComponent(key) + "=" + encodeURIComponent(details[key])
)
.join("&");
}
try {
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const redirect_uri = "{{redirect_uri|safe}}";
const success_redirect_uri = "{{success_redirect_uri|safe}}";
if (params.flag === "google") {
axios
.get(
`/api/accounts/auth/o/google-oauth2/?redirect_uri=${redirect_uri}/api/accounts/google`
)
.then((res) => {
window.location.replace(res.data.authorization_url);
})
.catch((errors) => {
redirectToClientSide(success_redirect_uri);
});
} else if (params.state && params.code && !params.flag) {
const details = {
state: params.state,
code: params.code,
};
const formBody = getFormBoday(details);
// axios.defaults.withCredentials = true;
axios
.post(`/api/accounts/auth/o/google-oauth2/?${formBody}`)
.then((res) => {
const formBody = getFormBoday(res.data);
window.location.replace(
`${success_redirect_uri}/google/?${formBody}`
);
})
.catch((errors) => {
redirectToClientSide(success_redirect_uri);
});
} else {
redirectToClientSide(success_redirect_uri);
}
} catch {
redirectToClientSide(success_redirect_uri);
}
</script>
</body>

Related

Remembering the URL a user was trying to access when redirected to Login page in Next.Js

What I'm trying to do: If a user tried to go to the URL some-basepath/events/632dbb3f6852f but was not authenticated, I want to be redirected to something like some-basepath/login?redirect=events%2F632dbb3f6852f & after signing in, I should be taken to the URL I was trying to access.
(Note that here I encoded slashes as %2F but anything else that makes sense would also work)
What I tried in my useAuth custom hook which handles authentication:
if(user-not-logged-in){
const asPath = router.asPath();
console.log("as path = ",asPath);
const encodedAsPath = encodeURIComponent(asPath);
console.log("encoded as path = ",encodedAsPath);
router.push({
pathname:"/login",
query: {redirect: encodedAsPath}
});
}
and then in the login page:
useEffect(
() => {
const {redirect} = router.query;
console.log("decoded redirect in login page = ",decodeURIComponent(redirect))
},[router.isReady,router.asPath]
);
But when I navigate to the unauthorized page & I get redirected to login page I get the following results:
And the url of the page is:
And I can't understand what I did wrong.
I'd like to clarify 2 things.
1 - /api/* are server-handled requests. There might be a logic there which correctly gives a 401 response status code for unauthenticated users.
2 - as /api/* are handled in the server, you cannot run React-related features there.
If those statements above resonate with your code, I assume those 401 responses are correct and they aren't errors. That's only the way the browser shows it to you in the DevTools.
If those statements above do not resonate with your code. Please, clarify where you are implementing the code you've provided.

Nextjs - How Do I Store A JWT in The Client's Browser That Can Persist A Browser Restart

I am making an app that involves creating a JWT that the client must "remember" for at least 24 hours. I know that sounds strange but this app I'm building involves something called "Litprotocol" which does decentralized authorization. When a user logs in/registers, a JWT is created by a series of 3rd party servers, and the resulting JWT is valid for 24 hours. Since this JWT involves a somewhat intricate process, for efficiencies sake, I don't want to create a new JWT every time the user tries to login. The app is written in Nextjs and I tried to use the following code to attempt creating a persistently stored variable name "storedVal."
export default function Home(props: any) {
...
...
...
const [storedVal, setStoredVal] = useState(String);
function storePersistence() {
setStoredVal('I set this');
}
function printPersistence() {
console.log(storedVal);
}
return (
<>
<VStack>
<Heading as='h3'>Connect to see unity app</Heading>
{
!connected ? <Button variant='solid' onClick={connect}>Connect</Button> : <Text>Now you can click on protected path link at the bottom</Text>
}<br></br>
<Button variant='solid' onClick={storePersistence}>Store Persistence</Button>
<Button variant='solid' onClick={printPersistence}>Check Persistence</Button>
</VStack>
</>
)
}
The "Store Persistence" and "Check Persistence" buttons and functions are the relevant code. This doesn't work. As soon as I restart my browser, "storedVal" is cleared. So how can I make "storedVal" persist browser and possibly computer restarts?
General Idea/Mechanism for persisting data via localStorage/sessionStorage in client side.
Store it in localStorage when JWT is sent to client.
When user closes the site and open again that time fetch JWT from localStorage and check if it's valid or not and do the needful. Refer Blog for localstorage.
You can set JWT as follows localStorage.setItem("jwt", value) and fetch it localStorage.getItem("jwt")
You can use hooks/function provided by library/framework as well it does work on similar idea.
Aside from #GodWin's comment, you could also use this hook which I love: useLocalStorage. There's many variants of it and you can certainly write your own. It basically loads in a localStorage value into a hook given a key, for which you can provide a default value.
You can use it just like a useState:
function Home() {
const key = "local-jwt"
const defaultValue = null
const [jwt, setJWT] = useLocalStorage(key, defaultValue)
// use your jwt somewhere
return (
...
)
}

Is there a way to remove previous session without hard refreshing (sveltekit and Supabase)

im practicing authentication at the moment with sveltekit and Supabase
RLS is on and one of the issues im having is after i log out, and i sign in with another email, i can see the info from the previous session and when i hard refresh, then i see the correct data. Im not sure how to fix this
in my store
export const user = readable(null, (set) => {
set(supabase.auth.user());
const unsubscribe = supabase.auth.onAuthStateChange((_, session) => {
session ? set(session.user) : set(null);
});
return () => {
unsubscribe.data.unsubscribe();
};
});
I currently have a similar problem.
Where after sign out, still the old user persists somehow.
My auth is only on the clientside.
I use sveltekit (as a static site generator)
after signOut() there is still the sb-refresh-token cookie and the sb-access-token cookie with the jwt
thus, supabase.auth.getUser() still gets me the (old)user
so user does not get logged out.
what helped me is to reload the page after the signout()
aka not use the linking and goto() function of my framework but rather use the browser function
window.location.href = '/loggedOut';
This deleted the cookies for me

BrowserAuthError: interaction_in_progress: Interaction is currently in progress with azure/msal-browser#2.11.2

I has this error when trying to loginRedirect in React app using #azure/msal-react#1.0.0-alpha.6 and #azure/msal-browser#2.11.2. The login data returns correctly but the exception is raised in the console.
Uncaught (in promise) BrowserAuthError: interaction_in_progress:
Interaction is currently in progress. Please ensure that this
interaction has been completed before calling an interactive API.
import * as msal from "#azure/msal-browser";
const msalConfig = {
auth: {
clientId: '995e81d0-',
authority: 'https://login.microsoftonline.com/3a0cf09b-',
redirectUri: 'http://localhost:3000/callback'
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge
}
};
const msalInstance = new msal.PublicClientApplication(msalConfig);
try {
msalInstance.handleRedirectPromise()
.then(res=>{
console.log(res)
})
.catch(err => {
console.error(err);
});
var loginRequest = {
scopes: ["api://58ca819e-/access_as_user"] // optional Array<string>
};
msalInstance.loginRedirect(loginRequest);
} catch (err) {
// handle error
console.log(err)
}
The exception
Uncaught (in promise) BrowserAuthError: interaction_in_progress: Interaction is currently in progress. Please ensure that this interaction has been completed before calling an interactive API.
at BrowserAuthError.AuthError [as constructor] (http://localhost:3000/static/js/vendors~main.chunk.js:852:20)
at new BrowserAuthError (http://localhost:3000/static/js/vendors~main.chunk.js:8943:24)
at Function.BrowserAuthError.createInteractionInProgressError (http://localhost:3000/static/js/vendors~main.chunk.js:9023:12)
at PublicClientApplication.ClientApplication.preflightInteractiveRequest (http://localhost:3000/static/js/vendors~main.chunk.js:13430:30)
at PublicClientApplication.<anonymous> (http://localhost:3000/static/js/vendors~main.chunk.js:12581:33)
at step (http://localhost:3000/static/js/vendors~main.chunk.js:215:17)
at Object.next (http://localhost:3000/static/js/vendors~main.chunk.js:146:14)
at http://localhost:3000/static/js/vendors~main.chunk.js:118:67
at new Promise (<anonymous>)
at __awaiter (http://localhost:3000/static/js/vendors~main.chunk.js:97:10)
at PublicClientApplication.ClientApplication.acquireTokenRedirect (http://localhost:3000/static/js/vendors~main.chunk.js:12565:12)
at PublicClientApplication.<anonymous> (http://localhost:3000/static/js/vendors~main.chunk.js:13760:16)
at step (http://localhost:3000/static/js/vendors~main.chunk.js:215:17)
at Object.next (http://localhost:3000/static/js/vendors~main.chunk.js:146:14)
at http://localhost:3000/static/js/vendors~main.chunk.js:118:67
at new Promise (<anonymous>)
at __awaiter (http://localhost:3000/static/js/vendors~main.chunk.js:97:10)
at PublicClientApplication.loginRedirect (http://localhost:3000/static/js/vendors~main.chunk.js:13755:12)
at Module.<anonymous> (http://localhost:3000/static/js/main.chunk.js:192:16)
at Module../src/App.tsx (http://localhost:3000/static/js/main.chunk.js:292:30)
at __webpack_require__ (http://localhost:3000/static/js/bundle.js:857:31)
at fn (http://localhost:3000/static/js/bundle.js:151:20)
at Module.<anonymous> (http://localhost:3000/static/js/main.chunk.js:2925:62)
at Module../src/index.tsx (http://localhost:3000/static/js/main.chunk.js:3028:30)
at __webpack_require__ (http://localhost:3000/static/js/bundle.js:857:31)
at fn (http://localhost:3000/static/js/bundle.js:151:20)
at Object.1 (http://localhost:3000/static/js/main.chunk.js:3570:18)
at __webpack_require__ (http://localhost:3000/static/js/bundle.js:857:31)
at checkDeferredModules (http://localhost:3000/static/js/bundle.js:46:23)
at Array.webpackJsonpCallback [as push] (http://localhost:3000/static/js/bundle.js:33:19)
at http://localhost:3000/static/js/main.chunk.js:1:67
msalInstance.loginRedirect(loginRequest);
The piece of code above does next:
Looks into session storage for key msal.[clientId].interaction.status and other temp values required for redirection process. If such key exist and its value equals 'interaction_in_progress' error will be thrown.
Creates entry in session storage msal.[clientId].interaction.status = interaction.status
Redirects user to auth-page.
In case of successful login user will be redirected to initial page with your code and go through 1-3 steps and will catch an error;
The piece of code below removes all temp values in session storage and completes auth redirection flow but it is async and never will be completed.
msalInstance.handleRedirectPromise()
.then(res=>{
console.log(res)
})
.catch(err => {
console.error(err);
});
The solution will be
// Account selection logic is app dependent. Adjust as needed for different use cases.
// Set active acccount on page load
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
msalInstance.setActiveAccount(accounts[0]);
}
msalInstance.addEventCallback((event) => {
// set active account after redirect
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload.account) {
const account = event.payload.account;
msalInstance.setActiveAccount(account);
}
}, error=>{
console.log('error', error);
});
console.log('get active account', msalInstance.getActiveAccount());
// handle auth redired/do all initial setup for msal
msalInstance.handleRedirectPromise().then(authResult=>{
// Check if user signed in
const account = msalInstance.getActiveAccount();
if(!account){
// redirect anonymous user to login page
msalInstance.loginRedirect();
}
}).catch(err=>{
// TODO: Handle errors
console.log(err);
});
I believe this is the correct answer and way to set this up. Others here led me to clues to solve this.
TLDR; set your code up like this:
// authRedir.ts (or authRedir.vue inside mounted())
await msalInstance.handleRedirectPromise();
// mySignInPage.ts (or userprofile.vue, or whatever page invokes a sign-in)
await msalInstance.handleRedirectPromise();
async signIn(){
const loginRequest: msal.RedirectRequest = {
scopes: ["openid", "profile", "offline_access","your_other_scopes"]
redirectUri: "http://localhost:8080/authredirect"
};
const accounts = msalInstance.getAllAccounts();
if (accounts.length === 0) {
await msalInstance.loginRedirect();
}
}
If you do this correctly, you wont need the code #shevchenko-vladislav shared, wherein setActiveAccount() has to be manually done by you. Remember to verify all async/await wherever you call this in your app! And notice how I did NOT use handleRedirectPromise().then() or anything, really, in my main authredirect.vue file. Just handleRedirectPromise() on load.
Other solutions on Stackoverflow suggest things like checking for and deleting the interaction state from the session. Um, no! If you have that state left over after a sign-in, it means the process wasn't done right! MSAL cleans itself up!
Full details:
It is super important to understand what MSAL is actually doing during it's entire lifecycle (especially the redir path as opposed to popup), and sadly the docs fail to do a good job. I found this little "side note" extremely, extremely important:
https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/errors.md#interaction_in_progress
"If you are calling loginRedirect or acquireTokenRedirect from a page
that is not your redirectUri you will need to ensure
handleRedirectPromise is called and awaited on both the redirectUri
page as well as the page that you initiated the redirect from. This is
because the redirectUri page will initiate a redirect back to the page
that originally invoked loginRedirect and that page will process the
token response."
In other words, BOTH your Redirect page AND the page that INVOKED the sign-in request MUST call handleRedirectPromise() on page load (or on mounted(), in my case, since I am using Vue)
In my case, I have this:
http://localhost:8080/authredirect *
http://localhost:8080/userprofile
*Only my AuthRedirect Uri needs to be registered as a RedirectUri with my app registration in Azure AD.
So here is the loginRedirect() lifecycle, which I had NO idea, and lost a days work sorting out:
/UserProfile (or some page) invokes a sign-in request
The request calls handleRedirectPromise() (which sets MSAL up with info about where the request was made AND the interaction state that will bite you later if you dont complete the process)
and THEN calls loginRedirect(loginRequest)
-> user is redirected, completes sign-in
Azure redir back to -> /AuthRedirect
/AuthRedirect invokes handleRedirectPromise(), which forwards along to -> /UserProfile
/UserProfile invokes handleRedirectPromise() which does the actual processing of tokens AND internally calls setActiveAccount() to save your user to session.
Dang. That was fun. And not explained in the docs AT ALL.
So, the reason you are getting the interaction-in-progress error is because you are thinking you're all done on step 6. NOPE! Step 7 is where that interaction_in_progress state gets settled and cleaned up so that subsequent calls wont trip up on it!!
Final thought:
If you have a designated sign-in page you want users to always start/finish from (and itself is the registered redirect Uri), I suppose these steps will be reduced (no forwarding like in step 6 here). In my case, I want the user redirected back to wherever they might have gotten bumped out of due to a session expiration. So I just found it easier to call handleRedirectPromise() on every single page load everywhere, in case said page it needs to finalize authentication. Or, I could build my own redirect logic into a dedicated sign-in page that can put the user back where they were prior to hitting it. It's just that as for MSAL, I had NO idea the process was finishing up on the requesting page rather than contained within my AuthRedirect page, and THAT is what bit me.
Now, if we could just get MS to provide far better docs on the delicate and critical nature of MSAL, and to provide a Vue plugin (why does only Angular and React get all the glory? :) ), that would be great!
During development, it is possible that you left the sign-in flow in a progress-state due to a coding issue that you will need to correct. You can clear the immediate problem by deleting the msal.interaction.status cookie from the browser. Of course, if this problem persists, then you need to correct the problem using one of the other solutions suggested on this page.
You can clear the browser storage before open the loginPopup:
let msalInstance: PublicClientApplication = this._msauthService.instance as PublicClientApplication;
msalInstance["browserStorage"].clear();
I have found that in msal.js v2 you can check interaction status in vanilla .js code to see if there is an interaction in progress, should you need to do this for some reason:
const publicClientApplication = new window.msal.PublicClientApplication(msalConfig);
var clientString = "msal." + msalConfig.clientId + ".interaction.status";
var interaction-status = publicClientApplication.browserStorage.temporaryCacheStorage.windowStorage[clientString]
Update #azure/msal-browser#2.21.0.
For folks with an Azure/Active Directory situation:
My issue wasn't with my code. It was with deactivating the "Access tokens (used for implicit flows)" setting found in the Active Directory > Authentication > Implicit grant and hybrid flows section.
After you put the proper Redirect URIs into the Web section:
ex: https://example.com/.auth/login/aad/callback
And after you put the proper Redirect URIs into the Single-page application section:
ex: https://example.com
ex: https://localhost:4200
The last step is to make sure you disable the Access tokens I mentioned in the beginning:
When I was migrating my apps from .NET5 to .NET6 and the prior Angular Authentication over to MSAL, this setting was already checked for me (both were checked). After unchecking this setting, everything ended up working.
This may not be a clean solution. But this does work at least in Vue.js.
Next to your acquireToken() logic, add this
// Check Local or Session storage which may have already contain key
// that partially matches your Azure AD Client ID
let haveKeys = Object.keys(localStorage).toString().includes('clientId')
// That error will just go away when you refrest just once
let justOnce = localStorage.getItem("justOnce");
if (haveKeys && !justOnce) {
localStorage.setItem("justOnce", "true");
window.location.reload();
} else {
localStorage.removeItem("justOnce")
}
I have faced the similar error in my project.I took reference of the below link. It takes hardly 10 minutes to go through it. It will surely resolve if you face the scenarios given in it.
Link:
https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-angular/docs/v2-docs/redirects.md

Firebase - Best Practice For Server Firestore Reads For Server-Side Rendering

I have a server-side-rendered reactjs app using firebase firestore.
I have an area of my site that server-side-renders content that needs to be retrieved from firestore.
Currently, I am using firestore rules to allow anyone to read data from these particular docs
What worries me is that some bad person could setup a script to just continuously hit my database with reads and rack up my bills (since we are charged on a per-read basis, it seems that it's never wise to allow anyone to perform reads.)
Current Rule
// Allow anonymous users to read feeds
match /landingPageFeeds/{pageId}/feeds/newsFeed {
allow read: if true;
}
Best Way Forward?
How do I allow my server-side script to read from firestore, but not allow anyone else to do so?
Keep in mind, this is an initial action that runs server-side before hydrating the client-side with the pre-loaded state. This function / action is also shared with client-side for page-to-page navigation.
I considered anonymous login - which worked, however, this generated a new anonymous user with every page load - and Firebase does throttle new email/password and anonymous user accounts. It did not seem practical.
Solution
Per Doug's comment, I thought about the admin SDK more. I ended up creating a separate API in firebase functions for anonymous requests requiring secure firestore reads that can be cached.
Goals
Continue to deny public reads of my firestore database
Allow anonymous users to trigger firestore reads for server-side-rendered reactjs pages that require data from Firestore database (like first-time visitors, search engines).
Prevent "read spam" where a third party could hit my database with millions of reads to drive up my cloud costs by using server-side CDN cache for the responses. (by invoking unnessary reads in a loop, I once racked up a huge bill on accident - I want to make sure strangers can't do this maliciously)
Admin SDK & Firebase Function Caching
The admin SDK allows me to securely read from firestore. My firestore security rules can deny access to non-authenticated users.
Firebase functions that are handling GET requests support server caching the response. This means that subsequent hits from identical queries will not re-run all of my functions (firebase reads, other function invocations) - it will just instantly respond with the same data again.
Process
Anonymous client visits a server-side rendered reactjs page
Initial load rendering on server triggers a firebase function (https trigger)
Firebase function uses Admin SDK to read from secured firestore database
Function caches the response for 3 hours res.set('Cache-Control', 'public, max-age=600, s-maxage=10800');
Subsequent requests from any client anywhere for the next 3 hours are served from the cache - avoiding unnecessary reads or additional computation / resource usage
Note - caching does not work on local - must deploy to firebase to test caching effect.
Example Function
const functions = require("firebase-functions");
const cors = require('cors')({origin: true});
const { sendResponse } = require("./includes/sendResponse");
const { getFirestoreDataWithAdminSDK } = require("./includes/getFirestoreDataWithAdminSDK");
const cachedApi = functions.https.onRequest((req, res) => {
cors(req, res, async () => {
// Set a cache for the response to limit the impact of identical request on expensive resources
res.set('Cache-Control', 'public, max-age=600, s-maxage=10800');
// If POST - response with bad request code - POST requests are not cached
if(req.method === "POST") {
return sendResponse(res, 400);
} else {
// Get GET request action from query
let action = (req.query.action) ? req.query.action : null;
console.log("Action: ", action);
try {
// Handle Actions Appropriately
switch(true) {
// Get Feed Data
case(action === "feed"): {
console.log("Getting feed...");
// Get feed id
let feedId = (req.query.feedId) ? req.query.feedId : null;
// Get feed data
let feedData = await getFirestoreDataWithAdminSDK(feedId);
return sendResponse(res, 200, feedData);
}
// No valid action specified
default: {
return sendResponse(res, 400);
}
}
} catch(err) {
console.log("Cached API Error: ", err);
return sendResponse(res, 500);
}
}
});
});
module.exports = {
cachedApi
}

Resources