loopback + passport + openID - passport.js

Starting from the existing loopback example concerning passport integration
loopback-example-passport
I'm trying to integrate an openId based external IDP, in my case KeyCloak.
Here the example original server.js code
'use strict';
var loopback = require('loopback');
var boot = require('loopback-boot');
var app = module.exports = loopback();
var cookieParser = require('cookie-parser');
var session = require('express-session');
// Passport configurators..
var loopbackPassport = require('loopback-component-passport');
var PassportConfigurator = loopbackPassport.PassportConfigurator;
var passportConfigurator = new PassportConfigurator(app);
/*
* body-parser is a piece of express middleware that
* reads a form's input and stores it as a javascript
* object accessible through `req.body`
*
*/
var bodyParser = require('body-parser');
/**
* Flash messages for passport
*
* Setting the failureFlash option to true instructs Passport to flash an
* error message using the message given by the strategy's verify callback,
* if any. This is often the best approach, because the verify callback
* can make the most accurate determination of why authentication failed.
*/
var flash = require('express-flash');
// attempt to build the providers/passport config
var config = {};
try {
config = require('../providers.json');
} catch (err) {
console.trace(err);
process.exit(1); // fatal
}
// -- Add your pre-processing middleware here --
// Setup the view engine (jade)
var path = require('path');
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
// boot scripts mount components like REST API
boot(app, __dirname);
// to support JSON-encoded bodies
app.middleware('parse', bodyParser.json());
// to support URL-encoded bodies
app.middleware('parse', bodyParser.urlencoded({
extended: true,
}));
// The access token is only available after boot
app.middleware('auth', loopback.token({
model: app.models.accessToken,
}));
app.middleware('session:before', cookieParser(app.get('cookieSecret')));
app.middleware('session', session({
secret: 'kitty',
saveUninitialized: true,
resave: true,
}));
passportConfigurator.init();
// We need flash messages to see passport errors
app.use(flash());
passportConfigurator.setupModels({
userModel: app.models.user,
userIdentityModel: app.models.userIdentity,
userCredentialModel: app.models.userCredential,
});
for (var s in config) {
var c = config[s];
c.session = c.session !== false;
passportConfigurator.configureProvider(s, c);
}
var ensureLoggedIn = require('connect-ensure-login').ensureLoggedIn;
app.get('/', function(req, res, next) {
console.debug("Passo di qui");
res.render('pages/index', {user:
req.user,
url: req.url,
});
});
app.get('/auth/account', ensureLoggedIn('/login'), function(req, res, next) {
res.render('pages/loginProfiles', {
user: req.user,
url: req.url,
});
});
app.get('/local', function(req, res, next) {
res.render('pages/local', {
user: req.user,
url: req.url,
});
});
app.get('/ldap', function(req, res, next) {
res.render('pages/ldap', {
user: req.user,
url: req.url,
});
});
app.get('/signup', function(req, res, next) {
res.render('pages/signup', {
user: req.user,
url: req.url,
});
});
app.post('/signup', function(req, res, next) {
var User = app.models.user;
var newUser = {};
newUser.email = req.body.email.toLowerCase();
newUser.username = req.body.username.trim();
newUser.password = req.body.password;
User.create(newUser, function(err, user) {
if (err) {
req.flash('error', err.message);
return res.redirect('back');
} else {
// Passport exposes a login() function on req (also aliased as logIn())
// that can be used to establish a login session. This function is
// primarily used when users sign up, during which req.login() can
// be invoked to log in the newly registered user.
req.login(user, function(err) {
if (err) {
req.flash('error', err.message);
return res.redirect('back');
}
return res.redirect('/auth/account');
});
}
});
});
app.get('/login', function(req, res, next) {
res.render('pages/login', {
user: req.user,
url: req.url,
});
});
app.get('/auth/logout', function(req, res, next) {
req.logout();
res.redirect('/');
});
app.start = function() {
// start the web server
return app.listen(function() {
app.emit('started');
var baseUrl = app.get('url').replace(/\/$/, '');
console.log('Web server listening at: %s', baseUrl);
if (app.get('loopback-component-explorer')) {
var explorerPath = app.get('loopback-component-explorer').mountPath;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
}
});
};
// start the server if `$ node server.js`
if (require.main === module) {
app.start();
}
and here my providers.json definition
{
"local": {
"provider": "local",
"module": "passport-local",
"usernameField": "username",
"passwordField": "password",
"authPath": "/auth/local",
"successRedirect": "/auth/account",
"failureRedirect": "/local",
"failureFlash": true
},
"keycloak": {
"provider": "keycloak",
"module": "passport-openid-connect",
"authScheme": "openid connect",
"scope": "openid",
"passReqToCallback":true,
"session": true,
"issuerHost": "https://[my host]/auth/realms/[my realm]",
"client_id": "[my client id]",
"client_secret": "[my client secret]",
"redirect_uri": "http://localhost:3000/auth/account",
"authPath": "/auth/keycloak",
"callbackPath": "/auth/keycloak/callback",
"response_type":"code",
"usePKCE": false,
"link":true
}
}
Testing the idp I'm able to be redirected, login and come back but I'm never logged and req.user is always undefined. Anything missing?

Solved by myself, hope helps others.
I've used another passport component, passport-openidconnect instead of passport-openid-connect.
This is the provider added to loopback-passport-example
"openid": {
"provider": "openid",
"module": "passport-openidconnect",
"issuer": "https://[my idp]/auth/realms/[my realm]",
"session": "true",
"clientID": "[my client id]",
"clientSecret": "[my client secret]",
"callbackURL": "/auth/openid/callback",
"authorizationURL": "[auth url]",
"tokenURL": "[token url]",
"userInfoURL": "[user info url]",
"scope": "auth_web openid profile email",
"authPath": "/auth/openid",
"callbackPath": "/auth/openid/callback",
"successRedirect": "/auth/account",
"failureRedirect": "/login",
"failureFlash": true
}
Remember to add the /auth/openid to the sample login page.

Related

req.user is blank even after authentication is successful using passportjs with saml

For my project I need to use authentication using ADFS + SAML.
I have followed the code and details as suggested at
http://www.passportjs.org/packages/passport-saml and sample code at https://github.com/gbraad/passport-saml-example
During authentication, the authentication is successful, but req.user is always undefined.
When I tried to print the req, using CircularJSON.stringyfy, I can see all the details inside SAMLResponse -> 'body'.
Here is my code snippet
routes.js
module.exports = function (app,config,passport) {
app.get('/', function (req, res) {
try {
if (req.isAuthenticated()) {
console.log(req.session)
res.send('user authenticated successfully' + req.user.name) // => If I print req using CircularJSON.stringify, I can see all the details.
} else {
//res.send('User not authenticated.')
res.redirect('/login')
}
} catch (error) {
console.log('Error ' + error);
}
});
app.get('/login',
passport.authenticate(config.passport.strategy,
{
successRedirect: '/',
failureRedirect: '/login'
})
);
}
app.js
const express = require('express');
const https = require('https');
const path = require('path');
const passport = require('passport');
const morgan = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
const session = require('express-session');
const errorhandler = require('errorhandler');
const fs = require('fs');
const config = require('./config/config');
console.log('Using configuration', config);
require('./config/passport')(passport, config);
var app = express();
app.use(express.static("public"));
app.set('port', 3000);
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader('Access-Control-Allow-Origin', 'https://localhost:3000');
// Request methods you wish to allow
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
// Request headers you wish to allow
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type');
// Set to true if you need the website to include cookies in the requests sent
// to the API (e.g. in case you use sessions)
res.setHeader('Access-Control-Allow-Credentials', true);
// Pass to next layer of middleware
next();
});
app.use(morgan('combined'));
app.use(cookieParser());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false }));
app.use(bodyParser.json({ type: 'application/*+json' }));
app.use(session(
{
resave: true,
saveUninitialized: true,
secret: 'my_secret',
cookie: {
secure: true,
httpOnly: true
}
}));
app.use(passport.initialize());
app.use(passport.session());
require('./config/routes')(app, config, passport);
const options = {
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem')
};
var my_server = https.createServer(options,app)
my_server.listen(3000);
passport.js
const SamlStrategy = require('passport-saml').Strategy;
module.exports = function (passport, config) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
passport.use(new SamlStrategy(
{
path: config.passport.saml.path,
entryPoint: config.passport.saml.entryPoint,
issuer: config.passport.saml.issuer,
},
function (profile, done) {
return done(null,
{
id: profile.uid,
email: profile.email,
displayName: profile.cn,
firstName: profile.givenName,
lastName: profile.sn
});
})
);
};
Thanks for your help.
Let me answer my own question, in case someone finds it useful.
The mistake I was making was in passport.use
When I changed it to following, it worked..
passport.use(new SamlStrategy(
{
path: config.passport.saml.path,
entryPoint: config.passport.saml.entryPoint,
issuer: config.passport.saml.issuer,
cert: config.passport.saml.cert,
},
function (profile, done) {
return done(null,
{
id: profile['nameID'],
email: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'],
displayName: profile['http://schemas.microsoft.com/identity/claims/displayname'],
name: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'],
lastName: profile['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname']
});
})
);

nodejs passport-azure-ad redirect to failureRedirect

Hello i am trying to understand and apply "passport-azure-ad" function which i found on https://learn.microsoft.com/en-us/graph/tutorials/node?tutorial-step=1 to my own web applicaton.
Instead of "hbs"template engine what they use in the tutorial i use "jade". I registered the application in Azure. When running the web-application I and noticed that the signin function is working (redirected to the login page of Microsoft to enter my credentials). But when i leave the credentials page it returns to the http://localhost:3000/error instead of **http://localhost:3000/succes ** what i expected.
Can you please help me?
Below i put the javascript files:
app.js
require('dotenv').config();
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var flash = require('connect-flash');
var passport = require('passport');
var OIDCStrategy = require('passport-azure-ad').OIDCStrategy;
var graph = require('./graph');
// Configure simple-oauth2
const oauth2 = require('simple-oauth2').create({
client: {
id: process.env.OAUTH_APP_ID,
secret: process.env.OAUTH_APP_PASSWORD
},
auth: {
tokenHost: process.env.OAUTH_AUTHORITY,
authorizePath: process.env.OAUTH_AUTHORIZE_ENDPOINT,
tokenPath: process.env.OAUTH_TOKEN_ENDPOINT
}
});
// Configure passport
// In-memory storage of logged-in users
// For demo purposes only, production apps should store
// this in a reliable storage
var users = {};
// Passport calls serializeUser and deserializeUser to
// manage users
passport.serializeUser(function(user, done) {
// Use the OID property of the user as a key
users[user.profile.oid] = user;
done (null, user.profile.oid);
});
passport.deserializeUser(function(id, done) {
done(null, users[id]);
});
// Callback function called once the sign-in is complete
// and an access token has been obtained
async function signInComplete(iss, sub, profile, accessToken, refreshToken, params, done) {
if (!profile.oid) {
return done(new Error("No OID found in user profile."), null);
}
try{
const user = await graph.getUserDetails(accessToken);
if (user) {
// Add properties to profile
profile['email'] = user.mail ? user.mail : user.userPrincipalName;
console.info(users)
}
} catch (err) {
done(err, null);
}
// Create a simple-oauth2 token from raw tokens
let oauthToken = oauth2.accessToken.create(params);
// Save the profile and tokens in user storage
users[profile.oid] = { profile, oauthToken };
return done(null, users[profile.oid]);
}
// Configure OIDC strategy
passport.use(new OIDCStrategy(
{
identityMetadata: `${process.env.OAUTH_AUTHORITY}${process.env.OAUTH_ID_METADATA}`,
clientID: process.env.OAUTH_APP_ID,
responseType: 'code id_token',
responseMode: 'form_post',
redirectUrl: process.env.OAUTH_REDIRECT_URI,
allowHttpForRedirectUrl: true,
clientSecret: process.env.OAUTH_APP_PASSWORD,
validateIssuer: false,
passReqToCallback: false,
scope: process.env.OAUTH_SCOPES.split(' ')
},
signInComplete
));
//setup routes
//require('./routes/routes')(app, passport);
var indexRouter = require('./routes/index');
var authRouter = require('./routes/auth');
var app = express();
app.use(session({
secret: 'your_secret_value_here',
resave: false,
saveUninitialized: false,
unset: 'destroy'
}));
// Flash middleware
app.use(flash());
// Set up local vars for template layout
app.use(function(req, res, next) {
// Read any flashed errors and save
// in the response locals
res.locals.error = req.flash('error_msg');
// Check for simple error string and
// convert to layout's expected format
var errs = req.flash('error');
for (var i in errs){
res.locals.error.push({message: 'An error occurred', debug: errs[i]});
}
next();
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// Initialize passport
app.use(passport.initialize());
app.use(passport.session());
app.use(function(req, res, next) {
// Set the authenticated user in the
// template locals
if (req.user) {
res.locals.user = req.user.profile;
}
next();
});
app.use('/', indexRouter);
app.use('/auth', authRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
auth.js
var express = require('express');
var passport = require('passport');
var router = express.Router();
/* GET auth callback. */
router.get('/signin',
function (req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res,
prompt: 'login',
failureRedirect: '/',
failureFlash: true,
successRedirect: '/'
}
)(req,res,next);
}
);
router.post('/callback',
function(req, res, next) {
passport.authenticate('azuread-openidconnect',
{
response: res,
failureRedirect: '/Error',
failureFlash: true,
successRedirect: '/Succes'
}
)(req,res,next);
}
);
graph.js
var graph = require('#microsoft/microsoft-graph-client');
require('isomorphic-fetch');
module.exports = {
getUserDetails: async function(accessToken) {
const client = getAuthenticatedClient(accessToken);
const user = await client.api('/me').get();
return user;
}
};
function getAuthenticatedClient(accessToken) {
// Initialize Graph client
const client = graph.Client.init({
// Use the provided access token to authenticate
// requests
authProvider: (done) => {
done(null, accessToken);
}
});
return client;
}

NodeJS app using CSRF for web and JWT for API does async.parallel out of order

When a logged-in user gets to a page through the browser using EJS I'm able to get the function to do what it's supposed to but when I use the API with Ionic using a logged in user with JWT, the async.parallel function doesn't "wait" to do things in order.
Here is my function:
console.log('1');
async.parallel([
function(callback){
buildAlertButtonsArray.getRealTestAlerts(req,function(arrayAlerts) {
console.log('2');
callback(null, arrayAlerts);
});
},
function(callback) {
if(req.decoded) //API
callback('API');
else //EJS
functions.aclSideMenu(req, res, function (acl) {callback(null, acl);}); //aclPermissions sideMenu
}
],function(err, results){
console.log('3');
})
when I login through the browsed on my console.log() is 1, 2, 3 but when I login through the API using JWT I get 1, 3, 2.
Here is my app.js:
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var app = express();
var bluebird = require('bluebird');
//me
var mongoose = require('mongoose');
var db = mongoose.connection;
var cors = require('cors');
var session = require('client-sessions');
var flash = require('express-flash');
//.js file
var routesApi = require('./routes/api');
var routesEjs = require('./routes/ejs');
var routes = require('./routes/index');
//var login = require('./routes/authentication/login');
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(cookieParser());
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.urlencoded({ extended: true })); //was FALSE by default. was TRUE for auth Template
// middleware
app.use(session({
cookieName: 'session',
secret: 'mysecret',
duration: 30 * 60 * 1000,
activeDuration: 30 * 60 * 1000,
httpOnly: true, //doesn't let javascript access cookies ever
secure: true, // only use cookies over https
ephemeral: true // delete this cookie when the browser is closed (nice when people use public computers)
}));
app.use(flash());
app.use(function(req, res, next){
res.locals.success_messages = req.flash('success_messages');
res.locals.error_messages = req.flash('error_messages');
next();
});
// use cors
app.use(cors());
app.use('/public', express.static(path.join(__dirname, 'public')));
app.use('/api', routesApi);
app.use('/', routes);
app.use('/', routesEjs);
//bluebird
mongoose.Promise = require('bluebird');
//connecting to database
mongoose.connect('mongodb://myip:2999/SMECS_database', { useMongoClient: true });
//if we connect successfully or if a connection error occurs
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function (callback) {
// yay!
});
// error handlers
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
Here is my Login function for both EJS using CSRF and API using JWT:
module.exports.postLogin = function(req, res, next) {
if (req.body.pushToken) { // run SMECS API
models.Users.findOne({
email: req.body.email.toLowerCase()
}, function (err, user) {
if (err) throw err;
if (!user) {
res.json({success: false, message: 'Authentication failed. User not found.'});
} else if (user) {
//check if password matches
if (!bcrypt.compareSync(req.body.pin, user.pin)) {
res.json({success: false, message: 'Authentication failed. Wrong password.'});
} else {
// if user is found and password is right
// create a token
var token = jwt.sign({user: user}, config.secret, {
//expiresIn: 1440 // expires in 24 hours
});
user.save(function (err) {
if (err) {
res.json({
success: false,
message: 'contact your system administrator. pushToken not saved'
});
} else {
// return the information including token as JSON
res.json({
success: true,
message: 'Welcome aboard!',
token: token,
userRoleID: user.userRoleID,
userRoleName: user.userRoleName,
userPrivilegeID: user.userPrivilegeID,
userPrivilegeName: user.userPrivilegeName,
firstName: user.firstName,
lastName: user.lastName,
email: user.email
});
}
});
}
}
});
}
else { //run SMECS EJS
models.Users.findOne({email: req.body.email.toLowerCase()}, function (err, user) {
if (!user || user.softDeleted !== null) {
//Parent Self Registration Login
models.ParentSelfRegistration.findOne({email: req.body.email.toLowerCase()}, function (err, parentSelfRegistration) {
if (!parentSelfRegistration) {
res.render('login', {error: "ERROR: Incorrect email or pin.", csrfToken: req.csrfToken()});
} else {
if (req.body.pin == parentSelfRegistration.pin) {
req.session.user = parentSelfRegistration;
res.redirect('/parentsSelfRegistration/registerParentStep1');
} else {
res.render('login', {error: "ERROR: Incorrect email or pin.", csrfToken: req.csrfToken()});
}
}
});
//END OF checks for users in UtilityUsers database
} else {
if (bcrypt.compareSync(req.body.pin, user.pin)) { // if user is found and password is right
req.session.user = user;
res.redirect('/dashboard');
//}
} else {
//res.status(400).send('Current password does not match');
res.render('login', {error: "ERROR: Incorrect email or pin.", csrfToken: req.csrfToken()});
//res.render('login', { error: "ERROR: Incorrect email or pin."});
}
}
});
}
};
Here is my ejs.js file:
//Dependencies
var express = require('express');
var routerEjs = express.Router();
var login = require('./authentication/login');
var auth = require('./authentication/auth');
var chooseAlert = require('./alerts/sendingReceiving/1.chooseAlert');
var login = require('./authentication/login');
var csrf = require('csurf');
routerEjs.use(csrf());
/* GET login page. */
routerEjs.get('/login', login.getLogin, function(req, res) {});
routerEjs.post('/login', login.postLogin, function(req, res) {});
routerEjs.get('/logout', login.getLogout, function(req, res) {});
module.exports = routerEjs;
and my api.js file:
//Dependencies
var express = require('express');
var routerApi = express.Router();
var login = require('./authentication/login');
var auth = require('./authentication/auth');
var chooseAlert = require('./alerts/sendingReceiving/1.chooseAlert');
routerApi.post('/login', login.postLogin, function(req, res) {});
routerApi.get('/chooseGroup', auth.auth, chooseAlert.showGroups, function(req, res) {});
routerApi.get('/alerts/sending/chooseAlert', auth.auth, chooseAlert.showAlerts, function(req, res) {});
/* Update pushToken ------------------------------------*/
routerApi.post('/updatePushToken', auth.auth, auth.pin, function(req, res) {});
module.exports = routerApi;
I figured out my problem. I was missing a NULL on my callback...
console.log('1');
async.parallel([
function(callback){
buildAlertButtonsArray.getRealTestAlerts(req,function(arrayAlerts) {
console.log('2');
callback(null, arrayAlerts);
});
},
function(callback) {
if(req.decoded) //API
callback(NULL, 'API');
else //EJS
functions.aclSideMenu(req, res, function (acl) {callback(null, acl);}); //aclPermissions sideMenu
}
],function(err, results){
console.log('3');
})

How to provide SP metadata to TestShib IdP using passport-saml?

I have a node.js script, using passport-saml, that simulates an SP. My goal is to connect it to this TestShib IdP but I get the following error: SAML 2 SSO profile is not configured for relying party.
Based on what I read here, I know that I need to provide the SP metadata, but I do not know how. I know that passport-saml has the function: generateServiceProviderMetadata(decryptionCert) and I while I have the certificates required, I do not know how to make it all work.
Also, I want to avoid having to register my SP if that is possible.
Here is my script:
const https = require('https');
const fs = require('fs');
const express = require('express');
const morgan = require('morgan');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const passport = require('passport');
const saml = require('passport-saml');
/*
---------------------------------------------------------------------------------------------------
-- certificates
---------------------------------------------------------------------------------------------------
*/
// for https server
const https_cert = fs.readFileSync('certificate.pem', 'utf-8');
const https_pvk = fs.readFileSync('privatekey.pem', 'utf-8');
// from idp's metadata
const idp_cert_1 = 'MIIDAzCCAeugAwIBAgIVAPX0G6LuoXnKS0Muei006mVSBXbvMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAMMEGlkcC50ZXN0c2hpYi5vcmcwHhcNMTYwODIzMjEyMDU0WhcNMzYwODIzMjEyMDU0WjAbMRkwFwYDVQQDDBBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg9C4J2DiRTEhJAWzPt1S3ryhm3M2P3hPpwJwvt2q948vdTUxhhvNMuc3M3S4WNh6JYBs53R+YmjqJAII4ShMGNEmlGnSVfHorex7IxikpuDPKV3SNf28mCAZbQrX+hWA+ann/uifVzqXktOjs6DdzdBnxoVhniXgC8WCJwKcx6JO/hHsH1rG/0DSDeZFpTTcZHj4S9MlLNUtt5JxRzV/MmmB3ObaX0CMqsSWUOQeE4nylSlp5RWHCnx70cs9kwz5WrflnbnzCeHU2sdbNotBEeTHot6a2cj/pXlRJIgPsrL/4VSicPZcGYMJMPoLTJ8mdy6mpR6nbCmP7dVbCIm/DQIDAQABoz4wPDAdBgNVHQ4EFgQUUfaDa2mPi24x09yWp1OFXmZ2GPswGwYDVR0RBBQwEoIQaWRwLnRlc3RzaGliLm9yZzANBgkqhkiG9w0BAQsFAAOCAQEASKKgqTxhqBzROZ1eVy++si+eTTUQZU4+8UywSKLia2RattaAPMAcXUjO+3cYOQXLVASdlJtt+8QPdRkfp8SiJemHPXC8BES83pogJPYEGJsKo19l4XFJHPnPy+Dsn3mlJyOfAa8RyWBS80u5lrvAcr2TJXt9fXgkYs7BOCigxtZoR8flceGRlAZ4p5FPPxQR6NDYb645jtOTMVr3zgfjP6Wh2dt+2p04LG7ENJn8/gEwtXVuXCsPoSCDx9Y0QmyXTJNdV1aB0AhORkWPlFYwp+zOyOIR+3m1+pqWFpn0eT/HrxpdKa74FA3R2kq4R7dXe4G0kUgXTdqXMLRKhDgdmA==';
const idp_cert_2 = 'MIIDAzCCAeugAwIBAgIVAPX0G6LuoXnKS0Muei006mVSBXbvMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAMMEGlkcC50ZXN0c2hpYi5vcmcwHhcNMTYwODIzMjEyMDU0WhcNMzYwODIzMjEyMDU0WjAbMRkwFwYDVQQDDBBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg9C4J2DiRTEhJAWzPt1S3ryhm3M2P3hPpwJwvt2q948vdTUxhhvNMuc3M3S4WNh6JYBs53R+YmjqJAII4ShMGNEmlGnSVfHorex7IxikpuDPKV3SNf28mCAZbQrX+hWA+ann/uifVzqXktOjs6DdzdBnxoVhniXgC8WCJwKcx6JO/hHsH1rG/0DSDeZFpTTcZHj4S9MlLNUtt5JxRzV/MmmB3ObaX0CMqsSWUOQeE4nylSlp5RWHCnx70cs9kwz5WrflnbnzCeHU2sdbNotBEeTHot6a2cj/pXlRJIgPsrL/4VSicPZcGYMJMPoLTJ8mdy6mpR6nbCmP7dVbCIm/DQIDAQABoz4wPDAdBgNVHQ4EFgQUUfaDa2mPi24x09yWp1OFXmZ2GPswGwYDVR0RBBQwEoIQaWRwLnRlc3RzaGliLm9yZzANBgkqhkiG9w0BAQsFAAOCAQEASKKgqTxhqBzROZ1eVy++si+eTTUQZU4+8UywSKLia2RattaAPMAcXUjO+3cYOQXLVASdlJtt+8QPdRkfp8SiJemHPXC8BES83pogJPYEGJsKo19l4XFJHPnPy+Dsn3mlJyOfAa8RyWBS80u5lrvAcr2TJXt9fXgkYs7BOCigxtZoR8flceGRlAZ4p5FPPxQR6NDYb645jtOTMVr3zgfjP6Wh2dt+2p04LG7ENJn8/gEwtXVuXCsPoSCDx9Y0QmyXTJNdV1aB0AhORkWPlFYwp+zOyOIR+3m1+pqWFpn0eT/HrxpdKa74FA3R2kq4R7dXe4G0kUgXTdqXMLRKhDgdmA==';
/*
---------------------------------------------------------------------------------------------------
-- passport-saml setup
---------------------------------------------------------------------------------------------------
*/
const saml_strategy = new saml.Strategy(
{
'callbackUrl': 'https://localhost:44300/login/callback',
'entryPoint': 'https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO',
'issuer': 'https://localhost:44300',
'decryptionPvk': https_pvk,
'cert': [idp_cert_1, idp_cert_2]
},
function (profile, done)
{
console.log('passport.use() profile: %s \n', JSON.stringify(profile));
return done(
null,
{
'nameIDFormat': profile.nameIDFormat,
'nameID': profile.nameID
}
);
}
);
passport.serializeUser(function (user, done) {
console.log('passport.serializeUser() user: %s \n', JSON.stringify(user));
done(null, user);
});
passport.deserializeUser(function (user, done) {
console.log('passport.deserializeUser() user: %s \n', JSON.stringify(user));
done(null, user);
});
passport.use(saml_strategy);
/*
---------------------------------------------------------------------------------------------------
-- express setup
---------------------------------------------------------------------------------------------------
*/
const app = express();
// configure view engine to render EJS templates
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
// additional settings for logging and parsing
app.use(morgan('dev'));
app.use(cookieParser());
app.use(bodyParser.urlencoded({'extended': true}));
app.use(session({ 'secret': 'this-is-secret', 'resave': false, 'saveUninitialized': false }));
// initialize Passport and restore authentication state, if any, from the session
app.use(passport.initialize());
app.use(passport.session());
/*
---------------------------------------------------------------------------------------------------
-- routes
---------------------------------------------------------------------------------------------------
*/
app.get(
'/',
function (req, res) {
if (req.isAuthenticated()) {
console.log('GET [/] user authenticated! req.user: %s \n', JSON.stringify(req.user));
res.render('home', { 'user': req.user });
} else {
console.log('GET [/] user not authenticated! \n');
res.render('home', { 'user': null });
}
}
);
app.get(
'/login',
passport.authenticate('saml', { 'successRedirect': '/', 'failureRedirect': '/login' })
);
app.post(
'/login/callback',
passport.authenticate('saml', { 'failureRedirect': '/', 'failureFlash': true }),
function(req, res) {
console.log('POST [/login] \n');
res.redirect('/');
}
);
app.get(
'/profile',
function(req, res){
if (req.isAuthenticated()) {
console.log('GET [/profile] user authenticated! req.user: %s \n', JSON.stringify(req.user));
res.render('profile', { 'user': req.user });
} else {
console.log('GET [/profile] user not authenticated! \n');
res.redirect('/login');
}
}
);
app.get(
'/logout',
function(req, res) {
console.log('GET [/logout] \n');
passport._strategy('saml').logout(
req,
function(err, requestUrl) {
req.logout();
res.redirect('/');
}
);
}
);
/*
---------------------------------------------------------------------------------------------------
-- start https server
---------------------------------------------------------------------------------------------------
*/
const server = https.createServer({
'key': https_pvk,
'cert': https_cert
}, app);
server.listen(44300, function() {
console.log('Listening on https://localhost:%d', server.address().port)
});
Thank you in advance for your help and guidance!
I know that passport-saml has the function: generateServiceProviderMetadata(decryptionCert) and I while I have the certificates required, I do not know how to make it all work.
For the sake of completeness, I'll list all the steps needed. But you've already done the first 2 so you can skip them:
Create the cert/key
In your SamlStrategy, define decryptionPvk
decryptionPvk: fs.readFileSync('./credentials/key.pem', 'utf-8'),
Lastly, you can create an endpoint that exposes your metadata (this is the part you're missing)
app.get('/metadata',
function(req, res) {
const decryptionCert = fs.readFileSync('./credentials/cert.pem', 'utf-8');
res.type('application/xml');
res.send((myStrategy.generateServiceProviderMetadata(decryptionCert)));
}
);
Here's what that would look like reformatted to match the sample code you posted:
app.get(
'/metadata',
function(req, res) {
res.type('application/xml');
res.send((saml_strategy.generateServiceProviderMetadata(https_cert)));
}
);
Once that's done you should be able to access your metadata from the /metadata path.
Also, I want to avoid having to register my SP if that is possible.
I'm not a SAML expert, but you probably won't be able to avoid registration. This would only work with an IdP that was set up to trust all SPs, but that wouldn't make much sense because a key part of SAML is the two-way trust that's established between the IdP and SP.

Securing Node + Postgres + Passport

I'm currently learning node, postgres, and passport but I am not sure on how to secure my application and best practices. I'm not sure on how to sanitize data from client and server or if it's needed but I've parameterized my queries. Any help would be greatly appreciated! Apologies for my newbieness.
var express = require('express');
var helmet = require('helmet')
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var expressSession = require('express-session');
var passport = require('passport');
var passportLocal = require('passport-local');
var pg = require ('pg');
var bcrypt = require('bcryptjs');
var app = express();
app.use(helmet());
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var config = {
user: 'REDACTED',
database: 'REDACTED',
password: 'REDACTED',
host: 'REDACTED',
port: 5432,
max: 10,
idleTimeoutMillis: 30000,
};
var pool = new pg.Pool(config);
pool.on('error', function (err, client) {
console.error('idle client error', err.message, err.stack)
})
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(expressSession( {
secret: process.env.SESSION_SECRET || 'secret',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new passportLocal.Strategy(function(username, password, done) {
pool.connect(function(poolErr, poolClient, poolDone) {
if(poolErr) {
return console.error('pool client fetch error', poolErr);
}
poolClient.query('SELECT id, password FROM users WHERE username = $1', [username], function(queryErr, queryRes) {
if(queryErr) {
return console.error('query error', queryErr);
}
if(queryRes.rows[0] != undefined)
{
bcrypt.compare(password, queryRes.rows[0].password, function(compareErr, compareRes) {
if(compareErr) {
return console.error('bcrypt error', compareErr);
}
if(compareRes) {
done(null, { id: queryRes.rows[0].id });
} else {
done(null, null);
}
poolDone();
});
} else {
//user not found
poolDone();
done(null, null);
}
});
});
}));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
pool.connect(function(err, poolClient, poolDone) {
if(err) {
return console.error('pool client fetch error', err);
}
poolClient.query('SELECT * FROM users WHERE id = $1', [id], function(queryErr, queryRes) {
if(queryErr) {
return console.error('query error', queryErr);
}
if(queryRes.rows[0] != undefined)
{
done(null, { id: id, username: queryRes.rows[0].username, firstname: queryRes.rows[0].firstname, lastname: queryRes.rows[0].lastname });
} else {
//user not found
done(null, null);
}
poolDone();
});
});
});
app.set('view engine', 'ejs');
app.get('/', function(req, res) {
res.render('index', {
isAuthenticated: req.isAuthenticated(),
user: req.user
})
});
app.get('/login', function(req, res) {
res.render('login');
});
app.post('/login', passport.authenticate('local'), function(req, res) {
res.redirect('/');
});
app.get('/logout', function(req, res) {
req.logout();
res.redirect('/');
});
server.listen(8080);
console.log('Server is running...');
I don't see much problem but here is my 2 cent to improve it.
Use compression to reduce network size
app.use(require('compression'));
Use serve-static for static file like images or pdf
app.use(require('serve-static')('./public'))
Use middleware to control end point that need Authentication like view/edit profile, change password and etc
function authenticated(req, res, next) {
if (req.isAuthenticated()) next();
else {
res.status(401).send('User not authenticated.');
// or redirect to login
}
}
app.get('/profile', authenticated, function(req, res) {
res.render('profile', {
req.user
})
})
Include session destroy when logout
app.get('/logout', function(req, res) {
req.session.destroy();
req.logout();
res.redirect('/');
});
for sanitizing queries you can use ORM like sequelise

Resources