Passport-SAML: read user information - node.js

Still a noob!
I am working on to build a Node application, and I have already setup various required end points. One of the requirements for my project is to use authentication using SAML mechanism. I am using passport-SAML for authentication in my application.
So far, I have been able to setup and use SAML strategy, and my application is able to call the idp entry point, and receive the response back from Idp.
I am unable to understand how do we access the user information returned by idp, so that I can use the SAML returned user information to create and maintain sessions.
const saml = require('passport-saml');
module.exports = function (passport, config) {
passport.serializeUser(function (user, done) {
done(null, user);
});
passport.deserializeUser(function (user, done) {
done(null, user);
});
var samlStrategyOptions = new saml.Strategy(
{
// URL that goes from the Identity Provider -> Service Provider
callbackUrl: config.passport.saml.callback_url,
// path: config.passport.saml.path,
// URL that goes from the Service Provider -> Identity Provider
entryPoint: config.passport.saml.entryPoint,
issuer: config.passport.saml.issuer,
identifierFormat: null,
// Service Provider private key
decryptionPvk: config.passport.saml.decryptionPvk,
// Service Provider Certificate
privateCert: config.passport.saml.privateCert,
// Identity Provider's public key
cert: config.passport.saml.cert,
validateInResponseTo: false,
disableRequestedAuthnContext: true
},
function (profile, done) {
return done(null,
{
id: profile.uid,
email: profile.email,
displayName: profile.cn,
firstName: profile.givenName,
lastName: profile.sn
});
})
// module.exports.samlStrategyOptions = samlStrategyOptions ;
passport.use(samlStrategyOptions);
};
Following are my route controllers for express
router.route('/login')
.get(
passport.authenticate(config.passport.strategy,
{
successRedirect: '/',
failureRedirect: '/login'
})
);
router.route('/login/callback/')
.post(
passport.authenticate(config.passport.strategy,
{
failureRedirect: '/',
failureFlash: true
}),
function (req, res) {
res.redirect('/');
}
);
And this is a SAML snippet of properties that I recieve in response from Idp.
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">Shubham123</saml:NameID>

I was getting the same. SO I have used body-parser as middleware
// middleware to parse HTTP POST's JSON, buffer, string,zipped or raw and URL encoded data and exposes it on req.body
app.use(bodyParser.json());
// use querystring library to parse x-www-form-urlencoded data for flat data structure (not nested data)
app.use(bodyParser.urlencoded({ extended: false }));
and then you will get the profile like
{ issuer: '',
sessionIndex: '_x0P5ZeWx-ACSQAulKgVTxSquNsVdac_H',
nameID: 'auth0|5a266569083226773d5d43a9',
nameIDFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified',
nameQualifier: undefined,
spNameQualifier: undefined,
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier': 'auth0|s9ds',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress': 'myuser#q.com',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name': 'myuser#q.com',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn': 'myuser#q.com',
'http://schemas.auth0.com/identities/default/provider': 'auth0',
'http://schemas.auth0.com/identities/default/connection': 'Username-Password-Authentication',
'http://schemas.auth0.com/identities/default/isSocial': 'false',
'http://schemas.auth0.com/email_verified': 'false',
'http://schemas.auth0.com/clientID': 'bZVOM5KQmhyir5xEYhLHGRAQglks2AIp',
'http://schemas.auth0.com/picture': 'https://s.gravatar.com/avatar/e85e57405a82225ff36b5af793ed287c?s=480&r=pg&d=https%3A%2F%2Fcdn.auth0.com%2Favatars%2Fsu.png',
'http://schemas.auth0.com/nickname': 'myuser',
'http://schemas.auth0.com/identities': '[object Object]',
'http://schemas.auth0.com/updated_at': 'Mon Dec 18 2017 12:14:28 GMT+0000 (UTC)',
'http://schemas.auth0.com/created_at': 'Tue Dec 05 2017 09:22:49 GMT+0000 (UTC)',
getAssertionXml: [Function] }
and create user by extracting data like
{ id: profile["nameID"], userName: profile["http://schemas.auth0.com/nickname"] }

In order to get the user details, in your IDP's console, you have to setup the parameters in SP settings which you want the IDP to return and you'll get them in the assertion.
This is what I did in onelogin:

I'm using the node-saml passport module and I found this example very useful.
To summarize, once the SAML process is resolved, (your IdP is making a POST callback to your handler), the user data is stored in the request object. Now, if you want to get that user data, for example in any GET request, you could do the following:
app.get('/logout', function(req, res) {
console.log('logout');
console.log(req.user);
req.logout();
res.redirect(config.passport.saml.logoutCallback);
});
Where req.user contains all your user data. In the example, req.user contains the following:
{
firstName: 'local givenName',
lastName: 'local lastname',
email: 'testUser#sample.com'
}

Related

How to get user details using passport-SAML in node js

I'm using passport-SAML in node js for SAML authentication.
my SAML Saml Strategy
passport.use(new SamlStrategy({
protocol: 'https://',
entryPoint: 'https://accounts.google.com/o/saml2/idp?idpid=', // SSO URL (Step 2)
issuer: 'https://.../sp', // Entity ID (Step 4)
path: '/auth/saml/callback' // ACS URL path (Step 4),
cert:"fake certificate"
}, function (profile, done) {
// Parse user profile data
done(null, {
email: profile.email,
name: profile.name
})
})
)
And my login code
app.get('/login', passport.authenticate('saml', {
successRedirect: '/',
failureRedirect: '/login'
}))
I have cloned project from
Git hub
I'm not able to get user details and also not able to print any console log in
'/login' route. How I can achieve this?

{message":"Failed to discover OP endpoint URL"} - passport-paypal in nodejs

My application is in nodejs, and im integrating for Paypal authentication using passport-paypal
Middleware code
const PPStrategy = require('passport-paypal').Strategy;
passport.use(
new PayPalStrategy(
{
clientID: 'My Paypal clientID',
clientSecret: 'My PayPal secret Id',
returnURL: 'localhost:4000/paypal/callback',
scope: 'openid'
},
function(identifier, done, next) {
return next(null, identifier);
}
)
);
ppRouter.get('/paypal', passport.authenticate('paypal'));
ppRouter.get(
'/paypal/callback',
passport.authenticate('paypal', { failureRedirect: '/Home' }),
function(req, res) {
//My logic on successful authentication
res.redirect('/Home/Payments');
}
);
The above implementation gives me the error
{"message":"Failed to discover OP endpoint URL"}
I have tried to pass callbackUrl instead of returnUrl URL in the strategy but i get the same issue.

Why won't my express-session persist?

I'm currently working on a MERN stack application that will utilize a login/sign-up form that is linked to my Mongo database. I have successfully been able to create a user, hash the user's password, and then store this user into the database, as well as log the user in by checking their credentials from the database.
When attempting to employ a solution that's industry standard, I decided to work with Passport and Express-session to implement authentication and sessions for each user when they log in. Once a user is created/logged in, passport correctly stores their information into req.user, and a session is created and logged with req.session. Below is the console log of req.user followed by req.session :
user with id xxx serialized
email: ...
password: ...
isAuthenticated: true
Session {
path: '/',
_expires: null,
originalMaxAge: null,
httpOnly: true },
passport: {user: xxx } }
This all displays right after I log-in, and to my knowledge this means that a session has been created and my user has been serialized properly. However, when I go to a new page on the webapp, I lose my session-- req.user becomes undefined, and req.sessionId changes each time the page does -- which leads me to believe that a new session with an empty user is created each time I switch pages (I am running a fetch method in my NavBar in React that triggers each time the Component mounts). Google Chrome also does not accurately log the cookies even when they are created as evidenced by the console logs.
My question is : why won't my session persist when I switch pages? My goal is to keep track of this session data to pass to my React frontend to render specific content. Is there a better way to go about this? Below is my middleware ordering:
app.use(session({
secret: 'test',
saveUninitialized: false,
resave: true
}));
app.use(passport.initialize());
app.use(passport.session());
Here are the methods I am calling in my router file:
router.get('/',function(req, res){
console.log(req.user);
console.log(req.session);
});
router.post('/login',passport.authenticate('local'), function(req,res)
{
console.log(req.user);
console.log(req.isAuthenticated());
console.log(req.session);
});
passport.serializeUser(function(user, done) {
console.log("user with id " + user._id + " serialized");
done(null, user._id);
});
passport.deserializeUser(function(id, done) {
console.log("deserializing user with id " + id + " ");
User.findById(id, function(err, user) {
done(err, user);
});
});
Here is my api fetch call from my front end:
componentDidMount(){
var current_user = "";
fetch("http://localhost:3001/users/", {
method: "get",
headers: {
'Accept':'application/json',
'Content-Type': 'application/json'
}
})
.then( (response)=> response.json())
.then( (response)=> {
if(response.user!==current_user){
current_user = response.user;
this.setState({
user: current_user
}), ()=> console.log(response);
}
})
}
I am also aware that currently the fetch method will not properly work: the goal is for that skeleton to work properly and have it return the correct id. What is logging improperly is the session and the req.user before I can even get the chance to pass it to my front end. Any and all help would be appreciated.

Passport & JWT & Google Strategy - Disable session & res.send() after google callback

Using: passport-google-oauth2.
I want to use JWT with Google login - for that I need to disable session and somehow pass the user model back to client.
All the examples are using google callback that magically redirect to '/'.
How do I:
1. Disable session while using passport-google-oauth2.
2. res.send() user to client after google authentication.
Feel free to suggest alternatives if I'm not on the right direction.
Manage to overcome this with some insights:
1. disable session in express - just remove the middleware of the session
// app.use(session({secret: config.secret}))
2. when using Google authentication what actually happens is that there is a redirection to google login page and if login is successful it redirect you back with the url have you provided.
This actually mean that once google call your callback you cannot do res.send(token, user) - its simply does not work (anyone can elaborate why?). So you are force to do a redirect to the client by doing res.redirect("/").
But the whole purpose is to pass the token so you can also do res.redirect("/?token=" + token).
app.get( '/auth/google/callback',
passport.authenticate('google', {
//successRedirect: '/',
failureRedirect: '/'
, session: false
}),
function(req, res) {
var token = AuthService.encode(req.user);
res.redirect("/home?token=" + token);
});
But how the client will get the user entity?
So you can also pass the user in the same way but it didn't felt right for me (passing the whole user entity in the parameter list...).
So what I did is make the client use the token and retrieve the user.
function handleNewToken(token) {
if (!token)
return;
localStorageService.set('token', token);
// Fetch activeUser
$http.get("/api/authenticate/" + token)
.then(function (result) {
setActiveUser(result.data);
});
}
Which mean another http request - This make me think that maybe I didnt get right the token concept.
Feel free to enlighten me.
Initialize passport in index.js:
app.use(passport.initialize());
In your passport.js file:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL:
'http://localhost:3000/auth/google/redirect',
},
async (accessToken, refreshToken, profile,
callback) => {
// Extract email from profile
const email = profile.emails![0].value;
if (!email) {
throw new BadRequestError('Login failed');
}
// Check if user already exist in database
const existingUser = await User.findOne({ email
});
if (existingUser) {
// Generate JWT
const jwt = jwt.sign(
{ id: existingUser.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update existing user
existingUser.token = jwt
await existingUser.save();
return callback(null, existingUser);
} else {
// Build a new User
const user = User.build({
email,
googleId: profile.id,
token?: undefined
});
// Generate JWT for new user
const jwt = jwt.sign(
{ id: user.id },
process.env.JWT_KEY,
{ expiresIn: '10m' }
);
// Update new user
user.token = jwt;
await auth.save();
return callback(null, auth);
}
}));
Receive this JWT in route via req.user
app.get('/google/redirect', passport.authenticate('google',
{failureRedirect: '/api/relogin', session: false}), (req, res) => {
// Fetch JWT from req.user
const jwt = req.user.token;
req.session = {jwt}
// Successful authentication, redirect home
res.status(200).redirect('/home');
}

Node.js passport-saml redirects to localhost:3000/login/callback all the time

I am using the tutorial from www.npmjs.org/package/passport-saml for the SAML. I am a beginner in SAML.
The tutorial says
The SAML identity provider will redirect you to the URL provided by the path configuration
I already have a OpenIdp account. It seems I can successfully login but the redirect URL always sends me to localhost:3000/login/callback which is not present in my code because I changed the 'path' to '/users/login-user-db-saml' or 'www.passporttoken.com:1234/users/login-user-db-saml' (both doesn't work and still sends me to the default login/callback).
I have the code below. What I am doing wrong?
/**start FOR SAML**/
passport.use(new SamlStrategy(
{
path: '/users/login-user-db-saml',
entryPoint: 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php',
issuer: 'passport-saml'
},
function(profile, done) {
findByEmail(profile.email, function(err, user) {
if (err) {
return done(err);
}
return done(null, user);
});
})
);
app.post('/users/login-user-db-sam',
passport.authenticate('saml', { failureRedirect: '/users/login-user-saml', failureFlash: true }),
function(req, res) {
res.redirect('/');
}
);
app.get('/users/login-user-saml',
passport.authenticate('saml', { failureRedirect: '/users/login-user-saml', failureFlash: true }),
function(req, res) {
res.redirect('/');
}
);
/**End for SAML**/
I removed the 'path' from the SAML configuration, and instead use a 'callbackUrl' with the full path to the callback specified. I also set 'issuer' as shown below:
saml : {
entryPoint : 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php',
issuer : 'http://192.168.56.101:3000',
callbackUrl : 'http://192.168.56.101:3000/login/callback'
}
You should also configure your SAML SP at OpenIdP on the metadata configuration page: https://openidp.feide.no/simplesaml/module.php/metaedit/edit.php - set the AssertionConsumerServiceURL on the SAML 2.0 tab to be your callbackUrl, and set the entityID to be the 'issuer' above.
Have you considered making your SAML Login route a POST request?
SAML wants it to be POST
The problem is in your strategy configuration; especially issuer. Your configuration point to the entity 'passport-saml', which is configured as is. Define your own entity and create settings you need.

Resources