let's say I had this block of route, so far I only knew that middleware could be assigned through nuxt-config.js (globally) or per route (independently)
pages
- index.vue
- goSomeWhere.vue
- goThisWay.vue
- admin
- index.vue
- goThere.vue
- goHere.vue
I want to assign a middleware just for every /admin routes, so is there another approach that might be suitable for me?
Certainly the most concise way to verify a block of routes is to use a global middleware that targets any route starting with /admin.
You could set up a file inside the middleware folder that defines the redirects you need depending on the conditions. Obviously you want to block any admin route from someone who isn't logged in as an admin level user. To do this you should set any admin user in your store with a property such as "admin" or if you need to set levels you could assign a value of admin1, admin2 etc. For the sake of simplicity lets say any authorized user who logs in has a property admin = true; set in their user object in the store.
You should then create a file in the middleware folder, let's call it 'auth.js':
export default function ({store, redirect, route}) {
const userIsAdmin = !!store.state.user.admin;
const urlRequiresAuth = /^\/admin(\/|$)/.test(route.fullPath)
if (urlRequiresAuth && !userIsAdmin) {
return redirect('/')
}
return Promise.resolve
}
This simply checks if the user has admin set to true and if the requested route requires auth. It will redirect to your index page if the user is not authorized.
You will need to register your middleware file in nuxt.config.js:
...
router: {
middleware: ['auth'];
},
...
And you should be good to go.
Related
I am using Next.js and I have a folder learning inside my pages folder. Now, this learning folder has about 10 pages.
All these pages need to redirect to the index page if the user is not logged in. The following code does the job, but is there any other way to protect multiple pages, so that I don't need to add this same code again and again to all the pages ?
export async function getServerSideProps(context) {
//redirect to index page if not logged in
const session = await unstable_getServerSession(context.req, context.res, authOptions);
if (!session) {
return {
redirect: {
destination: '/',
permanent: false
}
}
}
}
I believe you are confused between protecting pages and protecting API ROUTES.
If you simply want to protect pages, you can indeed use middleware
However, if you wish to protect API Routes (e.g prevent a user from deleting data using your API endpoint and postman), I believe you need to use this unstable_getServerSession
Except creating reusable function, it's true that I didn't find anywhere in the doc how to set it for multiple paths in one folder only...
you can use middleware. docs: https://next-auth.js.org/configuration/nextjs#middleware
Create a middleware.ts (or .js) file at the root or in the src directory (same level as your pages).
If you only want to secure certain pages, export a config object with a matcher:
export { default } from "next-auth/middleware"
// otherwise your app would require authentication for all
export const config = { matcher: ["/dashboard"] }
Now you will still be able to visit every page, but only /dashboard
will require authentication.
If a user is not logged in, the default behavior is to redirect them
to the sign-in page.
that example is from the docs. You can also write a custom middleware
import { NextResponse } from "next/server";
export function middleware(req) {
const sessionCookie = req.cookies.get("session");
}
// you could add more if logic for other pages
if (req.nextUrl.pathname.startsWith("/admin ")) {
if (!sessionCookie) {
return NextResponse.redirect("/home");
}
}
I have a /profile route that should only be accessible by an authenticated user. From research, the recommended approach is to use vue-router's navigation guard:
Here is the route object:
{
path: '/profile',
name: 'MyProfile',
component: () => import('#/views/Profile.vue'),
meta: { requiresAuth: true }
},
And here is the router's navigation guard:
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (isAuthenticated()) {
next()
}
else {
alert('Auth required!')
next('/login')
}
}
})
The isAuthenticated() function above sends a jwt token (stored in cookie) to the /jwt/validate endpoint which validates the token and returns a 200 OK response:
export function isAuthenticated() {
axios.post(`${baseUrl}/token/validate`, {}, { withCredentials: true })
.then(resp => resp.statusText == 'OK')
.catch(err => false)
}
With this approach, every time we visit the /profile route, we are making a POST request to the /token/validate endpoint. And this works quite well. However, is it too excessive to make this request every time?
My Solutions
I wonder if there is some way to store the data locally in memory. I thought of two possible solutions:
Option 1: Storing the data on vuex store, however I have learned that the vuex store object is accessible via the browser's console. This means that anyone can modify the access logic to gain access to protected routes.
Option 2: Store it inside a custom Vue.prototype.$user object, however this is similarly accessible via console and therefore has the same security risk as option 1.
Essentially, my question is: is there an option to access a protected route without having to validate the jwt token on the server-side every time?
You should query the server for if the token is valid once when the user initially loads the application. After that, there is usually no reason to check again. If the session expires on the server, then any other API calls you do should return a 401 response (or any other way that you choose to return an error) and your application can act on that.and your application can act on that.
If your profile route is getting data from the server to display and the server is properly validating the user for that request, then it doesn't matter if the user tries to manipulate the Vuex store or Vue state because they won't be able to load the data.
Doing authentication in the vue router is really more for convenience than for actual security.
Don't waste time trying to prevent a malicious user from exploring the Vue application - that is guaranteed to be a losing battle since all of the code is loaded into the browser.
If you really insist on some kind of protection, you can split the application using webpack chunks that are loaded dynamically and configure your web server to only serve those chunks to properly authenticated and authorize users. That said, I would expect such configuration to be difficult and error prone, and I don't recommend it.
I'm wondering if it's possible to essentially "reevaluate" the middleware conditions without actually changing the current route.
The middleware's purpose is to prevent non-logged-in users from accessing the "dashboard".
My issue is, a user could become logged in or logged out without necessarily changing route but they wouldn't be redirected until they try and change pages.
I have a VueX action that triggers when the user's auth state changes but this (from what I can see), can't access the redirect or route variables.
// /mixins/auth.js
const reevaluateAuthStatus = (store, redirect, route) => {
console.log(route)
const redirectPolicy = route.meta.map((meta) => {
if (meta.auth && typeof meta.auth.redirectPolicy !== 'undefined') { return meta.auth.redirectPolicy[0] }
return []
})
const user = store.getters['auth/getUser']
if (redirectPolicy.includes('LOGGEDOUT')) {
if (user) {
return redirect('/dashboard')
}
} else if (redirectPolicy.includes('LOGGEDIN')) {
if (!user) {
return redirect('/login')
}
}
}
module.exports = {
reevaluateAuthStatus
}
// /middleware/auth.js
import { reevaluateAuthStatus } from '../mixins/auth'
export default function ({ store, redirect, route }) {
reevaluateAuthStatus(store, redirect, route)
}
Appreciate any help on this :)
You cannot re-evaluate a middleware AFAIK because it's mainly this (as stated in the documentation)
middlewares will be called [...] on the client-side when navigating to further routes
2 clean ways you can still achieve this IMO:
use some websockets, either with socket.io or something similar like Apollo Subscriptions, to have your UI taking into account the new changes
export your middleware logic to some kind of call, that you could trigger again by calling the $fetch hook again or any other data-related fetching hook in Nuxt
Some more ugly solutions would probably be:
making an internal setInterval and check if the actual state is still valid every 5s or so
move to the same page you are actually on with something like this.$router.go(0) as somehow explained in the Vue router documentation
Still, most of the cases I don't think that this one may be a big issue if the user is logged out, because he will just be redirected once he tries something.
As if the user becomes logged-in, I'm not even sure on which case this one can happen if he is not doing something pro-active on your SPA.
I don't know if it's relevant or not, but I solved a similar problem this way:
I have a global middleware to check the auth status. It's a function that receives Context as a parameter.
I have a plugin that injects itself into context (e.g. $middleware).
The middleware function is imported here.
In this plugin I define a method that calls this middleware passing the context (since the Plugin has Context as parameter as well): ctx.$middleware.triggerMiddleware = () => middleware(ctx);
Now the middleware triggers on every route change as intended, but I can also call this.$middleware.triggerMiddleware() everywhere I want.
I need to update a value which is initially set in the main.js for my vue application, from a component. I am not sure the best way (or even if this is possible)
I have tried setting the value directly form the component, but it does not appear to update.
I would be happy with just having it set properly when the app starts. I only need this to be set once, but I want it to read the server/host info when the app starts and populate the value.
Vue.use(Adal, {
// This config gets passed along to Adal, so all settings available to adal can be used here.
config: {
// 'common' (multi-tenant gateway) or Azure AD Tenant ID
tenant: 'xxxxxxxxxxxxxxxxxxxxxxxxx',
// Application ID
clientId: 'xxxxxxxxxxxxxxxxxxxxxxx',
// Host URI
redirectUri: 'http://localhost:8080',
cacheLocation: 'localStorage'
},
// Set this to true for authentication on startup
requireAuthOnInitialize: false,
// Pass a vue-router object in to add route hooks with authentication and role checking
router
});
The redirectUri value I would like to be dynamic based on the URL that the user used to connect to the site. This is part of the adal-vue library to handle AzureAD Auth for my App.
It appears that this value is only set ONCE and cannot be modified on the fly. Is there a workaround to allow for this?
I don't know what you do in Adal.install function, I think you can try to declare an option object which is passed to Vue.use(), then you can change the value in option, as it is passed by reference, the value will be changed in Adal...
const option = {
config: {
redirectUri: 'http://localhost:8080',
}
Vue.use(Adal, option);
// then change it in somewhere
option.config.redirectUri = 'http://localhost:9000'
Basically I have a login/register landing page of my app. After a user logs in or registers I don't want them to be able to access this route anymore. I'm not sure how to achieve this with Hapi.js. The login page doesn't use any auth strategies so it has no idea if a user is logged in or not.
The approach I normally use for this is not disabling the route per se, but redirecting logged-in users away from the login route.
As you point out correctly your login route currently doesn't know whether a user is logged in if it has no auth strategy configured. The solution is to add an auth strategy to the login route, but using the try mode. This means the handler will be executed regardless of whether auth was successful. The trick is that you can then check if the user is authenticated (by inspecting the value of request.auth.isAuthenticated) or not and react accordingly.
So your route might look like this:
server.route({
config: {
auth: {
strategy: 'session',
mode: 'try'
}
},
method: 'GET',
path: '/login',
handler: function (request, reply) {
if (request.auth.isAuthenticated) {
return reply.redirect('/'); // user is logged-in send 'em away
}
return reply.view('login'); // do the login thing
}
});
Another approach with the same result is to set your auth strategy on try mode as the default for all routes:
server.auth.strategy('session', 'cookie', 'try', {
password: 'password-that-is-longer-than-32-chars',
isSecure: true
});
Notice that third argument try. With this approach you don't need to add any auth config to the route itself because it will try this strategy by default. According to the server.auth.strategy docs:
mode - if true, the scheme is automatically assigned as a required strategy to any route without an auth config. Can only be assigned to a single server strategy. Value must be true (which is the same as 'required') or a valid authentication mode ('required', 'optional', 'try'). Defaults to false.
There's some more info about modes in the Authentication tutorial on the hapi site.