i am trying to learn how to make a multi-tenant app with stormpath and node with express. This is the official document on that topic. As for now i am using express-stormpath lib to make my login and stuff. But i can not find how i do the multi-tenant.
UPDATE
I got it to work with passport stormpath strategy. I do not know if that is the right way but it works... The problem now is how do i change accountStore dynamic in the express version? It feels like a public declared variable is not so good?
var href = {
href: null
}
function hrefUrl(req, res, next){
var host = req.headers.host;
var account = host.split(".")[0];
spClient.getDirectories(function (err, directories) {
directories.each(function (dir, cb){
if(account.toLowerCase() == dir.name.toLowerCase()){
href.href = dir.href
}
cb();
}, function (err){
if(href.href == null){
return res.redirect(301, 'http://dashboard.local.dev/selectCompany');
}
next();
});
});
}
// Authenticate a user.
router.post('/login', hrefUrl, passport.authenticate('stormpath',
{
successRedirect: '/dashboard',
failureRedirect: '/login',
failureFlash: 'Invalid email or password.',
accountStore: href
}
)
);
Express-stormpath has provided APIs for you to access account information in your application. These accounts belong to directories. From the official document, you have two solutions to support multi-tenant. One is to create group per tenant, and another is to create directory per tenant.
For either solution you choose, you would have to use the APIs provided by express-stormpath to access these information associated with an account.
For example if you have created different directories for each tenant, you may need to add your business logics regarding to the multi-tenant in the postLoginHandler.
app.use(stormpath.init(app, {
postLoginHandler: function (account, req, res, next) {
account.getDirectory(function(err, directory) {
console.log(directory)
// if directory is tenant-1
// if directory is tenant-2
})
}
})
Related
Currently I am working on a passport-saml implementation in our NodeJS application.
The reason to do so is to give our customers the possibility to connect to their AD FS systems and take advantage of SingleSignOn(SSO).
As we also want to give logout functionality I was working on that logic. However, I can't seem to get this simple piece of functionality working. I have already googled a lot, tried a lot of variations and configurations but unfortunately, it does not work.
I would like to give our customers the possibility to SingleLogOut (SLO) that is both SP and IdP driven. This was my starting point. During the course of debugging and development I already took a step back and tried to kill the local session but even that is not possible it seems.
This is the relevant code from the routes I configured for SAML:
const isAuthenticated = (req, res, next) => {
if (req.isAuthenticated()) {
// User logged in, pass on to next middleware
console.info('User authenticated');
return next();
}
// User not logged in, redirect to login page
console.info('User not authenticated');
return res.redirect('/login');
};
// GET-routes
app.get('/',
isAuthenticated,
(req, res) => {
res.send('Authenticated');
});
app.get('/login',
passport.authenticate('saml', {
successRedirect: '/',
failureRedirect: '/login/fail',
}));
app.get('/logout',
(req, res) => {
passport._strategy('saml').logout(req, (err, url) => {
return res.redirect(url);
});
});
// POST-routes
app.post('/adfs/callback',
(req, res, next) => {
passport.authenticate('saml', (err, user) => {
// If error occurred redirect to failure URL
if (err) return res.redirect('/login/fail');
// If no user could be found, redirect to failure URL
if (!user) return res.redirect('/login/fail');
// User found, handle registration of user on request
req.logIn(user, (loginErr) => {
if (loginErr) return res.status(400).send(err);
// Request session set, put in store
store.set(req.sessionID, req.session, (storeErr) => {
if (storeErr) return res.status(400).send(storeErr);
return res.redirect('/');
});
});
})(req, res, next);
});
app.post('/logout/callback', (req, res) => {
// Destroy session and cookie
store.destroy(req.sessionID, async (err) => {
req.logout();
return res.redirect('/');
});
});
As can be seen I took control of the session store handling (setting and destroying sessions, but if this is unwise please advise).
The session store implemented is the MemoryStore (https://www.npmjs.com/package/memorystore).
What happens is that, when a user is logged in everything works fine.
Then a request is sent to route /logout, and some stuff happend and I can see the session changing, the session ID gets changed as well as relevant parameters for passport-saml (nameID, sessionIndex) and the user is then rerouted to '/'.
However then the user is seen as not authenticated and rerouted to '/login'. One would argue that it stops here, as the credentials have to be re-entered.
This is not the case, as the user is directly logged in again, without re-entering credentials and I do not know how to prevent this.
I do hope anybody knows what's going on :)
If there is need for additional information I would like to hear gladly.
So after much research and investigation I did found the solution for this problem.
The trick was in the definition of the passport-saml package, in particular the authncontext parameter.
So previously I had the SamlStrategy options defined as:
{
// URL that should be configured inside the AD FS as return URL for authentication requests
callbackUrl: `<URL>`,
// URL on which the AD FS should be reached
entryPoint: <URL>,
// Identifier for the CIR-COO application in the AD FS
issuer: <identifier>,
identifierFormat: null,
// CIR-COO private certificate
privateCert: <private_cert_path>,
// Identity Provider's public key
cert: <cert_path>,
authnContext: ["urn:federation:authentication:windows"],
// AD FS signature hash algorithm with which the response is encrypted
signatureAlgorithm: <algorithm>,
// Single Log Out URL AD FS
logoutUrl: <URL>,
// Single Log Out callback URL
logoutCallbackUrl: `<URL>`,
}
But after much research I realised this authentication:windows option was the culprit, so I changed it to:
{
// URL that should be configured inside the AD FS as return URL for authentication requests
callbackUrl: `<URL>`,
// URL on which the AD FS should be reached
entryPoint: <URL>,
// Identifier for the CIR-COO application in the AD FS
issuer: <identifier>,
identifierFormat: null,
// CIR-COO private certificate
privateCert: <private_cert_path>,
// Identity Provider's public key
cert: <cert_path>,
authnContext: ["urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
"urn:federation:authentication:windows"],
// AD FS signature hash algorithm with which the response is encrypted
signatureAlgorithm: <algorithm>,
// Single Log Out URL AD FS
logoutUrl: <URL>,
// Single Log Out callback URL
logoutCallbackUrl: `<URL>`,
},
Which basically means it won't retrieve the Windows credentials of the user that is logged onto the system by default, thus redirecting to the login screen of the ADFS server.
Using authnContext: ["urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"] in the session setup will show the username and password page again after logging out.
Logout achieved https://adfs-url/adfs/ls/?wa=wsignout1.0.
A new session-id is created after signing in again.
I have two user collections in my db and I want to make different login types for every one, so I have made two strategy on passport for my site ('local-user' & 'local-manager').
My question is how to check logged in user type (by used strategy) in my app?
In this code, passport just checks user auth but I want to check by strategy. (eg: if user logged in by 'local-manager', then show the page)
function isLoggedIn(req, res, next){
if (req.isAuthenticated()) {
next();
return;
}
res.redirect('/login');
}
It's better you use role mapping for this.
Anyway for now you can use this concept :
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
{passReqToCallback: true},
function(req, username, password, done) {
req.usedStrategy = 'local-user';
//do auth stuff
});
}
));
And use like this :
function isLoggedIn(req, res, next){
if (req.isAuthenticated() && req.usedStrategy === 'local-user') {
next();
return;
}
res.redirect('/login');
}
Also you can use session if you enable it in passport.
It must be said (and has been in other answers/comments) that you should really look again at your modelling of the domain. User objects can be really simple (just login information) and the rest can be broken out into other models/schemas.
Anyway on to the answer to your original question:
You can switch on the user type. Passport doesn't reach too far into the rest of your application. The log in strategies are not known about outside of the actual log in section.
You can handle that as middleware and add some extra information to the request object by checking for a unique property in one of the models:
function(request, response, next){
request.isManager = !!(request.user && request.user['unique_prop']);
next();
}
Place this after the auth middleware. Then in your route you can switch based on request.isManager. Also encapsulating this in middleware will abstract it from the user model and allow you to refactor it in the background.
Another alternative would be to add the function as a static/method/virtual (depending on the implementation) to the schema if you're using mongoose.
Hope this helps 👍 If you have further questions feel free to add comments and I can amend the answer. 🤔
I know this question is old, but I just had the same problem, and I managed to find a way to at least know what kind of account the user is. As someone else said, I don't think there is a way to see what strategy a user used to log in, but if you have a field in your user database to show what kind of account they are (e.g.: admin, client, etc.), then you can pass this information along in the req.user variable when you serialize the user:
passport.serializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, {
id: user.user_id,
username: user.user_email,
type: user.user_type
});
});
});
passport.deserializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, user);
});
});
In the example above, I'm using an SQL database, and one of the columns is called user_type. Once the user is deserialized, passport passes on the fields you ask for in passport.serialize() to req.user. So, if you wanted to know what type of account is currently logged in, you could say something like:
console.log("This user is a(n) " + req.user.type);
Or, something more realistic, if you're using Express:
app.get("/adminsOnly", (req, res) {
if (req.isAuthenticated() { // returns true if a user successfully logged in
if (req.user.type === "admin") { //check if user is an admin
res.render("adminsOnly.ejs"); //render admin section of website if true
} else {
res.redirect("/adminsLogin"); //redirected somewhere else because user is not an admin
}
} else {
res.redirect("/login"); //req.isAuthenticated() returns false, so user is redirected to the login page
}
});
I'm pretty new to coding in general, so I'm sure there are better ways to tackle this question, but this is a way to work around the problem of not being able to pinpoint which strategy the user logged in with.
I'm trying to use this library to authenticate using Linkedin:
https://github.com/auth0/passport-linkedin-oauth2
No Linkedin Login Prompt
I have configured my Passport Linkedin Strategy like so:
var passport = require('passport');
var LinkedInStrategy = require('passport-linkedin-oauth2').Strategy;
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
passport.use(new LinkedInStrategy({
clientID: 'LINKEDIN_API_KEY',
clientSecret: 'LINKEDIN_API_SECRET',
callbackURL: 'http://localhost:1337/auth/linkedin/callback',
scope: ['r_emailaddress', 'r_basicprofile'],
state: true
}, function(accessToken, refreshToken, profile, done) {
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's LinkedIn profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the LinkedIn account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}));
My AuthController.js looks like this:
var passport = require('passport');
module.exports = {
login: function(req, res) {
passport.authenticate('linkedin', function(err, user, info) {
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
});
},
callback: function(req, res) {
// ------------------------------------------------------------------------
// after user authenticated, we get the user's email from
// Linkedin's JSON response and save it against the matching
// email address in the User model
// ------------------------------------------------------------------------
console.log(res);
},
logout: function(req, res) {
req.logout();
res.send('logout successful');
}
};
From the linkedin oauth library, I expect the call to:
passport.authenticate('linkedin', function...);
In my AuthController's login action, to redirect the user to Linkedin's login prompt page but what I am actually seeing is my browser just keeps on loading, loading, loading and never stops.
Am I doing something wrong ?
Some questions I am not sure of:
Does Linkedin expect my server to be running on HTTPS before it lets this whole thing starts working ?
Is there some special configurations that I need to do in my Linkedin developers app setting ? (I've enabled all the correct Javascript SDK URLs)
Callback Error
OK, so continuing on, my next problem appears to be here:
return done(null, profile);
^
TypeError: object is not a function
My code is following the npm module instruction here: https://www.npmjs.com/package/passport-linkedin-oauth2
Maybe SailsJS has another way of writing it yet again....
Authentication Always Fails
After fixing the callback error as mentioned in my solution below, I decided to keep moving on and see how it goes despite the Linkedin documentation isn't quite matching 100% to what I expect from the NPM library.
My next problem is my authenticated.js policy appears to always fail.
My code is below:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.authenticated) { // <---- this is the error line
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
No Login Prompt Solution
sigh
I think I'm beginning to grasp some of the difference between SailsJS and pure ExpressJS codes.
The problem appears that I was missing this piece of code at the end of my passport.authenticate() method:
(req, res)
I picked it up after looking this tutorial again: http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/
So now, the final authenticate method should look like:
passport.authenticate('linkedin', function(err, user, info) {
// The request will be redirected to LinkedIn for authentication, so this
// function will not be called.
})(req, res); // <--- notice this extra (req, res) code here
Which matches the Passportjs documentation:
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
In a way....if you know what I mean... :D
Now I got my Linkedin login prompt as expected.
Finally!
Callback Error Solution
OK.....I'm not sure if this is completes the login process...but....
I noticed I had an extra line:
passReqToCallback: true
Taken from this page here:
https://github.com/auth0/passport-linkedin-oauth2/issues/29
I removed that and I got a different error message.
I've also changed my callback code to look like:
passport.authenticate('linkedin', function(err, user, info) {
res.json(200, {
user: user
});
})(req, res);
and I got my user JSON which appears to be my Linkedin user profile info:
{
user: {
provider: "linkedin",
...
}
}
But that's...contradicting the Linkedin documentation...I don't see any access_token or expire_in properties which I was expecting to see in step 3 of the Linkedin OAuth 2.0 documentation (https://developer.linkedin.com/docs/oauth2)...
So...supposedly...I should take this user object and create/update against an existing user object ?
Authentication Always Fails Solution
OK, so few more days, I added extra code to generate a User entity if one isn't found in my database, otherwise just return the found user.
The was one last problem, in my policies folder, I have a authenticated.js and it looked like this:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.authenticated) { // <---- this is the error line
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
Being new to all this web development stuff, I thought:
req.authenticated; // should call match name of the file ?
was correct but I was following this tutorial:
http://iliketomatoes.com/implement-passport-js-authentication-with-sails-js-0-10-2/
and he named his file: isAuthenticated.js I figured it's just a name....but I was wrong :D
Turns out, the correct code was:
req.isAuthenticated()
So in full, the correct code becomes:
// We use passport to determine if we're authenticated
module.exports = function (req, res, next) {
if(req.isAuthenticated()) { // alright, that's more like it!
return next();
}
else
{
res.send(401, {
error: 'Nice try buddy. Try logging in with Linkedin first :]'
});
}
};
Perhaps isAuthenticated is a Passportjs function and not just a name like I initially thought.
My further research shows this page which suggests so to me:
Problems getting Passport.js to authenticate user
Maybe req.authenticated can only be used for HTML email-password login form as suggested in above Stackoverflow post and req.isAuthenticated() is for OAuth stuff.
Anyhow, I still don't know if this is the right path but so far, I got authentication in my application now and I can access protected resources. Not sure how long I'll be logged in for, maybe I still need to build the refresh token thingo every 15 minute like the Linkedin documentation stated ?
Hope this helps other fellow Sailsjs users who are facing the same problem :)
Does Linkedin expect my server to be running on HTTPS before it lets
this whole thing starts working ?
No. The API works just as well on a local http setup.
Is there some special configurations that I need to do in my Linkedin
developers app setting ? (I've enabled all the correct Javascript SDK
URLs)
No, your setup is fine.
The browser keeps loading because after the authentication LinkedIn redirects to your callback action which isn't handling the response stream.
You need to handle the response in the callback action. Something like this will do:
callback: function(req, res) {
passport.authenticate('linkedin', function(err, user){
// handle error
// do something with the user (register/login)
return res.redirect('/home');
});
}
I'd highly recommend using sails-generate-auth for maintaining third-party logins. Very easy to setup and configure. All you need to do is serve the access tokens and secrets for the different strategies (either through config/passport.js or, preferably, through config/local.js). Will spare you a lot of redundant code.
Based on the user login status, I want to serve different results from the SAME api endpoint.
The usecase is having articles served from the same api endpoints.
So some articles meant to be public, while some private.
There are also special pages, where a list of articles can be queried.
Example:
api/articles/special/all
This API should return all pages currently in the database.
But it should also filter out private results, if the user is not logged in.
With Passport.js I can only protect the whole API endpoint, eg:
router.get('/', auth.isAuthenticated(), controller.index);
While I would like to call the auth.isAuthenticated() method from within the actual function, eg:
// Get list of articles
exports.index = function(req, res) {
Article.find(function (err, articles) {
if(err) { return handleError(res, err); }
var titles = [];
var loggedIn = false;
if (auth.isAuthenticated()) {loggedIn = true;} // NOT WORKING
for (var i = 0; i<articles.length; ++i) {
if (articles[i].role === 'loggedIn' && loggedIn) {
titles.push(articles[i].name);
} else if(articles[i].role !== 'loggedIn') {
titles.push(articles[i].name);
}
}
return res.json(200, titles);
});
};
Any idea, how to use Passport.js from within the controller, and not protecting the whole API endpoint?
I'm not sure how to accomplish this with passport.js, but if you're using something like Stormpath, this is quite easy to accomplish out of the box.
Once you've initialized / configured the express-stormpath middleware, it will automatically attempt to authenticate ANY user without making you explicitly use middleware.
So for instance, you could say:
var express = require('express');
var stormpath = require('express-stormpath');
var app = express();
app.use(stormpath.init(app));
app.get('/myapi', function(req, res) {
if (req.user) {
res.json({ status: 'YOU ARE LOGGED IN: ' + req.user.email });
} else {
res.json({ status: 'YOU ARE NOT LOGGED IN!' });
}
});
app.listen(3000);
Because Stormpath stores your users for you, if you're using the express-stormpath library this means that it will try to authenticate users either via a web session (in the browser), or a user's API keys (which Stormpath also stores).
This is why it's pretty simple to work, as the middleware will inspect the incoming HTTP headers and authenticate a user properly with either Basic Auth or OAuth2 =)
I used meanjs to generate a project.
There are hasAuthorization functions generated automatically for crud modules. Using the articles example in app/controllers/articles.server.controller.js the authorization is something like:
exports.hasAuthorization = function(req, res, next) {
if (req.article.user.id !== req.user.id) {
return res.status(403).send('User is not authorized');
}
next();
};
I want to add in user roles so admin or owner can edit like:
exports.hasAuthorization = function(req, res, next) {
if (req.article.user.id !== req.user.id) {
var isAdmin = false;
for (var userRoleIndex in req.user.roles) {
if ('admin' === req.user.roles[userRoleIndex]) {
isAdmin = true;
}
}
if (!isAdmin) {
return res.status(403).send('User is not authorized');
}
}
next();
};
Question 1 is this secure? Or maybe better question is How secure is this?
Question 2 if it is not secure then what is the Meanjs way to make it secure?
Do I have to do something like in this SO Question or is something already built in?
You don't have to do anything like in that answer, because (if I remember fine) meanjs is using passportjs for that stuff.
If I may suggest you, you could pass role from route, so you will not have admin role hardcoded, and this method will be more reusable. You can than use it like this:
app.route('/articles/:id')
.get(users.requiresLogin, users.hasAuthorization(['user']), articles.read)
.delete(users.requiresLogin, users.hasAuthorization(['admin']), articles.delete);