I am writing a headless solution for a WordPress website and noticed that for one particular endpoint, I need to authenticate to pull some data that will be used publicly. But, I'm concerned that where I'm using it will expose it to the web.
In my store/index.js I use the nuxtServerInit action method to execute some actions and I pass them some objects they need to fulfill their tasks:
async nuxtServerInit ({ dispatch }, { $axios, app }) {
await dispatch('initialize', { $axios, app })
},
$axios is passed because it will be used to query the API, and app is passed to help build the options to authenticate the request.
Is this a security vulnerability in Nuxt SSR? I think it is. If so, where are the only valid areas you can use secrets? asyncData ()?
If you're using SSR, you can use the privateRuntimeConfig runtime object and pass your secret in the nuxt.config.js file
export default {
privateRuntimeConfig: {
apiSecret: process.env.API_SECRET
}
}
If you read the documentation of nuxtServerInit, you can see that
Vuex action that is called only on server-side to pre-populate the store
Since this method is server-side only, you can use apiSecret (in my example) and it should be totally fine security-wise.
PS: Keep in mind that everything beyond what is generated on the server (hence, with NodeJS or nuxtServerInit) is "public". So your VueJS's client code lifecycle hooks are public: mounted(), fetch(), asyncData() because they will be visible on your browser's devtools.
Also, should your endpoint be that critical? If so, nuxtServerInit is the good way to go. If you need to fetch some more data in a "private way", you'll need to proxy it through some backend to hide the sensitive info and retrieve only the useful public data.
Related
In Next.js you have the option of server side rendering (SSR) or static site generation (SSG). Throughout the Next.js docs and community, SSG is recommended over SSR for performance reasons.
I have a Next.js build that uses SSG throughout the application, using getStaticProps() etc. to generate the content/pages at build time by integrating with an external CMS (Prismic). I prefer this because as mentioned it gives a performance boost and also most of the codebase can then use the same data-fetching strategy (at build time).
However, some of these pages need to be protected - meaning they should only be accessed by authenticated users. We are using Auth0 to generate a JWT token and have a React context provider save the status of the user (logged in or not) after validating the token in an API call.
But, I am struck that I don't seem to have a good way to protect SSG pages with this token. The recommended way here strikes me as odd because as far as I can tell this is a client-side redirect that could be manipulated by the client (for example - the client could manipulate it's local state/context or else tamper with whatever is returned from notloggedincondition) to show the static content or otherwise short-circuit the redirect.
For reference, here is a paste of that code:
import {useEffect} from 'react'
import {useRouter} from 'next/router'
export async function getStaticProps() {
return {
props: {
hello: 'Hello World'
}
}
}
export default (props) => {
const router = useRouter()
useEffect(() => {
if(notloggedincondition) {
router.push('/login')
}
}, [])
return <h1>Rest of the page</h1>
}
Note the <h1>Rest of the page</h1> could still be accessed by manipulating the client... so I want to secure the SSG at the request/response level and do a server side redirect (if need be), or something like that.
Short of something like this, is there no way to securely protect a SSG page without having to rely on client-side routing? Do I need to SSR the content even though it is no different really from the rest of the content, save for the requirement that only authenticated users can see it?
Perhaps I am missing something obvious, but it seems to me that even with a static site there should be a way to protect it without relying on client side routing. That is to say, it does not seem intrinsic to the concept of a statically generated site that every page must be public, so I'm wondering about a way to do this in Next.js that is secure.
The best way I could find to accomplish this is via SWR fetches, statically generating a skeleton of the page with initial unprotected static data and then hydrating it with the refresh, if the refresh returns content.
This does require that you move logic gathering data for the protected page behind an API or CMS (anything which would clear your view of permissions), and converting existing routes to use API calls isn't a trivial task, so YMMV.
Important note: your redirect would still need to be client-side, but you can avoid having anything protected shown to an unauthorized user as that'd still be controlled at the server level. Since your biggest concern appears to be a user actively trying to compromise content by manipulating code, this seems to meet your risk remediation criteria (they would still be unable to access protected content).
Example page code:
import {useEffect} from 'react'
import {useRouter} from 'next/router'
import useSWR from 'swr'
export async function getStaticProps() {
return {
props: {
hello: 'Hello World'
}
}
}
export default (props) => {
const router = useRouter()
// Access the protected content via an API route,
// provide the initial unprotected static content via the initialData param
const { data } = useSWR('/api/protected-content', fetcher, { initialData: props })
useEffect(() => {
if(notloggedincondition) {
router.push('/login')
}
}, [])
return <h1>{ data.hello }</h1>
}
Then, an example API implementation at pages/api/protected-content:
export default async function ProtectedContent(req, res) {
// Get a session object based on request cookies
let session = await initUserSession(req, res);
// If a session is available, return the protected content
if (session.props.userSession) {
return res.status(200).json({hello: 'This is protected content'});
} else {
return res.status(401).send("UNAUTHENTICATED");
}
}
Next js now has middleware that can redirect request based on the context.
Encountered the same problem at work, and opted for a custom server that sits in front of Next.js.
We're using Express, but any Node framework would work really. This lets us move authentication concerns to a separate server framework, which in turn forwards the requests to Next.js.
https://nextjs.org/docs/advanced-features/custom-server
I'm attempting to use the Google Maps Embed API to embed a map matching an address that a user enters. Following the developer guide, I acquired an API Key to add to my API requests. I'm able to successfully pull up a map when I request it via the "src" attribute of an iframe in my React client, like so:
<iframe
...
src={`https://www.google.com/maps/embed/v1/${mode}?key=${apiKey}&q=${encodedAddressQuery}`}
>
But this leaves my API key exposed to the client.
In Google's API Key Best Practices, it's recommended that the API key be stored in an environment variable and that a proxy server be used to safeguard keys. However when I try proxying the request (code below), it seems to return the appropriate map for a split second, but then the iframe replaces the map with an "Oops! Something went wrong" message, and the browser console displays this error:
Google Maps JavaScript API error: UnauthorizedURLForClientIdMapError
https://developers.google.com/maps/documentation/javascript/error-messages#unauthorized-url-for-client-id-map-error
Your site URL to be authorized: http://127.0.0.1:3001/api/maps?parameters=[my_encoded_parameters]
I'm just developing locally at the moment, so I've tried registering http://127.0.0.1:3001/* as an authorized URL as the error documentation suggests, and I've also tried removing all website restrictions on the API key just to see if I was authorizing the wrong URL, but both of those attempts still produced the same error.
I haven't found many resources on setting up a proxy other than this Google Codelabs project, but I haven't been able to pick anything out of it to help with this issue.
Here's the code I'm using in my React front end:
<iframe
...
src={`http://127.0.0.1:3001/api/maps?parameters=${encodedAddressQuery}`}
>
And in my Express Node.js back end:
router.get('/api/maps', async (req: Request, res: Response) => {
try {
const parameters = req.query.parameters;
const map = await MapDataAccessObject.getOne(parameters);
return res.status(OK).send(map.data);
} catch (err) {
...
}
});
export class MapDataAccessObject {
public static async getOne(parameters: string) {
const apiUrl = this.getMapsEmbedUrl(parameters);
const map = await axios.get(apiUrl);
return map;
}
private static getMapsEmbedUrl(parameters: string) {
const encodedParams = encodeURI(parameters);
return `https://www.google.com/maps/embed/v1/place?key=${process.env.MAPS_API_KEY}&q=${encodedParams}`;
};
}
I'm running my React front end and my Node.js back end in Docker containers.
My best guess is that I'm missing a request header that the Maps API is expecting, but I haven't found any documentation on that, or maybe I'm misunderstanding something more fundamental. Any guidance would be very much appreciated.
There is no way to protect your google maps key on a website from being "spied on" because it is always public. The only way to protect your key from foreign usage is to restrict it's access to only the allowed domain/ip-addresses - so if it is used from someone else, it will not work or take anything from your credits (google will show an error message).
https://developers.google.com/maps/documentation/javascript/get-api-key
Okay, so atm i have a frontend application built with Nuxt JS using Axios to do requests to my REST API(separate).
If a user does a search on the website the API URL is visible in XMLHttprequests so everyone could use the API if they want to.
What is the best way of making it so that only users that search through my website gets access to the API and people that just directly to the URL gets denied. I suppose using some sort of token system, but what is the best way to do it? JWT? (Users never log in so there is no "authentication")
Thanks!
IMO, you CANNOT block other illegal clients accessing your
backend as you describe that the official client and other illegal have the same knowledge about your backend.
But you can make it harder for illegal clients to accessing your backend through some approach such as POST all requests, special keys in header, 30-minutes-changed token in header and server-side API throttling by client IP.
If the security of the search API is really important, authenticate it by login; if not, just let it go since it is not in your critical path. Let's focus on other important things.
I'm in the same "boat" and my current setup is actually in VueJs but before even come to StackOverflow I developed a way to actually, the frontend calls the server and then the server calls the API, so in the browser, you will only see calls to the server layer that, the only constraint is that the call must come from the same hostname.
backend is handled with expressJs and frontend with VueJs
// protect /api calls to only be originated from 'process.env.API_ALLOW_HOST'
app.use(api.allowOnlySameDomainRequests());
...
const allowHostname = process.env.API_ALLOW_HOST ||'localhost';
exports.api = {
...
allowOnlySameDomainRequests: (req, res, next) => {
if(req.url.startsWith('/api') && req.hostname === allowHostname) {
// an /api call, only if request is the same
return next();
} else if (!req.url.startsWith('/api')) {
// not an /api call
return next();
}
return res.redirect('/error?code=401');
},
...
};
In our case, we use Oauth2 (Google sign through passportJs) to log in the user, I always have a user id that was given by the OAuth2 successful redirect and that user id is passed to the API in a header, together with the apikey... in the server I check for that userid permissions and I allow or not the action to be executed.
But even I was trying to find something better. I've seen several javascript frontend apps using calls to their backend but they use Bearer tokens.
As a curious user, you would see the paths to all the API and how they are composed, but in my case, you only see calls to the expressJs backend, and only there I forward to the real API... I don't know if that's just "more work", but seemed a bit more "secure" to approach the problem this way.
How can I show only public(i.e: home, about, authentication stuff) if the user is not signed in?
I know SPA's are not meant to refresh, but I want it to change most of the scripts on the page.
Environment: Using ReactJS, WebPack with an existing NodeJS API, both on separate projects.
I went through Webpack documentation and understood that it will load only the required scripts and components, but all scripts can be seen with "View Page Source". If I understood it the wrong way, please correct me.
Ideally you track if the user is logged-in or not by storing a sessiontoken somewhere in your application state tree. You can use something like Redux to manage state in your application. You first need to decide which route you need to redirect to if the user is not logged-in & requests a route which requires authentication. Lets assume you have 2 routes /login and /products. You want to ensure that only authenticated users can view the /products section. The way you accomplish this is by redirecting to /login by checking the sessiontoken value of your state which you can pass as props to your Products component mapped to the /products route.
Use something like react-router
In the Products component use something like below
class Products extends Component{
componentWillUpdate(nextProps, nextState, nextContext) {
let {sessiontoken}=nextProps;
if (!sessiontoken)
nextContext.router.push('/login');
}
}
Products.contextTypes = {
router: React.PropTypes.object.isRequired
}
In the code above we are reading the value of sessiontoken which is passed as props from a higher order Container component. In case there is no sessiontoken it will redirect to /login. As long as your APIs return an HTTP 401 on encountering an invalid sessiontoken this will work perfectly for you and you don't need to worry about people being able to view page source.
I need to make an API request to an external API using an API Key. I know how to make this API request in React by writing a onSubmit function. But since I have an API key that I want to keep a secret I am going to write a simple Node app to house env variables.
Besides messing around in node this is my first production experience with Node and I am wondering if my thought process is correct and if not, the better way to do this.
Most of this question will be pseudo code since I haven't started with the Node portion yet.
The idea is that from within the React component it would call the Node app who in turn would call the external API.
React -> Node -> External API
So the React component would be something like so:
handleSubmit: function() {
var data = this.refs.testData.getDomNode().value;
$.ajax({
url: '/my-node-endpoint',
dataType: 'json',
type: 'POST',
data: { test: data },
success: function(data) {
// Whatever success call I want to make
}.bind(this)
})
}
And then in my Node app it would like something like this:
app.post('/my-node-endpoint', function(req, res) {
// Store the values we are posting as JSON
// Start the post request
// On End tell the React component everything is ok
// Prosper
});
As always, thanks for any help that is offered.
Your thought process looks right to me.
If the API you are calling is from a different domain, you will have to build a wrapper on your node server like you did here. Unless the external API supports cross-origin requests with no domain restrictions (such as MapBox web services), you will have to do this.
Several improvements to your code:
As far as I know, you can use React.findDOMNode(this.refs.testData).value instead of this.refs.testData.getDomNode().value. getDomNode() is deprecated in v0.13.
For all the AJAX calls, you can use the Store concept in Flux. The store keeps the states of the data, including updating data through AJAX request. In your React UI code, you just need to call the methods of the store, which makes your UI code clean. I usually create a store class myself without using Flux.