Directly authenticate(login) after successful signup in Node.js - javascript

How can i directly authenticate the user after signup.
Below are the the deatail of serializeUser and deserializeUser.
passport.serializeUser(function(user, done) {
done(null, {tutorId: user.tutorId, userType: user.userType});
});
passport.deserializeUser(function(userData, done) {
Tutor.getTutorById(userData.tutorId, (err, user) => {
if (err) {
try {
logger.silly(`message: POST inside passport.deserializeUser; file: index.js; error: ${err}; user: ${JSON.stringify(user)}`);
logger.error(`message: POST inside passport.deserializeUser; file: index.js; error: ${err}; user: ${JSON.stringify(user)}`);
} catch (e) {

You can use req.login() after successful registration.
From official Passport documentation:
Note: passport.authenticate() middleware invokes req.login()
automatically. This function is primarily used when users sign up,
during which req.login() can be invoked to automatically log in the
newly registered user.
A sample registration code might look like this:
router.post("/register",(req,res) => {
var user = new User();
user.name = req.body.name;
user.email = req.body.email;
//define other things here
//create hash and salt here
user.save().then(user => {
//on successfult registration
//login user here, using req.login
req.login(user ,err => {
if(!err){
//redirect to logged-in page
//or user page
res.redirect('/')
}
})
})
})
Read about req.login() in official passport documentsation
I hope this helps you out.

you can create token just after successful registration and send it back in registration response

Related

Handle Email Verification Check with auth0/nextjs

I am using the library auth0/nextjs. I am trying to handle the email verification. I have access to the email_verification variable. If not verified, it will redirect to the page /please-verifiy-your-email.
At the moment I am using handleCallback method which is provided by auth0/nextjs.
Code:
const afterCallback = (req, res, session, state) => {
if (!session.user.email_verified) {
res.status(200).redirect('/please-verifiy-your-email')
}
return session;
};
export default auth0.handleAuth({
async login(req, res) {
try {
await auth0.handleLogin(req, res, {
authorizationParams: {
audience: 'https://dev-okz2bacx.us.auth0.com/api/v2/',
scope: 'openid profile email read:branding'
},
returnTo: "/dashboard"
});
} catch (error) {
res.status(error.status || 400).end(error.message);
}
},
async callback(req, res) {
try {
await auth0.handleCallback(req, res, { afterCallback });
} catch (error) {
res.status(error.status || 500).end(error.message);
}
}
});
How can I make sure, the user is still logged in, to get his email for a resend or the possibility to change his email.
He also needs the chance to do a new sign up , because when I will call api/auth/login after login with the unverified email, it will redirect automatically to /please-verifiy-your-email. I guess the session is not killed and auth0 goes back redirecting even tho I didn’t had the chance to sign up again.
Would be really cool if I get a few inputs.
It would solve the problem if I could log in the user and then it would redirect to /please-verifiy-your-email. I would be able to get his email address, name for further functions like resend verification. Right now I call api/auth/me I get:
{"error":"not_authenticated","description":"The user does not have an
active session or is not authenticated"}

Authenticate new user with passport.js only after email verify

I'm using passport.js with mongoose (mongoDB) (and passport-local-mongoose) to create and authenticate user accounts in a node/express backend.
The strategy I'm currently using is:
User completes a signup form, with a username input type="email" name="username" and a password input name='password' (among others)*
User submits form.
Add new user to a Profile schema let newUser = new Profile({username: req.body.username, ...})
Generate a token using crypto.randomBytes(...)
Profile.register(newUser, password) & passport.authenticate user and set token field generated from above set, along with an expiry date
Send an email to new user, with a url containing their mongoose _id and their token (/u/${id}/is-valid/${token}).
On visiting this url, Profile.findOne({_id: req.params.id}) and check that the tokens match, and has not expired
If not, send them to the login page.
Then problem with this approach, is that in calling Profile.register and passport.authenticate together, a pre authenticated account is created before the user has validated their email.
How can I decouple these two methods so that I can authenticate users only after they've verified their email address?
Here is how I call Profile.register and passport.authenticate (as part of an async.waterfall([]):
Profile.register(user, pw)
.then(response => {
passport.authenticate("local")(req, res, function(err) {
if (err) return callback(err);
user.resetEmailToken = token;
user.resetEmailExpires = Date.now() + 8.64e+7; // 1 day
user.save(function(err) {
callback(err, token, user);
});
});
})
.catch(err => {
return callback(err);
});
I would like to Profile.register as I do above, but only passport.authenticate once I've checked the tokens.
Here is how I check the tokens (on visiting link contained in email - /u/${id}/is-valid/${token}):
async verifyEmail(req, res, next){
let user = await Profile.findOne({ '_id': req.params.id })
if(!user) return res.redirect("back");
if (user.resetEmailToken != req.params.token){
req.flash("error", "Tokens don't match");
return res.redirect("back");
}
if(user.resetEmailExpires < Date.now()){
console.log("This date has expired", moment(user.resetEmailExpires).calendar());
return res.redirect(`/signup?token-expired=${user.email}`);
}
user.isVerified = true;
// Here is where I want to passport.authenticate() and redirect to their new profile
user.save();
return res.render('login', { user: user });
}
If this is not possible, what are other approaches I could take to achieve this? It must be a fairly common signup strategy.
* This is a bit of a hack as passport.js assumes registration with username & password (not email)

restrict users from accessing other user profiles

I have to implement security on my app by preventing users to access other users profile.
route.js
router.get('/currentUser/:username', userAuth, (req, res) => {
User.findOne({
username: req.params.username
}).then(user => {
if (user) {
return res.status(200).json(user);
} else {
return res.status(404).json({
message: 'User not found'
});
}
});
});
and my userAuth.js
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
jwt.verify(token, 'app_secret_token');
next();
} catch (error) {
res.status(401).json({
message: 'Authentication failed!'
});
}
};
now if I am logged in as user test so my URL will be http://localhost:4200/currentuser/test but if I change my URL to another user test2 it redirects and loads the test2 even though I am logged as test
how do I prevent this?
You need to also check that the logged in user accesses his data.
you can achieve this by checking the user in the token against the requested page. This means you need to encode the user Id inside the jwt token. That will also make sure this parameter wasn't meddled with since jwt.verify would fail if someone tried to change the jwt token without having the secret.
you can add that data to the jwt token when signing it:
jwt.sign({
userId: 'username'
}, 'secret', { expiresIn: '1h' });
Basically if you save the same data as serializeUser\deserializeUser result, it should also work (the username is just a suggestion).
you can use the callback from jwt.verify to get the decoded token and retrieve that data
const jwt = require('jsonwebtoken');
module.exports = (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
jwt.verify(token, 'app_secret_token', (err, decoded) => {
if (err) { throw err; }
const currentUsername = decoded.userId; // <-- this should be whatever data you encoded into the jwt token
// if the user requested is different than the user in the token,
// throw an authentication failure
if (req.originalUrl.includes('/currentUser/') &&
!req.originalUrl.includes(`/currentUser/${currentUsername}`)) {
throw new Error('access to other user data denied');
}
next();
});
} catch (error) {
res.status(401).json({
message: 'Authentication failed!'
});
}
};
Even though I think this might be a good case separating this into two different middlewares :-)
PS - as #anand-undavia mentioned, it might be better to identify the user request based on the jwt token itself instead of the 'url' itself. that way, each user should only have access to their own data and this problem can't occur at all.
basically, the user should be accessible with the method above (getting it from the token) or from a req.user field if you use any middleware that adds it automatically.
let us assume that user profile page id mydomian?passedId=userId so simply add profile-guard to check who can visit or activate this page, in CanActivate check if passed id id the same of current user id, then return true else redirect him to previous page
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
let passedId: string = next.queryParams.passedId;
let user = this.authService.GetUser();
if (user.id == passedId)
return true;
else {
this.router.navigate(['/home']); // or any page like un authorized to log to this page
return false;
}
}

JWT Login - No authorization token was found in middleware

I followed a tutorial to add login and registration to my Node.js app using JWT token and I'm having a hard time logging in and redirecting to my 'logged in' admin page. User registration works great, but the login portion I can't figure out.
This is the tutorial I was following:
https://medium.freecodecamp.org/learn-how-to-handle-authentication-with-node-using-passport-js-4a56ed18e81e
My code for login looks like this:
router.post('/login', auth.optional, (req, res, next) => {
console.log(req.body);
var user = {
email: req.body.email,
password: req.body.password
}
if (!user.email) {
return res.status(422).json({
errors: {
email: 'is required',
},
});
}
if (!user.password) {
return res.status(422).json({
errors: {
password: 'is required',
},
});
}
return passport.authenticate('local', { session: false }, (err, passportUser, info) => {
if (err) {
return next(err);
}
if (passportUser) {
const user = passportUser;
user.token = passportUser.generateJWT();
console.log("TOKEN: " + user.token);
res.setHeader('Authorization', 'Token ' + user.token);
return res.json({ user: user.toAuthJSON() });
}
return res.status(400).json({
errors: {
message: info,
},
});
})(req, res, next);
});
My '/admin' "logged in" route looks like this:
router.get("/admin", auth.required, function(req, res) {
res.render('admin', {
user : req.user // get the user out of session and pass to template
});
});
I'm not sure how I can redirect to my '/admin' route while also passing the token because currently I am seeing the following error after logging in. Makes sense since I am not passing the token to the '/admin' route...but how do I do that? :)
UnauthorizedError: No authorization token was found at middleware
Thanks in advance for the help!
EDIT:
Still can't figure this out and don't really understand how this flow is supposed to work...where do the headers need to be set to the token and how do I redirect to my admin page once the login is successful.
Here is my middleware code if this helps:
const getTokenFromHeaders = (req) => {
console.log("REQ: " + JSON.stringify(req.headers));
const { headers: { authorization } } = req;
if(authorization && authorization.split(' ')[0] === 'Token') {
return authorization.split(' ')[1];
}
return null;
};
const auth = {
required: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
}),
optional: jwt({
secret: 'secret',
userProperty: 'payload',
getToken: getTokenFromHeaders,
credentialsRequired: false,
}),
};
Your code does not have a problem. You seem to be confused with the login flow from server to client (Frontend/Web).
Let's first have a look the RESTFUL way of doing it. The article also refers to the same flow.
The RESTFUL API flow looks like this:
User requests for login:
POST: /api/v1/auth/login with username and password in request body.
If successful, user is returned with basic inforamtion and token.
If not, user is returned a 401 (Unauthorized) status code.
The login flow ends here.
The token provided earlier to the user is used to make subsequent calls to the backend, which a user can use to perform different operations on the sustem. In essence, it is the client which requests server for subsequent actions with the token provided in the login request.
So for your case, user after receiving the token should make a request for retrieving admin information from the backend.
But, I am assuming you are rendering views from your server-side and you want to render the admin view once the user is successfully logged in, and that's pretty straight forward.
Instead of your res.json() after successful login. You need to use res.render().
res.render('admin', {
user: user.toAuthJSON() // assuming your user contains the token already
})
Edit:
Since res.render() does not change the url in the browser. For that, you need to use res.redirect(). But the problem is, you can not send context in res.redirect().
To achieve that, you will need to pass in the user token as query paramter. See here.
TL;DR
// assuming you are using Node v7+
const querystring = require('querystring');
const query = querystring.stringify({
token: user.token,
});
const adminRoute = '/admin?' + query;
res.redirect(adminRoute)
And in your admin route, you need to slightly modify the code.
Verify the token belongs to a real user and get user information out of the token.
Render the admin template with user information retrieved from step 1.
router.get("/admin", function(req, res) {
// verify the token
const token = req.query.token;
const user = null;
jwt.verify(token, 'secret', function (err, decoded) {
if (err) {
res.status(401).send('Unauthorized user')
}
// decoded contains user
user = decoded.user
});
res.render('admin', {
user : user
});
});
I'm somewhat new to this as well, but I've got it working as follows.
In your server.js file:
const passport = require("passport");
const JwtStrategy = require("passport-jwt").Strategy;
const ExtractJwt = require("passport-jwt").ExtractJwt;
app.use(passport.initialize());
const opts = {};
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = Keys.secretOrKey;
passport.use(
new JwtStrategy(opts, (jwt_payload, done) => {
// somefunction looks up the id in jwt payload and
// supplies passport the authenticated user via the "Done" function
somefunction.user(jwt_payload.id)
.then(user => {
if (user) {
return done(null, user);
}
return done(null, false);
});
})
);
In your API definitions
const jwt = require("jsonwebtoken");
router.post("/login", (req, res) => {
const { userInfo } = req.body;
// userInfo has username and password in it
// anotherFuction validates the user id and password combo
anotherFunction(userInfo.id, userInfo.password)
.then(isAuthenticated => {
if (isAuthenticated) {
const payload = {
id: user.sAMAccountName,
firstname: user.givenName,
lastname: user.sn
};
// Sign Token with the payload
jwt.sign(
payload,
Keys.secretOrKey,
{ expiresIn: 3600 },
(err, token) => {
res.json({
success: true,
token: "Bearer " + token
});
}
);
} else {
// don't mind the statuses ^_^'
return res.status(401).json({ error: "Login failed." });
}
})
.catch(err => {
return res.status(400).json(err);
});
});
After calling the API you want to set the auth token. The following lets you delete the token if nothing is passed in, effectively "Logging out".
const setAuthToken = token => {
if (token) {
// Apply to every request
axios.defaults.headers.common["Authorization"] = token;
} else {
// Delete Auth Header
delete axios.defaults.headers.common["Authorization"];
}
};
If you're trying to use it in the front end, you need to use jwt_decode to pull the values from the token and set it however you deem necessary. If using redux to store login data it should look something like this. As I feel that the discussion of using localstorage for jwtToken is outside of the scope of this, just know would need to check for the token.
if (localStorage.jwtToken) {
setAuthToken(localStorage.jwtToken);
const decoded = jwt_decode(localStorage.jwtToken);
store.dispatch({
type: USER_LOGIN,
payload: decoded
});
}
Hope this helped.
From one beginner in JWT to another. Good luck.

SailsJS + PassportJS (sails-generate-auth) - unable to login user

I am currently having a problem authenticating user in my SailsJS app using the PassportJS.
I have generated all the stuff needed for authentication using sails-generate-auth according to this tutorial.
It seems like the POST request is routed correctly as defined in the routes.js file:
'post /auth/local': 'AuthController.callback',
'post /auth/local/:action': 'AuthController.callback',
In AuthController I've got following code:
callback: function (req, res) {
function tryAgain (err) {
// Only certain error messages are returned via req.flash('error', someError)
// because we shouldn't expose internal authorization errors to the user.
// We do return a generic error and the original request body.
var flashError = req.flash('error')[0];
if (err && !flashError ) {
req.flash('error', 'Error.Passport.Generic');
} else if (flashError) {
req.flash('error', flashError);
}
req.flash('form', req.body);
// If an error was thrown, redirect the user to the
// login, register or disconnect action initiator view.
// These views should take care of rendering the error messages.
var action = req.param('action');
switch (action) {
case 'register':
res.redirect('/register');
break;
case 'disconnect':
res.redirect('back');
break;
default:
res.redirect('/login');
}
}
passport.callback(req, res, function (err, user, challenges, statuses) {
if (err || !user) {
return tryAgain(challenges);
}
req.login(user, function (err) {
if (err) {
return tryAgain(err);
}
// Mark the session as authenticated to work with default Sails sessionAuth.js policy
req.session.authenticated = true
// Upon successful login, send the user to the homepage were req.user
// will be available.
res.redirect('/user/show/' + user.id);
});
});
},
I am submitting my login form in Jade template:
form(role='form', action='/auth/local', method='post')
h2.form-signin-heading Please sign in
.form-group
label(for='username') Username
input.form-control(name='username', id='username', type='username', placeholder='Username', required='', autofocus='')
.form-group
label(for='password') Password
input.form-control(name='password', id='password', type='password', placeholder='Password', required='')
.form-group
button.btn.btn-lg.btn-primary.btn-block(type='submit') Sign in
input(type='hidden', name='_csrf', value='#{_csrf}')
I have checked the values passed to the callback function specified for password.callback(..) call:
passport.callback(req, res, function (err, user, challenges, statuses) {
the user variable is set to "false". I suppose, this is where the error comes from.
What is interesting is, that when the callback() function is called after user registration, the user variable is set right to user object containing all the values like username, email, etc.
If you would like to check other source files, my project is available on github in this repository.
Any help is appreciated.
Shimon
You're using identifier as the usernameField for LocalStrategy (i.e. the default setting) and have username in the login view, which means the authentication framework receives no username and fires a Missing Credentials error.
Either change the field name in the login view to identifier or set the appropriate usernameField through the passport config file (config/passport.js):
local: {
strategy: require('passport-local').Strategy,
options: {
usernameField: 'username'
}
}

Categories

Resources