Providing endpoint URI to server-side rendered app - server-side-rendering

This code initializes an Apollo client for interaction with a GraphQL backend:
function create(initialState?: any) {
const isBrowser = typeof window !== "undefined"
return new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser,
link: new HttpLink({
uri: "https://APPNAME.herokuapp.com/v1/graphql",
credentials: "same-origin",
fetch: !isBrowser && fetch
}),
cache: new InMemoryCache().restore(initialState || {})
})
}
where APPNAME is replaced with the name of the Heroku app that hosts said backend. However, when the URI written as a simple string is replaced by an environment variable such as process.env.GRAPHQL_ENDPOINT, requests are not sent to the correct URI anymore but to the /graphql endpoint on the local server. That is most certainly caused by the app being a server-side rendered Next.js app, since the request has then chances of being sent by the client rather than the server, and process doesn't exist on client-side.
Therefore the question is: what is a simple way for the app to get the endpoint URI from something like an environment variable or a file hosted on server-side?
I did some research and a solution could be hosting a microservice locally in order to host a JSON file that would contain the URI, as recommended here. Would there be a solution that doesn't require such overkill?

Made it work by adding a hidden HTML tag inside the <head> like this:
<p style={hidden} id="graphql_endpoint">
{process.env.GRAPHQL_ENDPOINT}
</p>
with hidden defined to { display: "none" }, and then retrieving it when creating the Apollo client like this:
uri: isBrowser
? document.getElementById("graphql_endpoint").innerHTML
: process.env.GRAPHQL_ENDPOINT,

Related

NextJS: How can I create a server side request context

I need to forward a header from the browser to an external API I call from the server side.
The external API is called from getServerSideProps and API routes.
I was thinking about implementing some sort of a request context (using AsyncLocalStorage for example) that I can access from anywhere in the server side code.
That way I could create a middleware that will save the header to the context, and in the external API client I'll fetch it from the context and add it to the requests.
For example:
// context.ts
export const context = new AsyncLocalStorage<string>();
// middleware.ts
export function middleware(request: NextRequest) {
const store = request.headers[SOME_HEADER];
return context.run(store, () => NextResponse.next());
}
// client.ts
axios.post(EXTERNAL_API, DATA, {
headers: {
SOME_HEADER: context.getStore()
}
}).then(...)
Currently I simply send it as a parameter which is pretty tedious.
Is there a way of achieving it?
I tried adding async_hooks to my project but it got really messy when I tried to build the project.

Handle Firebase client-side token and access to protected pages

I'm using Firebase auth to login with Facebook, Google and email/pass. Basically, everything runs client-side, I make a call to Firebase and I receive an object containing an access token (that is a JWT ID Token), a customer id and its email. When I get this object, I put it into a persistent store (local storage, I know it's bad) and I perform an API call to one of my sveltekit endpoint that will in turn make another API call to a backend API (written in Go) to get all the user informations: firstname, lastname, phone, address, stats etc. To give a little bit of context, below is a diagram to illustrate what's happening when a user sign-in using Facebook.
Up to now, I just put the Firebase object into a store and just check if the information are there to allow access to a particular page. This check is done in the +layout.svelte page of the directory containing the page to be protected. It looks like something like this:
onMount(() => {
// redirect if not authenticated
if (browser && !$authStore?.uid) goto(`/auth/sign-in`);
});
It's probably not a good thing especially since my store persists in the local storage and therefore is prone to some javascript manipulation.
From my understanding, there's at least 2 things that could be better implemented but I may be wrong:
Set the access token in an httponly cookie straight after receiving it from Firebase. This would avoid storing the access token (that is a JWT) in the local storage and It could be used to authorize access or not to some protected pages...
I receive the Firebase authentication object on client-side buthttp only cookie can only be created from server side. I thought about sending the object as a payload to a POST sveltekit endpoint (like /routes/api/auth/token/+server.js) and set the cookie from here but apparently cookies is not available in a sveltekit endpoint.
So, how and where should I set this cookie ? I see that cookies is available in the load function of a +layout.server.js file, as well as in the handle function of a hooks.server.js file, but I don't see the logic here.
Populate locals.userwith the authenticated user once I've performed a call to my backend. Well, here, it's not obvious to me because I think point 1) would be enough to manage access to protected pages, but I see that a check of locals.user is something I've seen elsewhere.
I tried to set locals.user in the sveltekit endpoint that is making the API call to the backend API:
// /routes/api/users/[uid]/+server.js
import { json } from "#sveltejs/kit";
import axios from "axios";
import { GO_API_GATEWAY_BASE_URL } from "$env/static/private";
export async function GET({ request, locals, params }) {
try {
const config = {
method: "get",
baseURL: GO_API_GATEWAY_BASE_URL,
url: `/users/${params.uid}`,
headers: {
Authorization: `Bearer ${uidToken}`, // <-- the Firebase ID Token
},
withCredentials: true,
};
const res = await axios(config);
// set locals
locals.user = json(res.data); // <--- DOESN'T SEEM TO WORK
return json(res.data);
} catch (error) {...}
}
...but in the +layout.server.js page that I've created I see nothing:
// routes/app/protected_pages/+layout.server.js
import { redirect } from "#sveltejs/kit";
export function load({ locals }) {
console.log(locals); // <----- contains an empty object: {}
if (!locals.user) throw redirect(302, "/auth/sign-in");
}
Thank you so much for your help

nuxtServerInit not receiving cookies [duplicate]

This question already has answers here:
firebase cloud function won't store cookie named other than "__session"
(4 answers)
Closed 10 months ago.
What I am trying to achieve
The basic idea is to send a user to another route when a cookie is received in the Nuxt router middleware.
The middleware is always called both on server-side and client-side, and it works perfectly in the dev environment.
The problem that happens only in production is that the server-side middleware never receives the cookie.
My attempt
The logic is simple: nuxtServerInit is called on the server before the router middleware is called. So it gets the cookie from the user and saves it in Vuex:
nuxtServerInit({ commit }, { req }) {
const token = this.$cookies.get('test');
commit('auth/setToken', { value: !!token}) }
}
Then, the router middleware is called and checks Vuex if the cookie is there to redirect the user:
export default function ({ store, route, redirect }) {
if (route.path === '/' && store.getters['auth/getToken']) { redirect('/test'); }
}
Everything works perfectly fine locally, but upon deployment no redirection happens.What am I missing?
More info
As that may be relevant, I am using a firebase cloud function to host my Nuxt ssr website:
exports.renderApp = functions.https.onRequest(async (req, res) => {
res.set('Cache-Control', 'public, max-age=300, s-maxage=600');
await nuxt.ready(); nuxt.render(req, res);
});
I first thought it may be a cookie problem, but I tried setting them up with vanilla js, then (as implemented above) I tried cookie-universal-nuxt, as well as bypassing Vuex completely and checking the req.headers.cookie directly in the middleware. The result is always the same: works in dev, does not work in production.
Try to change the name of your cookie to __session.
You can find more info in this question.

How to verify the request is coming from my application clientside?

I have an app where users can create posts. There is no login or user account needed! They submit content with a form as post request. The post request refers to my api endpoint. I also have some other api points which are fetching data.
My goal is to protect the api endpoints completely except some specific sites who are allowed to request the api ( I want to accomplish this by having domain name and a secure string in my database which will be asked for if its valid or not if you call the api). This seems good for me. But I also need to make sure that my own application is still able to call the api endpoints. And there is my big problem. I have no idea how to implement this and I didn't find anything good.
So the api endpoints should only be accessible for:
Next.js Application itself if somebody does the posting for example
some other selected domains which are getting credentials which are saved in my database.
Hopefully somebody has an idea.
I thought to maybe accomplish it by using env vars, read them in getinitalprops and reuse it in my post request (on the client side it can't be read) and on my api endpoint its readable again. Sadly it doesn't work as expected so I hope you have a smart idea/code example how to get this working without using any account/login strategy because in my case its not needed.
index.js
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
export default function Home(props) {
async function post() {
console.log(process.env.MYSECRET)
const response = await fetch('/api/hello', {
method: 'POST',
body: JSON.stringify(process.env.MYSECRET),
})
if (!response.ok) {
console.log(response.statusText)
}
console.log(JSON.stringify(response))
return await response.json().then(s => {
console.log(s)
})
}
return (
<div className={styles.container}>
<button onClick={post}>Press me</button>
</div>
)
}
export async function getStaticProps(context) {
const myvar = process.env.MYSECRET
return {
props: { myvar },
}
}
api
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default function handler(req, res) {
const mysecret = req.body
res.status(200).json({ name: mysecret })
}
From what I understand, you want to create an API without user authentication and protect it from requests that are not coming from your client application.
First of all, I prefer to warn you, unless you only authorize requests coming from certain IPs (be careful with IP Spoofing methods which could bypass this protection), this will not be possible. If you set up an API key that is shared by all clients, reverse engineering or sniffing HTTP requests will retrieve that key and impersonate your application.
To my knowledge, there is no way to counter this apart from setting up a user authentication system.

Why do I get a "502 Gateway" error from NextJs app hosted on Firebase for POST requests only?

I started to build an API using NextJs framework. I want it to be hosted on Firebase (Hosting and Functions). Everything is working as long as I send only GET requests. When I send a POST request I receive a "502 Bad Gateway" error.
It's very simple to reproduce. You just have to download and deploy the example provided by the team developing NextJs.
create a new project on Firebase console
install the "with Firebase hosting" example
change the project name in the .firebaserc (line 3) file
create a folder "api" under the folder "pages"
create a file "hello.js" under the folder "api" and add the following snippet
export default async (req, res) => {
const {
body,
method
} = req;
console.log("method :>> ", method);
console.log("body :>> ", body);
switch (method) {
case "POST":
res.status(200).end(`Method ${method} supported!`);
break;
default:
res.setHeader("Allow", ["POST"]);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
deploy the app
send a GET request to "https://[project-name].web.app/api/hello" and see it works
send a POST request to "https://[project-name].web.app/api/hello" and see it does not work
Do you have a the same error as me?
I spent 2 days to read articles, watch videos and try different configurations. You can even update the firebaseFunctions to add a console.log and see the POST request is caught by the Firebase Cloud Function but the NextJs server does not pass it to our API like it does for a GET request. It's out of my skills range...
Below the output you should have. The POST request should be answered with 200 - Method POST is supported!.
This was a real pain to track down, but after poking around myself for a while, I found that the same issue crops up for PUT and PATCH requests. Which suggested that it had something to do with the body of the request. Annoyingly, after finding that out, I stumbled across the thread of Issue #7960, where they found the same problem.
Simply put, the body of the request processed once by https.onRequest() and then nextjsHandle() tries to parse it again. Because the body was handled already, the raw-body module (within nextjsHandle()) waits indefinitely for 'data' events that will never come.
Currently, there isn't a way to turn off the body parsing done by https.onRequest(), so it must be disabled on the next.js end. Unfortunately, there isn't a global off switch for body parsing that can be added in next.config.js and it must be done for each and every API route (the files in pages/api) (which may change if the proposed fix in PR #16169 is added).
To disable body parsing for a given route, you add the following to the route's file
export const config = {
api: {
// disables call to body parsing module
bodyParser: false,
}
};
However, as mentioned in Issue #7960 by #rscotten, you might also want to use next dev while developing your app, so you need to enable it while using next dev but disable it while deployed. This can be done using
export const config = {
api: {
// disables call to body parsing module while deployed
bodyParser: process.env.NODE_ENV !== 'production',
}
};
Applying these changes to hello.js gives:
export default async (req, res) => {
const {
body,
method
} = req;
console.log("method :>> ", method);
console.log("body :>> ", body);
switch (method) {
case "POST":
res.status(200).end(`Method ${method} supported!`);
break;
default:
res.setHeader("Allow", ["POST"]);
res.status(405).end(`Method ${method} Not Allowed`);
}
};
export const config = {
api: {
// disable nextjs's body parser while deployed
// (as body parsing is handled by `https.onRequest()`),
// but enable it for local development using `next dev`
bodyParser: process.env.NODE_ENV !== 'production',
}
};

Resources