On a server side I have 2 middlewares - protect (is logged in?) and restrictTo (checks user's role). These middlewares stop users or guests from performing certain actions if they are not allowed to
exports.protect = catchAsync(async (req, res, next) => {
let token;
if (
req.headers.authorization && req.headers.authorization.startsWith("Bearer")
) {
token = req.headers.authorization.split(" ")[1];
}
if (!token) {
return next(new AppError("You are not signed in!", 401));
}
const decodedToken = await promisify(jwt.verify)(
token,
process.env.JWT_SECRET
);
const currentUser = await User.findById(decodedToken.id);
if (!currentUser) {
return next(new AppError("User with such token no longer exists"));
}
req.user = currentUser;
next();
});
exports.restrictTo = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(new AppError("No access", 403));
}
next();
};
};
But how do I protect routes on a client side? If I'm not allowed to post a new note then I should be stopped from going to a /newnote page so I can't see and fill the form.
JWT token is stored in cookies with httpOnly flag. So I can't access the token from a Vue router. Store a user's role in Vuex? Then how do I synchronize the token state in cookies and in Vuex? If my token has been destroyed on a server side I still can have it in Vuex until I send a request to a protected endpoint.
Should I request a special auth endpoint for protected routes to check my current role using beforeEach?
Basically, you should add two things:
store the current authenticated user. By default, authUser is null. When someone logs in, authUser is an object with the user’s data. you can store this in Vuex, localStorage, etc.
create an interceptor/middleware in whatever library you are using for your api requests. If at some point you get a 401/403, it means that the current user’s session expired, or he is trying to access a protected area he shouldnt be looking at. Either way, reset the local authUser to null and redirect to login.
In Spa/mobile you dont have to worry too much about this as long as your backend is properly secured. If your authUser logic is correct, then only users with bad intentions will try to reach protected areas, whereas normal users will play by the rules and never hit a page they arent supposed to with their current privileges (assuming the UI is wired up correctly…).
Related
I have set up NextAuth to authenticate with google OAuth, but my middleware doesn't think I am authenticated.
This is my middleware.js file:
import middleware from 'next-auth/middleware';
export const config = {
matcher: ['/testing-page'],
};
export default middleware({
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
authorized({ req, token }) {
console.log('in the authorized');
console.log(token);
console.log(req.cookies);
if (token) return true;
},
},
});
When I log out the req.cookies I see that, when I am logged in via google and have a valid session in my database, I see a property called next-auth.session-token. This cookie isn't present when I am logged out and I navigate to this protected page, so I would assume this is the property that would authenticate me, but token is null when I log it out. I've heard that the token is referring to a JWT, which I don't have, since I authenticated via google. Is there a built in way for this middleware to check the session and authenticate based on that, or what am I missing that would authenticate me?
Any help is appreciated as I just started learning NextAuth yesterday. Thank you!
So I was trying out the authentication techniques with passport and passport-jwt with the express server. Here is the code I've been working with
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
const User = require("../models/user");
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = "secret";
module.exports = passport => {
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
User.findById(jwt_payload.id,(err,user)=>{
if(err){
return done(err,false);
}
if(user){
done(null,user);
}
else{
done(null,false);
}
})
})
)
};
So the all point of using this passport authorization is to minimize the number of times the database is accessed, right?
But in this code after extracting the token, the database is accessed through the findById method to find whether the user is in the database, so what's the point in all of this if the database is accessed during each authentication request?
I'm pretty sure I'm saying something wrong, some help in clarifying this matter is deeply appreciated.
The question is, why would you need to do User.findById on the middleware?
You don't have to access the database on the middleware to find whether user exists or not from the JWT payload. When the user is getting the jwt through the /login endpoint, you should've already checked whether the user exists or not
// just a logic example on the login enpoint
const user = User.findUserByEmail(req.body.email);
if (!user) res.sendStatus(401); //returns 401 if user not found
else {
if (verifyPassword(req.body.password, password)) {
res.send(generatedJwtWithUserIdOnThePayload)
} else {
res.sendStatus(401); //returns 401 if password invalid
}
}
The jwt that's passed when logging in to the client already had valid user id in it, therefore you dont need to get User document from User.findById everytime client sending a request to your other endpoint.
Since user id is already inside the payload, unless you need other data beside user id from User document, you don't really need to do User.findById on the middleware
I run an backend and a frontend both served by express the backend on port 8080 and the frontend on port 80.
/api/route1 returns 200ok with json
/api/route2 returns 200ok with json
So the app works fine fetching these routes. Now to the thing I need your help with. I have added next-auth so in the frontend I can
const [ session, loading ] = useSession();
to do something like
{!session && <p>You are not logged in</p>}
which works but what I haven't figured out is how to protect the routes to the API. I want to protect route1 and route2 in both frontend and backend. I guess when I'm logged in a need to pass a token down to the API but how can I have these 2 talking to each other
/api/route1 returns 200ok with json
/api/route2 returns 200ok with json
Remember I run the backend and frontend separately because my production build is in docker that's why.
You can find an example of this in the next-auth-example project
// pages/api/examples/protected.js
import { getSession } from 'next-auth/client'
export default async (req, res) => {
const session = await getSession({ req })
if (session) {
res.send({ content: 'This is protected content. You can access this content because you are signed in.' })
} else {
res.send({ error: 'You must be sign in to view the protected content on this page.' })
}
}
If a session object exists (i.e. is not null) then it means they either have a valid session token (if using database sessions) or a valid signed JSON Web Token (if using JWT session).
In both cases the session token is checked to make sure it is valid and has not expired.
The request object req is passed through to getSession() call when used in this way so that the cookie containing the session token can be inspected and validated.
The way that you could handle protected routes within Node is by using middleware.
So lets say that you have a route for adding employees salary in database, so obviously such a route needs someone that is and authenticated admin right?
So you could make a middleware function like the simple one below
const validateAdminCookie = (req, res, next)=>{
//Here you then write all your logic on how you validate admin
//Now you will have conditonals here that:
if (!validatedCookie){
return res.status(400).json({msg:'Not authorized'})
}
next();
}
So now that function is what you will pass within your route so that is gets executed first and when user is valid authenticated admin then the next() will push down that user to the main route that they were trying to hit else if not authenticated then the get back a message that they are not authenticated.
Now how you pass this middleware is like this below:
router.post('/api/admin-update-salaries',validateAdminCookie, (req, res)=>{
//Now that **validateAdminCookie** will execute first and if all
//checks out then user will be pushed down to the main part
//that is this route here
})
I have a problem creating graphql server and checking auth. I automatically created schemas with Prisma, and I manually added to the schema by creating a 'signin' mutation. I have also added the jwt checking (auth) middleware, as shown in the following code:
server.express.post(
server.options.endpoint,
auth,
(err, req, res, next) => {
console.log('bb');
if (err) return res.status(401).send(err.message)
next()
}
)
The problem is that the token is checked even when the signin is done.
Is there a way not to confirm tokens when requesting signin interaction, or if so, how do I get over it?
(is it right that there isn't token when client doesn't signed in?)
In your auth middleware, you can access the Request (req). You could check to see which operation if called.
If you are calling the login operation, call next()
Otherwise, check the token
I'm working on a MEAN application with authentication using JSON web tokens. basically on every request, I am checking to see if user has a valid token. if so they can go through to the route, otherwise they are returned to login page.
I want to make certain routes /admin/etc... only accessible to logged in users who are also admin. I have set up an isAdmin flag in mongo. I am new to nodejs and wondering what is the best way to check this. Do I do it on the angular side in routes? Or can I somehow create permission-based tokens on authentication? For reference, I am following the code from the MEAN Machine book, in particular here -
https://github.com/scotch-io/mean-machine-code/tree/master/17-user-crm
First, authorization decisions must be done on the server side. Doing it on the client side in Angular.js as you suggested is also a good idea, but this is only for the purpose of improving the user's experience, for example not showing the user a link to something they don't have access to.
With JWTs, you can embed claims about the user inside the token, like this:
var jwt = require('jsonwebtoken');
var token = jwt.sign({ role: 'admin' }, 'your_secret');
To map permissions to express routes, you can use connect-roles to build clean and readable authorization middleware functions. Suppose for example your JWT is sent in the HTTP header and you have the following (naive) authorization middleware:
// Naive authentication middleware, just for demonstration
// Assumes you're issuing JWTs somehow and the client is including them in headers
// Like this: Authorization: JWT {token}
app.use(function(req, res, next) {
var token = req.headers.authorization.replace(/^JWT /, '');
jwt.verify(token, 'your_secret', function(err, decoded) {
if(err) {
next(err);
} else {
req.user = decoded;
next();
}
});
})
With that, you can enforce your authorization policy on routes, like this:
var ConnectRoles = require('connect-roles');
var user = new ConnectRoles();
user.use('admin', function(req) {
return req.user && req.user.role === 'admin';
})
app.get('/admin', user.is('admin'), function(req, res, next) {
res.end();
})
Note that there are much better options for issuing & validating JWTs, like express-jwt, or using passport in conjunction with passort-jwt