I am completely new in web development and I'm trying to add HTTP security headers to my Next.js app but if I check it in securityheader.com it doesn't seem to work.
const express = require("express");
const next = require("next");
const helmet = require("helmet");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
server.use(helmet());
server.use(helmet.xframe());
server.use(helmet.hsts());
server.get("*", (req, res) => {
return handle(req, res);
});
server.listen(3000, err => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch(ex => {
console.error(ex.stack);
process.exit(1);
});
I'm looking for information for 4 days already and I still didn't find any example how to do it and I can't figure it out what exactly I'm doing wrong.
I found 2 solutions:
You can use Jaga Apple's next-secure-headers npm package which can be found here on GitHub. This is basically a TypeScript-based wrapper that can be used on the whole App component or on individual Page components, by accessing a request object during the Next.js getInitialProps lifecycle, which unfortunately also forms this issue. You will also need to know a little bit of TypeScript to use it, as well as having to install TypeScript according to these Next.js instructions, or this Next.js tutorial.
The above solution, however, currently does not work if you are deploying your Next.js project to Vercel (which used to be Zeit/Now), which is what I personally prefer since Next.js was created for Vercel. The solution I found for this case was to implement a custom routes/headers configuration in now.json, for example:
{
..., // <-- Other configuration options
"routes": [
{
"src": "/.*",
"headers": {
"Content-Security-Policy": "default-src 'self'; img-src 'self' https://cdn.sanity.io; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'",
"Feature-Policy": "'none'",
"Referrer-Policy": "same-origin",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "deny",
"X-XSS-Protection": "1"
},
"continue": true
}
]
}
Content-Security-Policy is not enabled by default in helmet, to enable it use
server.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", 'maxcdn.bootstrapcdn.com']
}
}))
to enable X-Frame-Options
server.use(frameguard({
action: 'allow-from',
domain: 'http://example.com'
}))
For more info, check lib docs
Related
I'm building a Vue/Nuxt (2) site running against a .NET Web Api. This site is already deployed in a staging capacity and is building and running as a statically generated site on Netlify. Now, I know it's not quite right as my content is not being rendered into the deployed files so effectively it's running as a SPA. Not quite what I saw happening in Dev at the time 5 weeks ago but I didn't think anything of it, I'd fix it later.
I've finally got a chance to work on this project again and proceeded to make the necessary changes so the content should be fetched via my dynamic route builder in nuxt.config.js (existing) and output during build via the asyncData hook in my pages (new).
Nuxt.config
// Generate dynamic page routes
let dynamicRoutes = async () => {
console.log( `${ process.env.API_BASE_URL }/page/main/generate` );
const fetchedConditions = await axios.get( `${ process.env.API_BASE_URL }/page/main/generate` );
const routesForConditions = fetchedConditions.data.map( ( condition ) => {
return {
route: `/conditions/${ condition.id }/${ condition.urlPath }`,
payload: condition
}
} );
console.log( `${ process.env.API_BASE_URL }/faq/top/generate?count=10` );
const fetchedFaqs = await axios.get( `${ process.env.API_BASE_URL }/faq/top/generate?count=10` );
const routesForFaqs = fetchedFaqs.data.map( ( faq ) => {
return {
route: `/frequently-asked-questions/${ faq.categoryId }/${ faq.id }/${ faq.urlPath }`,
payload: faq
}
} );
const routes = [ ...routesForConditions, ...routesForFaqs ];
return routes;
}
export default {
target: 'static',
ssr: false,
generate: {
crawler: true,
routes: dynamicRoutes
},
server: {
port: 3001
}...
Condition page
async asyncData(ctx) {
util.debug('Async data call...');
if (ctx.payload) {
ctx.store.dispatch("pages/storePage", ctx.payload);
return { condition: ctx.payload };
} else {
const pageResponse = await ctx.store.dispatch('pages/getCurrentPage', { pageId: ctx.route.params.id });
return { condition: pageResponse };
}
}
So far so good except now, when I try to generate the site in development i.e. "npm run generate", the dynamic route generator code cannot reach my local API running as HTTPS and fails with a "Nuxt Fatal Error: self signed certificate".
https://localhost:5001/api/page/main/generate 12:06:43
ERROR self signed certificate 12:06:43
at TLSSocket.onConnectSecure (node:_tls_wrap:1530:34)
at TLSSocket.emit (node:events:390:28)
at TLSSocket._finishInit (node:_tls_wrap:944:8)
at TLSWrap.ssl.onhandshakedone (node:_tls_wrap:725:12)
This worked 5 weeks ago and as far as I am aware I have not changed anything that should impact this. No software or packages have been updated (except windows updates perhaps). The API is still using .NET 5.0 and running on Kestrel using the default self signed cert on localhost (which is listed as valid in Windows). I simply added the payload to the routes, added the asyncData hook, and modified the page code accordingly.
I've Googled a few tidbits up but none have resolved the issue and now I'm at a loss. It really shouldn't be this blimmin opaque in 2022.
Tried disabling SSL via a proxy in nuxt.config;
proxy: {
'/api/': {
target: process.env.API_BASE_URL,
secure: !process.env.ENV === 'development'
}
}
Tried modifying my Axios plugin to ignore auth;
import https from 'https';
export default function ( { $axios } ) {
$axios.defaults.httpsAgent = new https.Agent( { rejectUnauthorized: false } );
}
And a variation of;
import https from 'https';
export default function ( { $axios, store } ) {
const agent = new https.Agent( {
rejectUnauthorized: false
} );
$axios.onRequest( config => {
if ( process.env.dev )
{
config.httpsAgent = agent;
}
} );
}
None of these 'worked for other people' solutions is working for me.
Also, the client side apps (public/admin) themselves have no problem working against my API locally, it's only the route builder within nuxt.config or asyncData code which is throwing this error.
Any suggestions would be appreciated. Happy to add other relevant code if needed, just not sure which atm.
I want to send a POST request to an external API with axios in a nuxt projekt where I use the nuxt auth module.
When a user is authenticated axios seems to automatically add an authorization header (which is fine and often required for calls to my backend API). However, when doing calls to an external API the header might not be accepted and cause the call to fail.
Is there any way to specify for which URLs the auth header should be added or excluded?
Here are the configurations of the auth and axios module in my nuxt.config
// Axios module configuration
axios: {
baseURL: '//localhost:5000',
},
// Auth module configuration
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/auth/login', method: 'post', propertyName: 'token' },
logout: { url: '/auth/logout', method: 'delete' },
user: { url: '/auth/user', method: 'get', propertyName: 'user' },
},
},
},
}
Some more background:
In my particular usecase I want to upload a file to an Amazon S3 bucket, so I create a presigned upload request and then want to upload the file directly into the bucket. This works perfectly fine as long as the user is not authenticated.
const { data } = await this.$axios.get('/store/upload-request', {
params: { type: imageFile.type },
})
const { url, fields } = data
const formData = new FormData()
for (const [field, value] of Object.entries(fields)) {
formData.append(field, value)
}
formData.append('file', imageFile)
await this.$axios.post(url, formData)
I tried to unset the Auth header via the request config:
const config = {
transformRequest: (data, headers) => {
delete headers.common.Authorization
}
}
await this.$axios.post(url, formData, config)
This seems to prevent all formData related headers to be added. Also setting any header in the config via the headers property or in the transformRequest function does not work, which again causes the call to the external API to fail obviously (The request will be sent without any of these specific headers).
As I'm working with the nuxt axios module I'm not sure how to add an interceptor to the axios instance as described here or here.
Any help or hints on where to find help is very much appreciated :)
Try the following
Solution 1, create a new axios instance in your plugins folder:
export default function ({ $axios }, inject) {
// Create a custom axios instance
const api = $axios.create({
headers: {
// headers you need
}
})
// Inject to context as $api
inject('api', api)
}
Declare this plugin in nuxt.config.js, then you can send your request :
this.$api.$put(...)
Solution 2, declare axios as a plugin in plugins/axios.js and set the hearders according to the request url:
export default function({ $axios, redirect, app }) {
const apiS3BaseUrl = // Your s3 base url here
$axios.onRequest(config => {
if (config.url.includes(apiS3BaseUrl) {
setToken(false)
// Or delete $axios.defaults.headers.common['Authorization']
} else {
// Your current axios config here
}
});
}
Declare this plugin in nuxt.config.js
Personally I use the first solution, it doesn't matter if someday the s3 url changes.
Here is the doc
You can pass the below configuration to nuxt-auth. Beware, those plugins are not related to the root configuration, but related to the nuxt-auth package.
nuxt.config.js
auth: {
redirect: {
login: '/login',
home: '/',
logout: '/login',
callback: false,
},
strategies: {
...
},
plugins: ['~/plugins/config-file-for-nuxt-auth.js'],
},
Then, create a plugin file that will serve as configuration for #nuxt/auth (you need to have #nuxt/axios installed of course.
PS: in this file, exampleBaseUrlForAxios is used as an example to set the variable for the axios calls while using #nuxt/auth.
config-file-for-nuxt-auth.js
export default ({ $axios, $config: { exampleBaseUrlForAxios } }) => {
$axios.defaults.baseURL = exampleBaseUrlForAxios
// I guess that any usual axios configuration can be done here
}
This is the recommended way of doing things as explained in this article. Basically, you can pass runtime variables to your project when you're using this. Hence, here we are passing a EXAMPLE_BASE_URL_FOR_AXIOS variable (located in .env) and renaming it to a name that we wish to use in our project.
nuxt.config.js
export default {
publicRuntimeConfig: {
exampleBaseUrlForAxios: process.env.EXAMPLE_BASE_URL_FOR_AXIOS,
}
}
I have panel is developed by vuejs and vuetify that uses of v-router for managing routes in client-side, actually I had developed approximately 70% of my panel and in and due to the final change, I have to handle the server post request only to return from the payment page.
I want to use NUXT for SSR, but the problem is that NUXT ignores all my past routes because it does not have these routes itself.
My question is, can NUXT only be used to load just one page after server request to client while my panel work properly in the client-side?
I am new to web programming and I hope I have asked the right question. Anyway, thank for all your helps.
I added nossr.vue in pages directory without index.vue file(This is the only component in this directory and the rest of my vue components are placed in the Components directory).
I use of [https://stackoverflow.com/questions/54289615/how-to-read-post-request-parameters-in-nuxtjs][1] for custom middleware and post request handle class.
Here is postRequestHandle.js:
const querystring = require('querystring');
module.exports = function (req, res, next) {
let body = '';
req.on('data', (data) => {
console.log("post request data",data)
body += data;
});
req.on('end', () => {
req.body = querystring.parse(body) || {};
console.log("post request handler",req.body)
next();
});
};
and nuxt.config.js
export default {
devServer: {
host: 'mypanel.test.net',
port: 8080,
disableHostCheck: true,
hotOnly: true,
},
serverMiddleware: [
{ path: '/clickout', handler: './server-middleware/postRequestHandler.js' },
],
layout(context) {
return 'nossr'
}
}
Create an extra layouts in Nuxt called nossr.vue, make it's first element a <client-only> element. On every page except the SSR rendered page you put this:
export default {
layout: 'nossr'
}
For more information regarding layouts in Nuxt, please look at this https://nuxtjs.org/guides/directory-structure/layouts/
You can disable SSR for every page except your particular page by setting res.spa = true in the serverMiddleware
export default function(req, res, next) {
if (req.url !== '/clickout') {
res.spa = true;
}
next();
}
See also:
Nuxt Issue: Per-route ssr
I have a very basic nuxt.js application using JSON in a local db.json file, for some reason the generated static site links leading to network error, but I can access them from the url or page refresh.
nuxt config
generate: {
routes () {
return axios.get('http://localhost:3000/projects')
.then((res) => {
return res.data.map((project) => {
return '/project/' + project.id
})
})
}
},
main root index page
data() {
return {
projects: []
}
},
async asyncData({$axios}){
let projects = await $axios.$get('http://localhost:3000/projects')
return {projects}
}
single project page
data() {
return {
id: this.$route.params.id
}
},
async asyncData({params, $axios}){
let project = await $axios.$get(`http://localhost:3000/projects/${params.id}`)
return {project}
}
P.S. I have edited the post with the code for the main and single project page
Issues with server-side requests of your application are caused by conflicts of ports on which app and json-server are running.
By default, both nuxt.js and json-server run on localhost:3000 and requests inside asyncData of the app sometimes do not reach correct endpoint to fetch projects.
Please, check fixed branch of your project's fork.
To ensure issue is easily debuggable, it is important to separate ports of API mock server and app itself for dev, generate and start commands.
Note updated lines in nuxt.config.js:
const baseURL = process.env.API_BASE_URL || 'http://localhost:3000'
export default {
server: {
port: 3001,
host: '0.0.0.0'
},
modules: [
['#nuxtjs/axios', {
baseURL
}]
],
generate: {
async routes () {
return axios.get(`${baseURL}/projects`)
.then((res) => {
return res.data.map((project) => {
return '/project/' + project.id
})
})
}
}
}
This ensures that API configuration is set from a single source and, ideally, comes from environmental variable API_BASE_URL.
Also, app's default port has been changed to 3001, to avoid conflict with json-server.
asyncData hooks have been updated accordingly to pass only necessary path for a request. Also, try..catch blocks are pretty much required for asyncData and fetch hooks, to handle error correctly and access error specifics.
I'm using an API with my Nuxt, here is my nuxt.config.js to allow the requests :
axios: {
prefix: '/api/',
proxy: true,
},
proxy: {
'/api/': {
target: process.env.API_URL || 'http://localhost:1337',
pathRewrite: {
'^/api/': '/',
}
},
}
On a specific page of my app, I need to send requests to another API from another domain. I'm using axios direclty in this Vue component :
axios.post('mysite.com/widget.rest')
As response, I obtain CORS error. Can I allow multiple API in my config ?
If you mean being able to access APIs under different URLs, AFAIK it's not possible out of the box. We tried adding proxy to different targets, but it only worked on client side, not during SSR.
What we ended up doing is having the default axios instance for our main API, and creating a plugin that creates extra axios instances for our other APIs for SSR.
Add extra "suburl" to proxy - this sorts out client side and means no CORS issues.
proxy: {
'/forum/': {
target: 'https://other.domain.com/',
pathRewrite: {'^/forum/': '/'}
},
'/api/': ...
},
For SSR to work, axios should hit directly the other API
import Vue from 'vue'
var axios = require('axios');
const forumAxios = axios.create(process.client ? {
baseURL: "/"
} : {
baseURL: 'https://other.domain.com/'
});
// this helps Webstorm with autocompletion, otherwise should not be needed
Vue.prototype.$forumAxios = forumAxios;
export default function (context, inject) {
if (process.server) {
forumAxios.interceptors.request.use(function (config) {
config.url = config.url.replace("/forum/", "");
return config;
}, function (error) {
return Promise.reject(error);
});
}
inject('forumAxios', forumAxios);
In components, you can then use something like:
async asyncData({app}) {
let x = await app.$forumAxios.get("/forum/blah.json");
You can use process.env prop of course instead of hardcoded URL.
This will hit https://other.domain.com/blah.json.