PassportJS openid-client How to use without a session, save token/user in normal cookie - node.js

We are currently using the https://github.com/panva/node-openid-client strategy for passportJS, along with cookie-session.
However we would like to try and move away from sessions and just store either a simple cookie with a token, or attach the token to a header on each request which we then introspect to see whether the token is valid.
I can't figure it out, maybe it's not possible, I just simply don't know where or how I can retrieve the token from the openid-client library, and when and how I should save it in a cookie. Maybe it is only built to use a session.
currently we have:
passport.use(
`oidc.${site}`,
new Strategy(
{
client,
params: getParamsForSite(site),
passReqToCallback,
usePKCE,
},
(tokenset, done) => {
const user = {
token: tokenset,
name: tokenset.claims.sub,
};
return done(null, user);
}
)
);
for the login
app.get(['/login', '/login/:site'], (req, res, next) => {
if (req.params.site) {
passport.authenticate(`oidc.${req.params.site}`)(req, res, next);
} else {
res.end(loginFrontend.success());
}
});
and for the callback
app.get('/auth_callback', (req, res, next) => {
passport.authenticate(`oidc.${req.query.state}`, {
callback: true,
successReturnToOrRedirect: process.env.BASE_URI,
})(req, res, next);
});
We would like to continue using this library as the authentication service we call has a discovery endpoint etc. and wouldn't want to implement all of the features ourselves. If I set session to false, how do I retrieve the token and where for this strategy, can someone help me?

Related

Next.js implementing google signin with /api routes and passport

I'm trying to implement Google signin in my serverless (/api routes) next.js app.
I'm using #passport-next/passport-google-oauth2, next-connect and passport packages.
I searched a lot and found a few helpful links online but I couldn't make it work, and I'm not sure about the whole flow that should happen here.
For example, I found those:
https://github.com/andycmaj/nextjs-passport-session-auth
https://todayilearned.io/til/nextjs-with-passport-oauth-cookie-sessions
I have /api/auth/login route for regular login. If login was successful, I'm setting JWT cookie on user response.
For Google login, I added /api/auth/social/google route, with the following code:
import passport from 'passport';
import { Strategy as GoogleStrategy } from '#passport-next/passport-google-oauth2';
import nextConnect from 'next-connect';
passport.use(new GoogleStrategy({
clientID: process.env.OAUTH_GOOGLE_CLIENT_ID,
clientSecret: process.env.OAUTH_GOOGLE_CLIENT_SECRET,
callbackURL: process.env.OAUTH_GOOGLE_CALLBACK_URL,
scope: "https://www.googleapis.com/auth/plus.login",
},
(accessToken, refreshToken, googleUserInfo, cb) => {
console.log('accessToken, refreshToken, googleUserInfo');
cb(null, googleUserInfo);
}
));
export default nextConnect()
.use(passport.initialize())
.get(async (req, res) => {
passport.authenticate('google')(req, res, (...args) => {
console.log('passport authenticated', args)
})
})
and /api/auth/social/callback/google route, with the following code:
import passport from 'passport';
import nextConnect from 'next-connect';
passport.serializeUser((user, done) => {
console.log('serialize')
done(null, user);
});
export default nextConnect()
.use(passport.initialize())
.get(async (req, res) => {
passport.authenticate('google', {
failureRedirect: '/failure/success',
successRedirect: '/auth/success',
})(req, res, (...args) => {
console.log('auth callback')
return true;
})
})
So what happens is that the user is redirected to /auth/success after signin to his google account, and console logs are:
accessToken, refreshToken, googleUserInfo
serialize
So my questions are:
When and how can I set the JWT cookie on the response to "login" the user?
Why the line console.log('auth callback') never runs? when it should run?
The same for console.log('passport authenticated', args)
How is a complete flow should look like in my app?
Thanks !
I had the same problem. I'm still working on the full implementation, but according to this thread it looks like the problem might be that passport.authenticate is supposed to be used as a middleware:
// api/auth/social/callback/google
export default nextConnect()
.use(passport.initialize())
.get(passport.authenticate("google"), (req, res) => {
console.log('auth callback')
res.writeHead(302, {
'Location': '/auth/success'
});
res.end();
})
Ok, I'm not a Next.js expert but I had a similar "problem", only using other authentication method.
You see, the framework has a method that is really important in the authentication process, they are called: getInitialProps, which is an async function that can be added to any page as a static method to trigger before the initial render;
it looks like this:
static async getInitialProps(ctx) {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
It takes this ctx prop, which is a object with {req, res} that you can use to make your request to authenticate the user and, then, you may use req with the lib next-cookies to get the JWT token and check if it is valid and, then, you may return an object, which will be used as a prop of your page (or all the pages, if you wrap your _app.js around a Context of Authentication.
Also, your 'auth callback' is not being called because you redirect the user before it triggers, which is not happening. The same for 'passport authenticated'
This article may also help you.
https://dev.to/chrsgrrtt/easy-user-authentication-with-next-js-18oe

ExpressJS/Passport-SAML Single Log Out re-logs in directly

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.

How to add JWT authentication with passport-steam

Simple JWT authenticated mern app (here) in place using redux and create-react-app. I want to add passport-steam as an authentication method, however I'm not sure how to handle adding a JWT to the passport-steam strategy. Below is what I have:
// Steam
router.get('/steam', passport.authenticate('steam', { session: false }));
router.get(
'/steam/return',
passport.authenticate('steam', { session: false }),
(req, res) => {
const user = req.user
jwt.sign(
{ id: user.id },
'JWT_secret',
{ expiresIn: '2h' },
(err, token) => {
if (err) throw err;
res.json({
user: user,
token,
});
}
);
res.redirect('/')
}
)
Using the default steam strategy It works and user data is added to the DB, but there's no token in local storage. I'm not sure how to do it with steam as I'm not dispatching an action/not sure If I can. Do I need to authenticate via steam, grab and save the data to the database and then dispatch another action to retrieve it and add the JWT, or is there a better method?

When to set session to false when using passport with Nodejs

I have just completed my signup auth with passport.js but I kept on getting error when I was trying to use the login auth
Error: Failed to serialize user into session
This was my post route :
router.post("/login",passport.authenticate('local-login'),function(req,res){
res.redirect("/users")
if (req.user) {
console.log("Logged In!")
} else {
console.log("Not logged in!")
}
})
I saw a comment on stackoverflow that says we need to do:
app.post('/login', passport.authenticate('local', {
successRedirect: '/accessed',
failureRedirect: '/access',
session: false
}));
In the login route.
Using the code above does solve the error message.Maybe this is my poor understanding of passport authentication but isn't the point of going through the login to store the user info in the session. If we set session to false how do we store the user info?
This is taken from the docs of passport.js.
Disable Sessions
After successful authentication, Passport will establish a persistent
login session. This is useful for the common scenario of users
accessing a web application via a browser. However, in some cases,
session support is not necessary. For example, API servers typically
require credentials to be supplied with each request. When this is the
case, session support can be safely disabled by setting the session
option to false.
So basically, the difference is that for clients such as browsers, you usually want to have session persistence. For cases when you're calling internal APIs and don't really need persistence, you disable sessions.
Try Below :
Please make sure you use newest version of passport (which is 0.2.1 for today).
Please try passing { session: false } as a second parameter of your req.logIn() function:
app.get('/login', function (req, res, next) {
passport.authenticate('local', function (err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login'); }
req.logIn(user, { session: false }, function (err) {
// Should not cause any errors
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
Open this below link for more :
PassportJS - Custom Callback and set Session to false

How can I report an invalid login properly with Express and PassportJS?

I've successfully implemented passport-local into my Express/Mongoose web-app but I'm having trouble figuring out how to render a failed login message properly.
Here's my login route:
app.get('/login', function(req, res) {
res.render('user/login', {
});
});
With a route like that how am I supposed to report an invalid login? If the login is successful it will write the id/username to the req.user object but that doesn't help me in the "GET /login" route because if it's successful you will get redirected to the page you want to go.
That means req.user will always be undefined when you GET the login page.
I want to be able to write out a message saying something like 'yo, invalid login!' when the following things happen:
The user does not exist.
The password supplied does not match but the user existed.
I might want to output a different message depending on what occurred.
When I implemented the LocalStrategy I used this code:
passport.use(new LocalStrategy({
usernameField: 'email'
},
function(email, password, fn) {
User.findOne({'login.email': email}, function(err, user) {
// Error was thrown.
if (err) {
return fn(err);
}
// User does not exist.
if (!user) {
return fn(null, false);
}
// Passwords do not match.
if (user.login.password != utility.encryptString(user.login.salt + password)) {
return fn(null, false);
}
// Everything is good.
return fn(null, user);
});
}
));
As you can see there are some problems but this is how the author of PassportJS set up his application. How are we supposed to access what the Strategy returns?
Like if it throws an error, what am I supposed to even call to get access to err?
Thanks.
In the latest version of Passport, I've added support for flash messages, which make it easy to do what you are asking.
You can now supply a third argument to done, which can include a failure message. For example:
if (user.login.password != utility.encryptString(user.login.salt + password)) {
return fn(null, false, { message: 'yo, invalid login!' });
}
Then, set failureFlash to true as an option to authenticate().
passport.authenticate('local', { successRedirect: '/',
failureRedirect: '/login',
failureFlash: true });
In this case, if authentication fails, the message will be set in the flash, ready for you to render it when you display the login page again.
Custom callbacks are also perfectly fine. The built in options just make it simpler to accomplish common tasks.
Also, I'm curious: you mention that there are problems with the sample. What do you think should be improved? I want to make the examples as good as possible. Thanks!
(For more details, see this comment on issue #12).
You can use the custom callback or middleware functionality to have more control. See the Authentication section of the guide for examples.
For example, a custom callback might look like:
app.get('/login', function(req,res,next) {
passport.authenticate('local', function(err,user) {
if(!user) res.send('Sorry, you\'re not logged in correctly.');
if(user) res.send('Skeet skeet!');
})(req,res,next);
});
Alternatively, you could always redirect both responses:
app.get('/login',
passport.authenticate('local', { successRedirect: '/winner',
failureRedirect:'/loser' }));
Or redirect the failure with simple middleware:
app.get('/login', ensureAuthenticated,
function(req,res) {
// successful auth
// do something for-the-win
}
);
// reusable middleware
function ensureAuthenticated(req,res,next) {
if(req.isAuthenticated()) {return next();}
res.redirect('/login/again'); // if failed...
}

Resources