I already searched for a good while on the Internet and even checked all suggested questions here, but I found nothing.
Basically, I'm using vue-router to load views when the user clicks on them (without prefetching, just lazy-loading), using a function that imports the Vue view/component. To better visualize, I made a barebone example of what I'm doing:
const router = new VueRouter({
routes: [
...
{
path: "/a_page",
component: function() {
return import("./views/A_Page.vue");
}
}
]
});
I'm using Express in the backend to protect certain routes, because protecting it in the Frontend is wasted effort, since the user could bypass the 'protection' easily, if he wants to. Also all views have their own splitted .js file (using "webpackChunkName") and Express needs a Bearer Authentication Token header for every API call OR .js file requested. This works great with Axios (responsible for fetching API data) where you can manually define a header, but vue-router hasn't this option, and since it doesn't send the Authorization header, it doesn't authenticate, Express blocks the file with a 403 and vue-router fails to import the file.
Is there any way to send the Authorization header with the import (which is basically just a GET request)?
Thanks in advance.
If someone thinks I'm approaching the problem in a wrong way, feel free to comment and suggest another way.
EDIT: The suggested duplicate question was given too little attention and the only solution given (which is basically split in 2) doesn't work with the current webpack anymore; onload(event) and onerror(event) get undefined.
You could use a router guard instead of protecting with basic auth.
I use this method, along with lazy loaded routes. If the auth fails you can redirect the user to a login page.
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
If (auth) { // get value of cookie etc for a jwt token or similar auth method
next() // proceed
}
else {
next(false) // cancel navigation
// or
next(“/login-url”) // redirect you login url
}
})
Additionally, you could use an axios method to auth on each route change.
If you want to send up the Authorization header (which doesn't seem to be an easy task, given that no one knows how to go about it...) I think you could override webpack's jsonp function that it uses to load the chunks in splitChunks...
Here's the docs for the webpack function that loads the chunks
You'll override your webpack config with your modified chunk loading function and then tie that into your vue.config.js like so...
// vue.config.js
module.exports = {
configureWebpack: require('./webpack.config.js')
}
All this being said, I would suggest protecting your frontend assets much earlier than when you need to be loading your split chunks and not requiring the Authorization header to serve your static assets.
Sometimes you can do this at the network layer (load balancer, etc) depending on your use-case. Other times using a server-based approach, like rendering your app w/ Nuxt, will be what you want.
If I'm understanding correctly (feel free to correct me), would you be able to do an auth call with axios prior to the actual routing, or perhaps upon the routing using a mounted call? Especially if there is no valid authentication you can then either redirect to a login page or re-route to an error page.
Feel free to let me know if I'm misunderstanding.
Related
Is there a way using a middleware function or some other method in Next.js to see if a user is attempting to go to the home page?
What I'm trying to do basically is intercept a user's request for the home page. Intercepting a URL request is relatively easy to do with Next.js middleware. For example, if you want to see if a user is trying to access a single page called /login, you can access the request url like so:
export default async function middleware(req, res){
const url = req.url;
if (url.includes('/login')){
// carry out action
}
}
However, how can this be done for a home page URL (e.g https://fakewebsite.com/ or in development, localhost:3000)?
I think you can use the middleware introduced in nextjs version 12 to accomplish this. There you can introduce a config for matching URLs and have access to user's request:
Next.JS middleware.
The problem with what you have is that you're missing the config export from your middleware, inside which you can specify the matcher:
// middleware.js
export const config = {
matcher: '/',
}
This will only allow home requests to reach your middleware.
I see plenty of others have had issues with facebook appending this to the callback uri and saw some good solutions for handling this client side with js embedded in the page. I have a nodejs api that handles the actual authorization with passportjs and passes it to our frontend (nuxtjs based on vue 2). The client stalls when trying to access/redirect any routes after being sent to the uri with the appended
I was hoping I could fix it with
//dashboard.vue
mount(){
if (window.location.hash == "#_=_"){
window.location.hash = ''
}
}
but this doesn't seem to fix anything with the router. I also tried adding a redirect to the vue router
//.nuxt/router.js
{ path: "/dashboard#_=_", redirect: "/dashboard" }
but the vue router dynamically generates on runtime so anything I hard-code there gets undone.
From what I undestand this is strictly a client side problem, but maybe I'm missing some parameter in the Facebook API call that could prevent this?
//backend api calling fb api
app.get('/facebook', passport.authenticate('facebook')) //not specifying scope since still in dev and haven't applied for approval
Is there any way to make next.js API routes response data hidden when accessing it through URL? I want to hide the routes because there is some data I don't want to be directly accessed by the user.
Probably quick & simple way to protect the API routes is through the stateless session management libraries like iron-session with save / creation and destroy endpoints to validate and invalidate the Next JS api routes
Try this github example by Vercel. This might a be good starting point.
Remember: Always use a best authentication mechanism to protect any direct api route call with appropriate privileges in place. DYOR
There is no way to hide API routes from users through url in nextjs. In fact, nextjs API routes are publically available to anyone when you host the website without exporting and hosting from out folder. I ended making server-side routes using node express and then connected to the frontend in nextjs.
It is extremely unworthy effort to hide API routes. and for protecting essential data in API..there is CORS and Authentication methods can prevent noicy unwanted traffic I found brilliant blog on this
https://dev.to/a7u/how-to-protect-nextjs-api-routes-from-other-browsers-3838
You can set an authorization header that checks auth key everytime user access that API, that way normal user wont be able to access the page without knowing the auth key
In Next.js version 13 you can use middleware to stuck the user from directly checking the route of your api by checking the req.referer then only your app can call and api of your app. Auth token can also be use inside middleware.
https://nextjs.org/blog/next-13-1#nextjs-advanced-middleware
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(req: NextRequest) {
const url = req.nextUrl
const { pathname } = url
if (pathname.startsWith(`/api/`)) {
if (!req.headers.get("referer")?.includes(process.env.APP_URL as string)) {
return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
}
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!_next|fonts|examples|svg|[\\w-]+\\.\\w+).*)'],
}
process.env.APP_URL is the url of your app for example : http://localhost:3000
app.use("*", topUsers) is called multiple times..
topUsers is being called multiple times
function topUsers(req, res, next){
console.log("req.url", req.url)
findMostUsefullReviewsAggregate(6)
.then(function(aggregationResult){
// console.log("aggregationResult", aggregationResult)
// console.log("***aggregationResult::", aggregationResult)
return populateMostUseful(aggregationResult)
})
.then(function(populated){
console.log("<<<<<<<<<<<<<<<<<<TOPUSER CALLED >>>>>>>>>>>>")
// console.log("POPULATED: ", populated);
console.log(">>>populateMostUseful.length");
populated = populated.map(function(e){
e.momented = moment(e.createdAt.getTime()).fromNow();
return e;
})
req.session.mostUsefulReviews = populated;
// res.end()
})
.catch(function(err){
console.log("HEADERERR", err);
});
next();
}
( some info for later: main.ejs is for ("/"))
when I change it to app.get("/", topUsers) and go to "/"it is only called once (which is what I want).
the console.log for req.url shows "/" for all 3
In my main.ejs I include a header.ejs. I thought that might be a problem. Maybe the request to the header for the include was a problem but I don't think so.
Quick question : If I do app.get("/") would that work on all subroutes like /users/idofuser? I think If I do that the function doesn't get called.
I think app.get("*") also gives me the same problem with the multiple calls.
Edit: when I put next in the .then() it still gets called multiple times.
my real goal it to have something happen on all routes. I already have routes set up. I don't want to go to each route and do something like app.get("/", onemiddleware, topUsers, ). I don't want to put topUSers on each one physically. Do I have to.
If you are doing this from a browser loading a web page, the browser is probably requesting multiple resources from the website.
For starters, it often requests the favicon and if there are any other resources in the web page (other scripts, stylesheets, images, etc...) each of those will be a separate request to your web page. You can see exactly what URL is being requested by logging req.url for each request. From a single page load, there should be no reason you would get three requests for /, but you very well could get a request for / follow by other requests for some other resource (and consequently some other URL) from the same server.
Show us the web page you are loading and we can better point out what's going on.
If what you're really trying to do is to share a middleware on a set of routes, then you can create a router, put the middleware on the router with router.use(), define all the routes that you want to have that middleware on that specific router and then hook the router into your app. This will execute the middleware only for routes that match that router and you only have to specify the middleware once for the router and all routes defined on that router will use that middleware. Other routes defined directly on the app object or on other routers will not use the middleware. You will have to define the router as some unique path root or wildcard that helps express know which routes should be passed into your router. We'd have to see the whole universe of paths you plan to use that both do and don't use that middleware for us to know more specifically what to suggest.
I currently have a set-up based on the meanjs stack boilerplate where I can have users logged in this state of being 'logged-in' stays as I navigate the URLs of the site. This is due to holding the user object in a Service which becomes globally available.
However this only works if I navigate from my base root, i.e. from '/' and by navigation only within my app.
If I manually enter a URL such as '/page1' it loses the global user object, however if I go to my root homepage and navigate to '/page1' via the site. Then it's fine, it sees the global user object in the Service object.
So I guess this happens due to the full page refresh which loses the global value where is navigating via the site does not do a refresh so you keep all your variables.
Some things to note:
I have enabled HTML5Mode, using prefix of '!'.
I use UI-Router
I use a tag with '/'
I have a re-write rule on express that after loading all my routes, I have one last route that takes all '/*' to and sends back the root index.html file, as that is where the angularjs stuff is.
I'm just wondering what people generally do here? Do they revert the standard cookies and local storage solutions? I'm fairly new to angular so I am guessing there are libraries out there for this.
I just would like to know what the recommended way to deal with this or what the majority do, just so I am aligned in the right way and angular way I suppose.
Update:
If I manually navigate to another URL on my site via the address bar, I lose my user state, however if I manually go back to my root via the address bar, my user state is seen again, so it is not simply about loosing state on window refresh. So it seems it is related to code running on root URL.
I have an express re-write that manually entered URLs (due to HTML5 Location Mode) should return the index.html first as it contains the AngularJs files and then the UI-Route takes over and routes it properly.
So I would have expected that any code on the root would have executed anyway, so it should be similar to navigating via the site or typing in the address bar. I must be missing something about Angular that has this effect.
Update 2
Right so more investigation lead me to this:
<script type="text/javascript">
var user = {{ user | json | safe }};
</script>
Which is a server side code for index.html, I guess this is not run when refreshing the page to a new page via a manual URL.
Using the hash bang mode, it works, which is because with hash bang mode, even I type a URL in the browser, it does not cause a refresh, where as using HTML5 Mode, it does refresh. So right now the solution I can think of is using sessionStorage.
Unless there better alternatives?
Update 3:
It seems the best way to handle this when using HTML5Mode is that you just have to have a re-write on the express server and few other things.
I think you have it right, but you may want to look at all the routes that your app may need and just consider some basic structure (api, user, session, partials etc). It just seems like one of those issues where it's as complicated as you want to let it become.
As far as the best practice you can follow the angular-fullstack-generator or the meanio project.
What you are doing looks closest to the mean.io mostly because they also use the ui-router, although they seem to have kept the hashbang and it looks like of more of an SEO friendly with some independant SPA page(s) capability.
You can probably install it and find the code before I explained it here so -
npm install -g meanio
mean init name
cd [name] && npm install
The angular-fullstack looks like this which is a good example of a more typical routing:
// Server API Routes
app.route('/api/awesomeThings')
.get(api.awesomeThings);
app.route('/api/users')
.post(users.create)
.put(users.changePassword);
app.route('/api/users/me')
.get(users.me);
app.route('/api/users/:id')
.get(users.show);
app.route('/api/session')
.post(session.login)
.delete(session.logout);
// All undefined api routes should return a 404
app.route('/api/*')
.get(function(req, res) {
res.send(404);
});
// All other routes to use Angular routing in app/scripts/app.js
app.route('/partials/*')
.get(index.partials);
app.route('/*')
.get( middleware.setUserCookie, index.index);
The partials are then found with some regex for simplicity and delivered without rendering like:
var path = require('path');
exports.partials = function(req, res) {
var stripped = req.url.split('.')[0];
var requestedView = path.join('./', stripped);
res.render(requestedView, function(err, html) {
if(err) {
console.log("Error rendering partial '" + requestedView + "'\n", err);
res.status(404);
res.send(404);
} else {
res.send(html);
}
});
};
And the index is rendered:
exports.index = function(req, res) {
res.render('index');
};
In the end I did have quite a bit of trouble but managed to get it to work by doing few things that can be broken down in to steps, which apply to those who are using HTML5Mode.
1) After enabling HTML5Mode in Angular, set a re-write on your server so that it sends back your index.html that contains the Angular src js files. Note, this re-write should be at the end after your static files and normal server routes (e.g. after your REST API routes).
2) Make sure that angular routes are not the same as your server routes. So if you have a front-end state /user/account, then do not have a server route /user/account otherwise it will not get called, change your server-side route to something like /api/v1/server/route.
3) For all anchor tags in your front-end that are meant to trigger a direct call to the server without having to go through Angular state/route, make sure you add a 'target=_self'.