I'm using node.js with Express and connect-auth to authenticate users.
This is the verification when requesting /index:
if(req.isAuthenticated()) {
res.redirect('/dashboard');
} else {
res.render('index', { layout: 'nonav' });
}
However, after logging out and going back to f.e. '/dashboard', I can see the dashboard.
How can I put the authentication check to every request to make sure there's a valid user at all times?
Update
I don't have any problems with the authentication, everything works fine! I need a solution which checks every route/request if there's a valid user, without putting a function or if-statement in the route-implementation, as the whole App needs a valid user anyway. The Express-Authentication-Example uses "restrict" in the route-definition, which is close, but with many routes it can easily be forgotten.
app.all('*',function(req,res,next){
if(req.isAuthenticated()){
next();
}else{
next(new Error(401)); // 401 Not Authorized
}
});
// NOTE: depending on your version of express,
// you may need to use app.error here, rather
// than app.use.
app.use(function(err,req,res,next){
// Just basic, should be filled out to next()
// or respond on all possible code paths
if(err instanceof Error){
if(err.message === '401'){
res.render('error401');
}
}
});
If you define the all route before routes which require authentication and after routes which do not (such as the home page, login, etc) then it should only affect the routes that need it. Alternatively you could use a RegExp instead of '*', which would include a subpath or list of paths that require authentication.
Another option would be to create a function to include in each route that requires auth:
function IsAuthenticated(req,res,next){
if(req.isAuthenticated()){
next();
}else{
next(new Error(401));
}
}
app.get('/login',function(req,res,next){
res.render('login');
});
app.get('/dashboard',IsAuthenticated,function(req,res,next){
res.render('dashboard');
});
app.get('/settings',IsAuthenticated,function(req,res,next){
res.render('settings');
});
You can use sessions mechanism provided by connect. Put this code in app.configure() to enable it:
app.use(express.cookieParser());
app.use(express.session({
secret: 'some string used for calculating hash'
}));
After that, you′ll be able to use req.session object (different for each request) to store your authentication data (or anything else). So, your example code will look something like this:
if (req.session && req.session.authorized) {
res.redirect('/dashboard');
}
else {
res.render('index', {layout: 'nonav'});
}
And authentication will look like this:
req.session.authorized = checkPassword(login, passw);
Logout:
req.session.destroy();
More info can be found here.
Another way is to app.use a middleware function. (Example in CoffeeScript.)
# middleware
authKick = (req, res, next) ->
if not do req.isAuthenticated then return res.redirect '/login'
return do next
# apply
app.use authKick
This will work on each request without having to touch the routes.
Related
I'm new to next.js and I wanted to know if I could protect a whole API route via middleware. So for example if i wanted to protect /api/users Could I create /api/users/_middleware.ts and handle authentication in the middleware and not have to worry about authentication in the actual api endpoints? If so, how would I go about doing that? The library i'm using right now is #auth0\nextjs-auth0 so I guess it would look something like this? (Also please forgive me if I code this wrong, I am doing this in the stackoverflow editor)
export default authMiddleware(req,res)=>{
const {user,error,isLoading} = whateverTheNameOfTheAuth0HookIs()
if(user)
{
// Allow the request to the api route
}
else
{
// Deny the request with HTTP 401
}
}
Do I have the general idea correct?
next-auth v4 introduced middleware for this purpose. The basic use case is pretty simple.
You can add a middleware.js file with the following:
export { default } from "next-auth/middleware"
export const config = { matcher: ["/dashboard"] }
Other use cases can be found in the documentation
You can use middleware for that, something similar to this example from the documentation.
For a sub-directory inside pages, you can create a _middleware.ts file. It will run for all pages in this directory. It looks something like this:
import { NextRequest, NextResponse } from 'next/server'
export function middleware(req: NextRequest) {
const basicAuth = req.headers.get('authorization')
if (basicAuth) {
// do whatever checks you need here
const hasAccess = ...
if (hasAccess) {
// will render the specified page
return NextResponse.next()
}
}
// will not allow access
return new Response('No access', {
status: 401,
headers: {
'WWW-Authenticate': 'Basic realm="Secure Area"',
},
})
}
You can find more info in the documentation.
I am doing user authentication, but I ran into a problem. First, when loading, the middleware is loaded, it does not see the authorized user through $fire.auth.onAuthStateChanged. And if you go to another page (without reloading page), user is appear. How to make him see it at the first boot?
Here is my middleware
export default function ({app, route, redirect}) {
console.log('middleware')
app.$fire.auth.onAuthStateChanged(user => {
if (user) {
console.log('user+')
if (route.path === perm.signin || route.path === perm.signup) {
return redirect('/')
}
} else {
console.log('user-')
if (route.path !== perm.signin || route.path !== perm.signup) {
return redirect(perm.signin)
}
}
})
}
and what I received (1st pic) when first enter to app in console.log(middleware, user-). But if I go to another page I receive middleware, user+.
I need user + to be on the first start
onAuthStateChanged fires the callback you provided as argument after the middleware has run.
In other words the callback's return statements are not being run when the middleware runs.
You could either ensure the middleware is called after the first authentication attempt, but this would slow down the initial startup of the application. So you could expose the nuxt router to the onAuthStateChanged handler and router.push('/login') or router.push('/somewhere') from there.
We're switching from SPA to statically generated, and are running into a problem with middleware.
Basically, when Nuxt is statically rendered, middleware is run on the build server first, and then is run after each page navigation client side. The important point is that middleware is not run client side on first page load. This is discussed here
We work around this for some use cases by creating a plugin that uses the same code, since plugins are run on the first client load.
However, this pattern doesn't work well for this use case. The following is an example of the middleware that we want to use:
// middleware/authenticated.js
export default function ({ store, redirect }) {
// If the user is not authenticated
if (!store.state.authenticated) {
return redirect('/login')
}
}
// Inside a component
<template>
<h1>Secret page</h1>
</template>
<script>
export default {
middleware: 'authenticated'
}
</script>
This example is taken directly from the Nuxt docs.
When rendered statically, this middleware is not called on first page load, so a user might end up hitting their dashboard before they've logged in, which causes problems.
To add this to a plugin, the only way I can think to do this is by adding a list of authenticated_routes, which the plugin could compare to and see if the user needs to be authed.
The problem with that solution though is that we'd then need to maintain a relatively complex list of authed pages, and it's made worse by having dynamic routes, which you'd need to match a regex to.
So my question is: How can we run our authenticated middleware, which is page specific, without needing to maintain some list of routes that need to be authenticated? Is there a way to actually get the middleware associated to a route inside a plugin?
To me it is not clear how to solve it the right way. We are just using the static site generation approach. We are not able to run a nuxt middleware for the moment. If we detect further issues with the following approach we have to switch.
One challenge is to login the user on hot reload for protected and unprotected routes. As well as checking the login state when the user switches the tabs. Maybe session has expired while he was on another tab.
We are using two plugins for that. Please, let me know what you think.
authRouteBeforeEnter.js
The plugin handles the initial page load for protected routes and checks if the user can access a specific route while navigating around.
import { PROTECTED_ROUTES } from "~/constants/protectedRoutes"
export default ({ app, store }) => {
app.router.beforeEach(async (to, from, next) => {
if(to.name === 'logout'){
await store.dispatch('app/shutdown', {userLogout:true})
return next('/')
}
if(PROTECTED_ROUTES.includes(to.name)){
if(document.cookie.indexOf('PHPSESSID') === -1){
await store.dispatch('app/shutdown')
}
if(!store.getters['user/isLoggedIn']){
await store.dispatch('user/isAuthenticated', {msg: 'from before enter plugin'})
console.log('user is logged 2nd try: ' + store.getters['user/isLoggedIn'])
return next()
}
else {
/**
* All fine, let him enter
*/
return next()
}
}
return next()
})
}
authRouterReady.js
This plugin ment for auto login the user on unprotected routes on initial page load dnd check if there is another authRequest required to the backend.
import { PROTECTED_ROUTES } from "~/constants/protectedRoutes";
export default function ({ app, store }) {
app.router.onReady(async (route) => {
if(PROTECTED_ROUTES.includes(route.name)){
// Let authRouterBeforeEnter.js do the job
// to avoid two isAuthorized requests to the backend
await store.dispatch('app/createVisibilityChangedEvent')
}
else {
// If this route is public do the full init process
await store.dispatch('app/init')
}
})
}
Additionally i have added an app module to the store. It does a full init process with auth request and adding a visibility changed event or just adds the event.
export default {
async init({ dispatch }) {
dispatch('user/isAuthenticated', {}, {root:true})
dispatch('createVisibilityChangedEvent')
},
async shutdown({ dispatch }, {userLogout}) {
dispatch('user/logout', {userLogout}, {root:true})
},
async createVisibilityChangedEvent({ dispatch }) {
window.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'visible') {
console.log('visible changed');
await dispatch('user/isAuthenticated', {}, {root:true})
}
})
},
}
Not sure if this is possible, but here it goes.
What I am looking to do is include my "admin" routes as a separate file, only if the user is an admin (therefore a non admin will get a 404 error
routes.php
if( Session::get('user')->is_admin )
require_once('routes-admin.php');
if( Auth::check() )
require_once('routes-user.php');
Route::get('/', function() {
return view('home');
});
routes-admin.php
Route::get('admin', function() {
return view('admin-dashboard');
});
routes-user.php
Route::get('user', function() {
return view('user-dashboard');
});
What I am trying to do is avoid having the test repeated with every single Route
so if my user segment has 10 pages I currently need 30 lines of code dedicated to Auth::check() (the if, else and redirect if not), where I can instead have a single check on routes.php and the user will get a 404 if they don't belong
Is there a way to perform this check outside of the Route?
Perhaps you want to read documentation first?
Route::group(['middleware' => 'auth'], function()
{
Route::get('/', function()
{
// Uses Auth Middleware
});
Route::get('user/profile', function()
{
// Uses Auth Middleware
});
});
Above code does exactly what you need, is "person logged in?" let him go to page "whatever".
You can create middlewares (check if user is admin or basic user) yourself and apply on groups.
Example middleware
class BeforeMiddleware implements Middleware
{
public function handle($request, Closure $next)
{
// Perform action
return $next($request);
}
}
Do not get me wrong, just your approach is really not Laravel like. Try to see some open source projects done in L5 or even in L4. Try to use everything Taylor already done for you. Documentation is your firend here.
Following the response of #Kyslik for the middleware, you can "include" your own routes file in your RouteServiceProvider like the default routes file, the RouteServiceProvide is located in: app/Providers/RouteServiceProvider.php,
Find the section
require app_path('Http/routes.php');
and just replicate with the name of your routes file want to include
I'm using the following to check for privileges on every request to my server:
app.all('*',users.authCheck);
However, i wish to disable this for one specific request, namely the login route:
app.post('/users/login', users.login);
I know that i could move all my routes into another directory and change to app.all('foobar/*',users.authCheck);
However, i would rather like to exclude one route.
Is this possible?
You could simply add a if to your route:
// Somewhere in your app
users.authCheck = function(req, res, next) {
if (req.url === '/users/login') {
return;
}
};
That's not the best solution, however will help you for now.