Trying to integrate digest auth in sails. Using passport-http module for it . https://github.com/jaredhanson/passport-http
This is the policy I have defined.
module.exports = function(req, res, next) {
var passport = require('passport');
var Strategy = require('passport-http').DigestStrategy;
passport.use(new Strategy({ qop: 'auth' },
function(username, cb) {
console.log("in strategy");
user = { id: 1, username: 'postman', password: 'password', displayName: 'Postman', emails: [ { value: 'postman#example.com' } ] }
if(username == user.username) {
return cb(null,user,user.password)
}
else{
return cb(null,false)
}
}
));
passport.authenticate('digest',{session: false});
next();
};
Now in express the passport.authenticate function returns in case of error but in case of sails , it does not return here as a middle-ware. Please point me in the right direction , spent a lot of time on this .
I want to basically return a not authorized in case of wrong headers else move on to the controller action.
Related
I'm using passport-saml and express-session. I login with my original session id but when the idp response reach the login callback handler, I have another sessionId. Also, since my browser has the session cookie with the original session id, it cannot use the new session id in the login callback, so I cannot authenticate.
interface SamlProvider {
name: string;
config: SamlConfig;
}
const providers: SamlProvider[] = [
{
name: process.env.SAML_ENTITY_ID_1!,
config: {
path: "/login/callback",
entryPoint: process.env.SAML_SSO_ENDPOINT_1,
issuer: process.env.SAML_ENTITY_ID_1,
cert: process.env.SAML_CERT_1!,
...(process.env.NODE_ENV === "production" && { protocol: "https" }),
disableRequestedAuthnContext: true,
},
},
{
name: process.env.SAML_ENTITY_ID_2!,
config: {
path: "/login/callback",
entryPoint: process.env.SAML_SSO_ENDPOINT_2,
issuer: process.env.SAML_ENTITY_ID_2,
cert: process.env.SAML_CERT_2!,
...(process.env.NODE_ENV === "production" && { protocol: "https" }),
disableRequestedAuthnContext: true,
},
},
];
export const samlStrategy = (sessionStore: session.Store) =>
new MultiSamlStrategy(
{
passReqToCallback: true, // makes req available in callback
getSamlOptions: function (request, done) {
// Find the provider
const relayState = request.query.RelayState || request.body.RelayState;
const provider = providers.find((p) => p.name === relayState);
if (!provider) {
return done(Error("saml identity provider not found"));
}
return done(null, provider.config);
},
},
async function (
req: Request,
profile: Profile | null | undefined,
done: VerifiedCallback
) {
if (profile && profile.nameID) {
const { nameID, nameIDFormat } = profile;
const email = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
] as string;
const firstName = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
] as string;
const lastName = profile[
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
] as string;
// Check if user is in risk database
const user = await myUserService.getByEmail(email);
if (!user) return done(new UserNotFoundError());
// If user has existing session, delete that existing session
sessionStore.all!((err: any, obj: any) => {
const sessions = obj as Array<{
sid: string;
passport?: { user?: { email?: string } };
}>;
const existingSess = sessions.find(
(sess) =>
sess.passport &&
sess.passport.user &&
sess.passport.user.email &&
sess.passport.user.email === email
);
if (existingSess && existingSess.sid) {
sessionStore.destroy(existingSess.sid, (err: any) => {
console.error(err);
return done(Error("failed to delete existing user session"));
});
}
});
return done(null, { nameID, nameIDFormat, email, firstName, lastName });
}
return done(Error("invalid saml response"));
}
);
Here's my login and login callback
app.post("/login/callback", async function (req, res, next) {
passport.authenticate("saml", (err: any, user: ISessionUser) => {
if (err) {
// TODO: Handle specific errors
logger.info({ label: "SAML Authenticate Error:", error: err });
return next(err);
} else {
req.logIn(user, (err) => {
if (err) {
logger.info({ label: "Login Error:", data: err });
return next(err);
}
res.redirect("/");
});
}
})(req, res, next);
});
app.get(
"/auth/saml/login",
passport.authenticate("saml", { failureRedirect: "/", failureFlash: true }),
function (req, res) {
res.redirect("/");
}
);
I experienced a similar issue using Microsoft 365 for authentication. The answer was to pass a randomly-generated nonce to the authentication request - this gets passed back to your app in the callback request. With SAML I think it depends on the provider whether they support such a flow, but it is good practice. You can also use a cookie to maintain state in your app, instead of, or additional to, the session id.
I am trying to have a user log in by their email and password. MongoDb docs shows hashing the password with bcrypt in the user model. It also provides a nice way to validate the password in the model as well. My problem is how to I use that validation from the "controller"? I am very aware "if (req.body.password === user.password)" will not work because one is hashed and the other is not.
I have been searching for answers for hours and can't seem to find that connection on how I use that "UserSchema.methods.comparePassword" method in my post request to log in. This isn't completely a real log in, just trying to get the password to validate and send back a key once logged in. Here are the docs: https://www.mongodb.com/blog/post/password-authentication-with-mongoose-part-1
// This is my UserModel
let mongoose = require('mongoose'),
Schema = mongoose.Schema,
bcrypt = require('bcrypt'),
SALT_WORK_FACTOR = 10
var hat = require('hat');
let UserSchema = new Schema({
email: {
type: String,
required: true,
index: {
unique: true
}
},
password: {
type: String,
require: true
},
api_key: {
type: String
}
});
UserSchema.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) return next();
// generate a salt
bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
if (err) return next(err);
// hash the password using our new salt
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err);
// override the cleartext password with the hashed one
user.password = hash;
user.api_key = hat();
next();
});
});
});
UserSchema.methods.comparePassword = function(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
if (err) return cb(err);
cb(null, isMatch);
});
};
module.exports = mongoose.model('user', UserSchema);
// This is the sessions.js
let UserModel = require('../../../models/user.model');
var express = require('express');
var router = express.Router();
router.post('/', (req, res, next) => {
UserModel.findOne(
{
$or: [
{ email : req.body.email }
]
}
)
.then(user => {
if (req.body.password === user.password) {
res.setHeader("Content-Type", "application/json");
res.status(200).send(JSON.stringify({
"api_key": `${user.api_key}`
}));
} else {
res.status(404).send("Incorrect email or password")
}
})
.catch(error => {
res.setHeader("Content-Type", "application/json");
res.status(500).send({error})
})
})
module.exports = router
If I just find user by email, everything works fine. Just need to figure out how to use the compare password method in the user model. Thanks!
Maybe have something like this in your model:
User = require('./user-model');
.......
User.findOne({ username: 'jmar777' }, function(err, user) {
if (err) throw err;
user.comparePassword('Password123', function(err, isMatch) {
if (err) throw err;
console.log('Password123:', isMatch); // -> Password123: true
});
........
Other resources:
http://devsmash.com/blog/password-authentication-with-mongoose-and-bcrypt
https://www.abeautifulsite.net/hashing-passwords-with-nodejs-and-bcrypt
https://medium.com/#mridu.sh92/a-quick-guide-for-authentication-using-bcrypt-on-express-nodejs-1d8791bb418f
Hope it helps!
I know this question has been asked many times but I cannot find an answer to my problem both here or on github. I have a login handler which compares hashed password from db to the the one typed by the user on login. bcrypt.compare almost always returns false. I say almost because sometimes it just starts working and it always works after I register user. I am trying to find what is wrong with my code but cant figure it out. Any help is highly appreciated.
mongoose pre save
userModel.schema.pre('save', function(next) {
let user = this;
bcrypt.hash(user.password, 10, null)
.then(hash => {
console.log(hash)
user.password = hash;
user.confirmPassword = hash;
next();
})
.catch(err => res.sendStatus(404));
});
login handler
exports.loginUser = (req, res) => {
let user = new User.model(req.body);
User.model
.find({email: user.email})
.exec()
.then(users => {
if (!users.length) {
res.status(401).json({
message: "Auth failed - user does not exist"
});
} else {
bcrypt
.compare(req.body.password, users[0].password)
.then(result=> {
console.log(user.password, users[0].password)
console.log(bcrypt.hashSync(req.body.password, 10))
if (result) {
const token =
jwt
.sign({ email: users[0].email, id: users[0]._id },
'secretKey', { expiresIn: "1h"});
res.status(200).json({
message: "Auth success - logged in",
token,
users
});
} else {
res.json('not working');
}
})
.catch(err => res.status(401).json({message: "Auth failed"}));
}
});
};
register handler
exports.registerUser = (req, res) => {
let user = new User.model(req.body);
if(user.email) {
User.model
.find({email: user.email})
.exec()
.then(docs => {
if (!docs.length) {
if (user.password !== user.confirmPassword) {
return res.status(404).json('passwords do not match');
}
user.save(function (err, user) {
if (err) return (err);
});
console.log('user saved');
res.sendStatus(200);
} else {
res.status(404).json('user exists');
}
})
.catch(err => res.sendStatus(404).json(res.body));
} else {
res.status(404).json('user name required');
}
};
The problem might be that you generate a new password each time the user is saved. You should skip this though.
userModel.schema.pre('save', function(next) {
let user = this;
if(!user.isModified("password")) return next();
bcrypt.hash(user.password, 10, null)
.then(hash => {
console.log(hash)
user.password = hash;
user.confirmPassword = hash;
next();
})
.catch(err => res.sendStatus(404));
});
Just a shot in the dark though. under the assumption something got changed and this was called again, because you stated it is working sometimes.
In my application some routes are just accessible for authenticated users.When a unauthenticated user clicks on a link, for which he has to be signed in, he will be redirected to the login component.
If the user logs in successfully, I would like to redirect him to the URL he requested before he had to log in. However, there also should be a default route, in case the user did not request another URL before he logged in.
How can I achieve this using vue-router?
My code without redirect after login
router.beforeEach(
(to, from, next) => {
if(to.matched.some(record => record.meta.forVisitors)) {
next()
} else if(to.matched.some(record => record.meta.forAuth)) {
if(!Vue.auth.isAuthenticated()) {
next({
path: '/login'
// Redirect to original path if specified
})
} else {
next()
}
} else {
next()
}
}
)
My login function in my login component
login() {
var data = {
client_id: 2,
client_secret: '**************',
grant_type: 'password',
username: this.email,
password: this.password
}
// send data
this.$http.post('oauth/token', data)
.then(response => {
// authenticate the user
this.$auth.setToken(response.body.access_token,
response.body.expires_in + Date.now())
// redirect to route after successful login
this.$router.push('/')
})
}
This can be achieved by adding the redirect path in the route as a query parameter.
Then when you login, you have to check if the redirect parameter is set:
if IS set redirect to the path found in param
if is NOT set you can fallback on root.
Put an action to your link for example:
onLinkClicked() {
if(!isAuthenticated) {
// If not authenticated, add a path where to redirect after login.
this.$router.push({ name: 'login', query: { redirect: '/path' } });
}
}
The login submit action:
submitForm() {
AuthService.login(this.credentials)
.then(() => this.$router.push(this.$route.query.redirect || '/'))
.catch(error => { /*handle errors*/ })
}
I know this is old but it's the first result in google and for those of you that just want it given to you this is what you add to your two files. In my case I am using firebase for auth.
Router
The key line here is const loginpath = window.location.pathname; where I get the relative path of their first visit and then the next line next({ name: 'Login', query: { from: loginpath } }); I pass as a query in the redirect.
router.beforeEach((to, from, next) => {
const currentUser = firebase.auth().currentUser;
const requiresAuth = to.matched.some(record => record.meta.requiresAuth);
if (requiresAuth && !currentUser) {
const loginpath = window.location.pathname;
next({ name: 'Login', query: { from: loginpath } });
} else if (!requiresAuth && currentUser) next('menu');
else next();
});
Login Page
No magic here you'll just notice my action upon the user being authenticated this.$router.replace(this.$route.query.from); it sends them to the query url we generated earlier.
signIn() {
firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(
(user) => {
this.$router.replace(this.$route.query.from);
},
(err) => {
this.loginerr = err.message;
},
);
},
I am going to be fleshing out this logic in more detail but it works as is. I hope this helps those that come across this page.
Following on from Matt C's answer, this is probably the simplest solution but there were a few issues with that post, so I thought it best to write a complete solution.
The destination route can be stored in the browser's session storage and retrieved after authentication. The benefit of using session storage over using local storage in this case is that the data doesn't linger after a broswer session is ended.
In the router's beforeEach hook set the destination path in session storage so that it can be retrieved after authentication. This works also if you are redirected via a third party auth provider (Google, Facebook etc).
router.js
// If user is not authenticated, before redirecting to login in beforeEach
sessionStorage.setItem('redirectPath', to.path)
So a fuller example might look something like this. I'm using Firebase here but if you're not you can modify it for your purposes:
router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some(x => x.meta.requiresAuth);
const currentUser = firebase.auth().currentUser;
if (requiresAuth && !currentUser) {
sessionStorage.setItem('redirectPath', to.path);
next('/login');
} else if (requiresAuth && currentUser) {
next();
} else {
next();
}
});
login.vue
In your login method, after authetication you will have a line of code that will send the user to a different route. This line will now read the value from session storage. Afterwards we will delete the item from session storage so that it is not accidently used in future (if you the user went directly to the login page on next auth for instance).
this.$router.replace(sessionStorage.getItem('redirectPath') || '/defaultpath');
sessionStorage.removeItem('redirectPath');
A fuller example might look like this:
export default Vue.extend({
name: 'Login',
data() {
return {
loginForm: {
email: '',
password: ''
}
}
},
methods: {
login() {
auth.signInWithEmailAndPassword(this.loginForm.email, this.loginForm.password).then(user => {
//Go to '/defaultpath' if no redirectPath value is set
this.$router.replace(sessionStorage.getItem('redirectPath') || '/defaultpath');
//Cleanup redirectPath
sessionStorage.removeItem('redirectPath');
}).catch(err => {
console.log(err);
});
},
},
});
If route guard is setup as below
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!loggedIn) {
next({
path: "/login",
query: { redirect: to.fullPath }
});
} else {
next();
}
} else {
next();
}
});
The redirect query can be extracted and used upon successful login
let searchParams = new URLSearchParams(window.location.search);
if (searchParams.has("redirect")) {
this.$router.push({ path: `${searchParams.get("redirect")}` });
} else this.$router.push({ path: "/dashboard" });
Another quick and dirty option would be to use local storage like the following:
In your beforeEach, before you redirect to login place the following line of code to save the initial requested path to local storage:
router.js
// If user is not authenticated, before redirecting to login
localStorage.setItem('pathToLoadAfterLogin', to.path)
Then in your login component, upon succesful login, you can redirect to the localStorage variable that you previously created:
login.vue
// If user login is successful, route them to what they previously requested or some default route this.$router.push(localStorage.getItem('pathToLoadAfterLogin') || 'somedefaultroute');
Much easier with this library
and login function is
let redirect = this.$auth.redirect();
this.$auth
.login({
data: this.model,
rememberMe: true,
redirect: { name: redirect ? redirect.from.name : "homepage", query: redirect.from.query },
fetchUser: true
})
This will help you #Schwesi .
Router.beforeEach(
(to, from, next) => {
if (to.matched.some(record => record.meta.forVisitors)) {
if (Vue.auth.isAuthenticated()) {
next({
path: '/feed'
})
} else
next()
}
else if (to.matched.some(record => record.meta.forAuth)) {
if (!Vue.auth.isAuthenticated()) {
next({
path: '/login'
})
} else
next()
} else
next()
}
);
This worked for me.
this.axios.post('your api link', {
token: this.token,
})
.then(() => this.$router.push(this.$route.query.redirect || '/dashboard'))
In Vue2 if someone has a routing and guarded some groups of routes. I solved this way.
function webGuard(to, from, next) {
if (!store.getters["auth/authenticated"]) {
sessionStorage.setItem("redirect", to); // hear I save the to
next("/login");
} else {
next();
}
}
Vue.use(VueRouter);
export default new VueRouter({
mode: "history",
hash: false,
routes: [
{
path: "/",
component: Home,
children: [
{ path: "", redirect: "home" },
...
...
],
beforeEnter: webGuard
},]
when you login
this.signIn({ email: test#gmail.com, password: 123 })
.then((res) => {
var redirectPath = sessionStorage.getItem('redirect');
sessionStorage.removeItem('redirect');
this.$router.push(redirectPath?redirectPath:"/dashboard");
})
I have had many problems, when I want to get information from user model. I read some solutions, but I didnt understand.
This is my code:
* AuthController
var passport = require('passport');
module.exports = {
_config: {
actions: false,
shortcuts: false,
rest: false
},
login: function(req, res) {
passport.authenticate('local', function(err, user, info) {
if ((err) || (!user)) {
return res.send({
message: info.message,
user: user
});
}
req.logIn(user, function(err) {
if (err) res.send(err);
return res.send({
message: info.message,
user: user
});
});
})(req, res);
},
logout: function(req, res) {
req.logout();
res.redirect('/');
},
signup: function (req, res) {
var data = req.allParams();
User.create({email:data.email,password:data.password,name:data.name}).exec(function(error,user){
if(error) return res.negotiate(err);
if(!user)return res.negotiate(err);
return res.ok();
});
}
};
*view
<h1>List of my dates</h1>
<h1><%= email %></h1>
<h1><%= req.user.name %></h1>
*model
attributes: {
email: {
type: 'email',
required: true,
unique: true
},
password: {
type: 'string',
minLength: 6,
required: true
},
toJSON: function() {
var obj = this.toObject();
delete obj.password;
return obj;
}
},
beforeCreate: function(user, cb) {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) {
console.log(err);
cb(err);
} else {
user.password = hash;
cb();
}
});
});
}
};
Only works if I use res.render('view', {email: req.user.email}) but, I would like to use the user data in many views. I cant write methods with Current user params, becouse dont work.
Thanks.
It is unclear to me what your actual problem is or what the question actually is but I will try to help.
Look here:
login: function(req, res) {
passport.authenticate('local', function(err, user, info) {
if ((err) || (!user)) {
return res.send({
message: info.message,
user: user
});
}
...
})(req, res);
},
There you are adding data (locals) to the ejs and the values are message and user so in the ejs you must reference it as this, so you will use user.name and not req.user.name? I'm not sure why you're binding the (req, res) either.
It's confusing because your ejs uses the email value but I don't see it there as a local so maybe thats your problem, it must be defined?
Consider the following simple example:
// User Controller
// GET request /signin
// The signin form
signin(req, res) {
// Load the view from app/views/*
return res.view('signin', {
title: 'Sign In'
});
},
// POST request to /signin
// This was posted from the signin form
// Use io.socket.post(...) to do this from the signin form
// Can use window.location.replace('/account') on successful request
authenticate(req, res) {
// The data posted, email and password attempt
var data = req.allParams();
// Does it match?
User.findOne({
email: data.email,
// This is stupid, don't ever use plain text passwords
password: data.password
})
.exec(function(err, user) {
// Server related error?
if (err) res.serverError(err.message);
// No user was found
if (!user) res.badRequest('Username or password not found');
// Sign the user in
req.session.userId = user.id;
// User was found
res.ok();
});
},
// GET request to /account
// Displays the users information
// Can use policies to ensure that only an authenticated user may access their own account information
account(req, res) {
// If the user is not signed in
// This is an alternative to using the sails policy isLoggedIn
if (!req.session.userId) res.redirect('/signin');
// Get the users details
User.findOne({
id: req.session.userId
})
.exec(function(err, user) {
// Server related error?
if (err) res.serverError(err.message);
// No user was found
if (!user) res.redirect('/signin');
// Load the ejs file that displays the users information
return res.view('account/index', {
title: 'Account Information',
user: user
});
});
},
// Account View
<p>Email: {{user.email}}</p>
<p>Password: {{user.password}}</p>
Check this out if you want to deal with password encryption: http://node-machine.org/machinepack-passwords
And this if you want to deal with the strength tests (when the user sets the password): https://www.npmjs.com/package/owasp-password-strength-test
This is as passport seems overkill if you're only doing local authentication?