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.
Related
I am aware that there are there are loads of questions already on this particular topic and I have gone through all of them and none of the solutions have help so I am aware that this may be a local issue or something very obvious that I'm missing, however any help or tips that anyone could provide would be much appreciated.
I am using passport.js to authenticate users in my express app. The authentication doesn't seem to raise any errors and seems to execute correctly as the users are stored in the session data in the db, e.g.
session: {"cookie":{"originalMaxAge":3600000,"expires":"2023-01-18T15:00:13.646Z","secure":false,"httpOnly":true,"path":"/"},"passport":{"user":"63c69e118d89ef5921231f75"}}
Despite this, if I try to access req.user in any of the controller modules, it appears undefined.
I will paste my code below:
app.js
// Declare imported packages and modules
const express = require('express');
const MongoStore = require('connect-mongo');
const passport = require('passport');
const methodOverride = require('method-override');
const session = require('express-session');
const staticDirectory = require('serve-static');
require('dotenv').config({ path: './config/config.env' });
const dbConnect = require('./config/db');
const authRouter = require('./routes/authRoutes');
const moviesRouter = require('./routes/moviesRoutes');
const userRouter = require('./routes/userRoutes');
const errorHandler = require('./middleware/errorHandler');
// Declare variables for server
const HOSTNAME = process.env.HOST || 'localhost';
const PORT = process.env.PORT || 3000;
// Instantiate express app
const app = express();
// Add middleware to instantiated app
app.use(express.json());
app.use(staticDirectory(__dirname + '/public'));
app.use(methodOverride('_method')); // TODO: read about method override
// Add session middleware and declare session parameters
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
cookie: { maxAge: 1000 * 60 * 60 },
store: MongoStore.create({
mongoUrl: `${process.env.DB_SERVER}/${process.env.database}`,
collection: 'sessions'
})
}));
// Add passport middleware
app.use(passport.initialize());
app.use(passport.session());
// Add routes to defined endpoints
app.use('/auth', authRouter);
app.use('/user/favourites', userRouter);
app.use('/', moviesRouter);
// Add error-handling middleware
app.use(errorHandler);
// Connect the db
dbConnect();
// Run the server
app.listen({ path: HOSTNAME, port: PORT }, (error) => {
if (error) return console.log(error);
console.log(`Server is running on port ${PORT}...`);
});
passport-config.js
onst LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');
const User = require('../models/User');
// Middleware function to initialise passport
const initialize = (passport) => {
// Defining the fields used to login
const customFields = {
usernameField: 'email',
passwordField: 'password'
};
// Function to determin how authentication is handled
const authenticateUser = async (email, password, done) => {
// Finding user in the db
const user = await User.findOne({ email: email });
if (!user) {
// No user found returns an error
return done(null, false, { message: `No user with email ${email} found` });
}
try {
// Checking password entered for user hashed against the stored hash
if (await bcrypt.compare(password, user.hash)) {
return done(null, user);
} else {
// return an error if the hashes don't match
return done(null, false, { message: 'Password incorrect' });
}
} catch (error) {
return done(error);
}
}
passport.use(new LocalStrategy(customFields, authenticateUser));
// Built-in passport.js method to add user id in session cookie info
passport.serializeUser((user, done) => {
return done(null, user.id);
});
// Built-in passport.js method to extract user id from session to obtain user info
passport.deserializeUser(async (id, done) => {
const user = await User.findOne({ _id: id });
return done(null, user);
})
}
module.exports = initialize;
In my authControllers file, I have tried a couple of options:
exports.login = (req, res, next) => {
try {
// Login using passport authenticate function
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/'
})(req, res, next);
} catch (error) {
next(error);
}
}
and
exports.login = (req, res, next) => {
try {
// Login using passport authenticate function
passport.authenticate('local', (error, user, info) => {
if (error) return next(error);
if (!user) return res.redirect('/');
req.login(user, (err) => {
if (err) return next(err);
res.redirect('/');
})
})(req, res, next);
} catch (error) {
next(error);
}
}
I have also tried importing cors module and adding
app.use(
cors({
origin: `http://localhost:${process.env.PORT}`,
credentials: true,
})
);
and also adding
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", req.headers.origin);
res.header("Access-Control-Allow-Credentials", true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
if ('OPTIONS' == req.method) {
return res.send(200);
}
next();
});
I have also tried adding { session: true } to the options for passport.authenticate(), switching from express-session to cookie-session, adding bodyParser middleware, adding cookieParser middleware. I checked the order of app.use(session({...})) and the passport functions and tried removing some of the middleware to see if it was interfering. I also tried manually saving the user in the login function and adding the user to res.locals and without results. And also importing passport-config.js after the passport functions:
app.use(passport.initialize());
app.use(passport.session());
const initializePassport = require('./config/passport-config')(passport);
All of which without results.
Sorry for the long question and as I said, any insight on what is going wrong here would be much appreciated. Thanks!
I have a question about deleting data in the database using Express and NodeJS as I have a form that is deleted.pug to get the ID input from the user then used that ID to find the record in the database to delete that.
I don't know why when I clicked on the "Delete Record" button in the index.pug, I get the error "Cannot GET /delete/". Therefore, I couldn't load the form for the user to input the id.
However, in the address bar, when I typed http://localhost:3000/api/delete/5, then the city with the id is 5 was deleted in the database. Can someone help me so that I can load the delete form to get the input from the user instead of letting them directly put the id in the URL? Thank you, and I appreciate it
api.js
const express = require('express');
const mysql = require('../config/db.config');
const router = express.Router();
router.get('/delete/:id', (req, res) => {
const sql = 'DELETE FROM tblCitiesImport WHERE id=?';
mysql.query(sql, req.params.id, (err) => {
if (err) throw err;
res.render('../views/deletesuccess');
});
});
module.exports = router;
delete.pug
html
head
body
form(action='/api/delete', method='POST')
p
| ID:
input(name='ID', value='', type='text')
input(value='Submit', type='submit')
index.pug
extends layout
block layout-content
div.View.WelcomeView
h1.Banner City Database Site
div.Message
div.Title
h3 IS219 Final
h1 Cities Project
span.Details Access the Cities Records Web Portal
div.NavButtons
if isAuthenticated
a(href="http://localhost:8000")
div.NavButton View Records
a(href="/user")
div.NavButton My Profile
a(href="/delete/")
div.NavButton Delete Record
a(href="/logout")
div.NavButton Logout
else
a(href="/login")
div.NavButton Log in
db structure
CREATE DATABASE citiesData;
use citiesData;
CREATE TABLE IF NOT EXISTS tblCitiesImport (
`id` int AUTO_INCREMENT,
`fldName` VARCHAR(21) CHARACTER SET utf8,
`fldLat` NUMERIC(6, 4),
`fldLong` NUMERIC(7, 4),
`fldCountry` VARCHAR(19) CHARACTER SET utf8,
`fldAbbreviation` VARCHAR(3) CHARACTER SET utf8,
`fldCapitalStatus` VARCHAR(7) CHARACTER SET utf8,
`fldPopulation` INT,
PRIMARY KEY (`id`)
);
app.js
const express = require('express');
const path = require('path');
const expressSession = require('express-session');
const passport = require('passport');
const Auth0Strategy = require('passport-auth0');
require('dotenv').config();
const open = require('open');
const bodyParser = require('body-parser');
const cors = require('cors');
const jwt = require('express-jwt');
const jwtAuthz = require('express-jwt-authz');
const jwksRsa = require('jwks-rsa');
const apiRouter = require('./routes/api');
const dbConn = require('./config/db.config');
const authRouter = require('./auth');
const citiesRoutes = require('./routes/cities.routes');
// App Variables
const app = express();
const port = process.env.PORT || '3000';
// Session Configuration
const session = {
secret: process.env.SESSION_SECRET,
cookie: {},
resave: false,
saveUninitialized: false,
};
if (app.get('env') === 'production') {
// Serve secure cookies, requires HTTPS
session.cookie.secure = true;
}
// Passport Configuration
const strategy = new Auth0Strategy(
{
domain: process.env.AUTH0_DOMAIN,
clientID: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
callbackURL: process.env.AUTH0_CALLBACK_URL,
},
(accessToken, refreshToken, extraParams, profile, done) => {
// Access tokens are used to authorize users to an API
// accessToken is the token to call the Auth0 API or a secured 3rd-party API
// extraParams.id_token has the JSON Web Token (JWT)
// profile has all the information from the user
return done(null, profile);
},
);
// App Configuration
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(express.static(path.join(__dirname, 'public')));
app.use(expressSession(session));
app.use(bodyParser.urlencoded({ extended: true }));
passport.use(strategy);
app.use(passport.initialize());
app.use(passport.session());
passport.serializeUser((user, done) => {
done(null, user);
});
passport.deserializeUser((user, done) => {
done(null, user);
});
// Creating custom middleware with Express
app.use((req, res, next) => {
res.locals.isAuthenticated = req.isAuthenticated();
next();
});
// Authorization middleware. When used, the
// Access Token must exist and be verified against
// the Auth0 JSON Web Key Set
const checkJwt = jwt({
// Dynamically provide a signing key
// based on the kid in the header and
// the signing keys provided by the JWKS endpoint.
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: 'https://YOURSDOMAIN.us.auth0.com/.well-known/jwks.json'
}),
// Validate the audience and the issuer.
audience: 'http://localhost:3000/api/v1/cities',
issuer: ['https://YOURSDOMAIN.us.auth0.com/'],
algorithms: ['RS256'],
});
// Router Mounting
app.use('/', authRouter);
app.use('/api', apiRouter);
// Routes Definitions
const secured = (req, res, next) => {
if (req.user) {
return next();
}
req.session.returnTo = req.originalUrl;
res.redirect('/login');
};
app.get('/', (req, res) => {
res.render('index', { title: 'Home' });
});
app.get('/user', secured, (req, res, next) => {
const { _raw, _json, ...userProfile } = req.user;
res.render('user', {
title: 'Profile',
userProfile: userProfile,
});
});
// This route is not needed authentication
app.get('/api/public', (req, res) => {
res.json({
message: 'You are in the public endpoint! Authentication is not needed to see this.',
});
});
// This route is needed authentication
app.get('/api/private', checkJwt, (req, res) => {
res.json({
message: 'You are in the private endpoint! Authentication is needed to see this.',
});
});
// using as middleware
app.use('/api/v1/cities', citiesRoutes);
app.set('port', process.env.PORT || 8000);
app.listen(port, () => {
console.log(`Listening to requests on http://localhost:${port}`);
});
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;
}
I am using passport.js for twitter social login. In my Web app after a successful login, it will populate the tweets accordingly and will reply to the tweets as per the loggedIn user credentials. Currently, I am able to successfully redirect after login, but not able to fetch the user details which passport gives after successful login i.e (username, email). As a result, I am not able to go further.
Below is the node server.js file.
Kindly help me with this issue.
server.js
const express = require('express');
const TwitterStrategy = require('passport-twitter').Strategy;
const twitConfig = require('./config/auth').twitterAuth;
const passport = require('passport');
const path = require('path');
const bodyParser = require('body-parser');
// Create a new Express application.
const app = express();
// require('./routes/')(app);
// Use application-level middleware for common functionality, including
// logging, parsing, and session handling.
app.use(require('cookie-parser')());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
//initialize passport strategy for twitter login.
passport.use(new TwitterStrategy({
consumerKey: twitConfig.consumer_key,
consumerSecret: twitConfig.consumer_secret,
callbackURL: twitConfig.callbackURL
}, (token, tokenSecret, profile, callback) => {
console.log('arguments in passport.use>>>>>>>>>>>>>>>', arguments);
console.log('profile in twitterStrategy>>>>>>>>>>>>>', profile,callback);
process.nextTick(() => {
if (err) {
console.log('err in passport TwitterStrategy>>>>>>>>>>>',err)
return callback()
} else {
console.log('inside the else condition no error found>>>>>>>>>>',profile)
}
})
return callback(null, profile);
}));
const twit = require('twit');
const T = new twit(twitConfig);
passport.serializeUser(function(user, callback) {
console.log('arguments in serializeUser passport.use>>>>>>>>>>>>>>>', arguments);
callback(null, user);
})
passport.deserializeUser(function (obj, callback) {
console.log('arguments in deserializeUser passport.use>>>>>>>>>>>>>>>', arguments);
callback(null, obj);
});
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
//load the routes
app.use(require('./routes/twitterApi'));
app.use(require('./routes/token'));
app.use(passport.initialize());
app.use(passport.session());
app.get('/', function (req, res) {
console.log('user in />>>>>>>>>>>>>>', req);
res.render('index', {user: req.user})
})
app.get('/twitter/login', passport.authenticate('twitter'), (req, res) => {
console.log('consoe.log req>>>>>>>>>>>>>', req);
console.log('res >>>>>>>>>>',res);
});
app.get('/twitter/return', passport.authenticate('twitter', { failureRedirect: '/' }), function (req, res) {
console.log('inside the return failure');
res.redirect('http://localhost:8080/');
})
// listen for requests :)
const listener = app.listen(process.env.PORT || 9000, () => {
console.log('Your app is listening on port ' + listener.address().port);
});
Here is an async/await example of how to perform a database operation on the User, after authenticating with passport strategy.
passport.use(new TwitterStrategy({
consumerKey: twitConfig.consumer_key,
consumerSecret: twitConfig.consumer_secret,
callbackURL: twitConfig.callbackURL
}, async (token, tokenSecret, profile, callback) => {
try {
// Perform some kind of db lookup/create with profile object
const user = await User.findOne({
where: {
email: profile.email
}
})
// now your user object will be passed to your callbackURL
return callback(null, user)
}
catch(error) {
return callback(error, error.message);
}
I have tested my whole app locally and it works perfectly, but the moment I deploy it to google app engine, The entire Oauth2 process by logging in with google stops working. I do not know what the problem is.
One use case I am testing write now is to have a ejs file with a link that says login. If you click on that link it should use google oauth2 to log you in and the page that is shown is your display name (obtained from your google account) after you log in. When I refresh this site, this display name disappears signifying that it has logged out (for some reason) even though it is supposed to stay logged in. On my localhost copy, it works completely, it stays logged in and the display name stays in tact.
const express = require('express');
const Datastore = require('#google-cloud/datastore');
const bodyParser = require('body-parser');
const passport = require('passport');
const session = require('express-session');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const app = express();
const datastore = Datastore();
app.enable('trust proxy');
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(require('cookie-parser')());
const projectId = 'ece6102-guestbookapp';
const GOOGLE_CLIENT_ID = "";
const GOOGLE_CLIENT_SECRET = "";
const Logging = require('#google-cloud/logging');
function getLogEntries () {
// Instantiates a client
const logging = Logging();
const options = {
pageSize: 10,
filter: 'resource.type="cloud_function"'
};
// Retrieve the latest Cloud Function log entries
// See https://googlecloudplatform.github.io/gcloud-node/#/docs/logging
return logging.getEntries(options)
.then(([entries]) => {
console.log('Entries:');
entries.forEach((entry) => console.log(entry));
return entries;
});
}
passport.serializeUser(function(user, done) {
done(null, user);
});
passport.deserializeUser(function(obj, done) {
done(null, obj);
});
// Use the GoogleStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Google
// profile), and invoke a callback with a user object.
passport.use(new GoogleStrategy({
clientID: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback",
//NOTE :
//Carefull ! and avoid usage of Private IP, otherwise you will get the device_id device_name issue for Private IP during authentication
//The workaround is to set up thru the google cloud console a fully qualified domain name such as http://mydomain:3000/
//then edit your /etc/hosts local file to point on your private IP.
//Also both sign-in button + callbackURL has to be share the same url, otherwise two cookies will be created and lead to lost your session
//if you use it.
passReqToCallback : true
},
function(request, accessToken, refreshToken, profile, done) {
console.log("Calling into this");
// asynchronous verification, for effect...
process.nextTick(function () {
// To keep the example simple, the user's Google profile is returned to
// represent the logged-in user. In a typical application, you would want
// to associate the Google account with a user record in your database,
// and return that user instead.
return done(null, profile);
});
}
));
function insertMessage (message, guestbook, user) {
let mess = 'An anonymous person wrote:';
if (user) {
mess = user.displayName + ' wrote:';
}
return datastore.save({
key: datastore.key(guestbook),
data: {
timestamp: new Date(),
username: mess,
message: message
}
});
}
function getMessages (guestbook) {
const query = datastore.createQuery(guestbook)
.order('timestamp', { descending: true })
.limit(10);
return datastore.runQuery(query)
.then((results) => {
return results[0];
})
.catch((error) => {
});
}
app.set('views', './views');
app.set('view engine', 'ejs');
app.use(express.static('public'));
app.use( session({
secret: 'cookie_secret',
resave: true,
saveUninitialized: true
}));
app.use( passport.initialize());
app.use( passport.session());
app.get('/', (req, res) => {
console.log(req.query);
res.render('index', {
user: req.user
});
// if ('book' in req.query) {
// getMessages(req.query['book']).then((results) => {
// res.render('index', {
// messages: results,
// guestbookname: req.query['book'],
// user: req.user
// });
// });
// } else {
// getMessages('message').then((results) => {
// res.render('index', {
// messages: results,
// guestbookname: 'message',
// user: req.user
// });
// });
// }
});
app.post('/addEntry', (req, res) => {
console.log(req.user);
insertMessage(req.body.entry, req.body.book, req.user)
.then(() => getMessages())
.then((messages) => {
res.redirect('/?book=' + req.body.book);
});
});
app.get('/auth/google', passport.authenticate('google', { scope: [
'https://www.googleapis.com/auth/plus.login']
}));
app.get('/auth/google/callback', passport.authenticate('google'), (req, res) => {
res.redirect('/');
});
app.get('/auth/logout', (req, res) => {
req.logout();
res.redirect('/');
});
const server = app.listen(8080, () => {
const host = server.address().address;
const port = server.address().port;
console.log(`Example app listening at http://${host}:${port}`);
});
This is a copy of the code I have written and some pics as to what its supposed to do:
This the the first image with the login button
This is the second image after logging in