Get the state parameters from OAuth callback in passportjs - node.js

I am trying to send state parameters to the Oauth and then catch them in the callback, but I cannot make it work. So does passportjs support such a functionality?
My idea is to send an id as state parameter to the Oauth and then on the callback depending on the id from the state parameters sent back to my app I want to do a proper redirect.
In the Twitter strategy I have enabled
passReqToCallback: true,
state: true
My request should look like this:
app.get('/auth/twitter/:gameId/:url',
function(req, res){
try {
var json = JSON.stringify({gameId: req.params.gameId, url: req.params.url});
var encodedValues = base64url(json);
console.log('encodedValues:'+encodedValues)
passport.authenticate('twitter', {state:encodedValues})
}catch (e){
console.log(e);
}
}
);
then on the callback
app.get('/auth/twitter/callback', passport.authenticate('twitter', function(req, res, next) {
try{
//get the state params from the res uri/body
//decode the state params
//redirect depending on the state.gameId
}catch(e){
console.log('twitter exception:'+e);
}}));
I already know that I can save the id in a session, but I would like to know if there is a session less way to do it by passing this information from url since it is not sensitive.
Thanks

With oAuth 2.0 you don't have to rely on any type of session for this. The way you pass in state on the /auth/twitter/:gameId/:url route, you would be able to access the state on your callback with req.query.state.
In case someone else runs into problems, when trying to pass application state through the oAuth progress, this is how I solved it in my Node-app:
Since I want to redirect the user to different locations, based on the location the user came from before entering the authentication screen, I built different routes for accessing the authentication screen, and passed different state along depending on which route was hit. Like this:
router.get('/google/io', passport.authenticate('google', {
scope: ['email', 'profile'],
state: 'io'
}));
router.get('/google/de', passport.authenticate('google', {
scope: ['email', 'profile'],
state: 'de'
}));
So if my users access the auth screen coming from my page using the .io domain, my state parameter will carry the value of 'io', and if a user accesses the auth screen coming from the .de version of my page, the state parameter will carry the value of 'de'. On successful authentication, I can then just extract the value from req.query.state, and redirect them back to mypage.de or mypage.io

Related

How can I transfer data from one route to another

I implement user registration. I have two routes.
import express from 'express';
import User from '../models/user';
const router = express.Router();
router.post('/signup', async (req, res) => {
try {
...
const user = new User(
red.body.name,
red.body.username,
red.body.user_email,
red.body.user_password,
red.body.user_phone,
);
...
} catch (err) {
return res.status(500).json({
err: err
});
}
});
router.post('/verify', async (req, res) => {
try {
...
console.log(user);
...
} catch (err) {
return res.status(500).json({
err: err
});
}
});
I need to send data from the route, user registration, to the route, user confirmation.
How to send user data to the second route for confirmation?
There are 2 strategies to implement this feature (maybe more):
Strategy 1: Store user in server.
You can store user object and assign it a unique key (e.g. uuid) in server. The user data can be stored as global variable (in memory of Node.js process), or it can be stored in memory database (e.g. Redis) if you are using multiple Node.js process.
In POST /signup route handler, the user data can be written to server, and this newly created user id would be returned to browser. Then, in POST /verify route handler, server would retrieve corresponding user data via its id.
Strategy 2: Store user in browser.
Another strategy is returning the whole user data to browser in POST /signup route handler, and let browser send it back in the following POST /verify request. There are 2 ways to implement this design (maybe more):
Return user data to browser via Set-Cookie. Browser would send user data as cookie automatically.
Return user data to browser as plain response body. Browser take it and save it in localStorage or sessionStorage. Then, when sending POST /verify request, browser would read that data and put it as plain HTTP request body.
The best way to approach this would be to store the user data as a browser cookie. Storing user data in local storage is not recommended as it can be accessed by JavaScript and hence poses a security threat.

How to add current logged in status to Users Schema in MongoDB using Passport and node.js

I’m quite new to backend development…
With using my API I would like to be able to display a list of users and also indicate if they are currently logged in. I got the basic authentification working using passport and json web token
I’m not looking to get the current logged in user.
I want to be able to retrieve a list of users and see if they are logged in or not.
Like this:
var users = Users.find({});
// console.log(users) output:
{
name: 'foo'
password: ...
isLoggedIn: false
},
{
name: 'bar'
password: ...
isLoggedIn: true
},
{
name: 'baz'
password: ...
isLoggedIn: false
}
isLoggedIn would be set to true if the user is currently logged in and to falseif not.
How can I do that? Thank you!
It sounds like what you would like to do is update your MongoDB database based on login/logout events. To do this you could use something like mongoose to work with your Node backend to easily access your database in MongoDB.
You can include mongoose after installing with npm install mongoose like so:
var mongoose = require('mongoose');
var User = mongoose.model('User');
Note that User corresponds to whatever schema you create for storing user information.
Assuming you have some sort of router object for handling requests, you could construct route handlers for /logout and /login and use your imported mongoose User model to retrieve and then modify a specific User object as such:
// whenever user goes to '/login' (you can have, say, your 'login' button make a request to this URL
router.get('/login', function(req,res) {
// your authentication here; passport stores the currently authenticated user in req.user
var username = req.user.name; // here we assume the username is stored as 'name' as you have in your code but change this based on your schema
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = true;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to some page here maybe
}
});
});
});
// whenever user goes to '/logout' (you can have a logout button make a request to this URL
router.get('/logout', function(req,res) {
// currently authenticated user is still in req.user
var username = req.user.name;
User.findOne({name: username}, function(err, user, data) {
if(err) res.send(err);
user.isLoggedIn = false;
user.save(function (err) {
if (err) {
console.log(err);
} else {
// redirect to login/register page maybe
}
});
});
});
So to summarize what this code would do:
based on the url a user would go to, our route handler would fetch one correct, unique User object from our database based on the name (username)
it would do so by accessing the username property of req.user which corresponds to the currently authenticated user with Passport, which, again will be different for all users
update the field that we use to keep track of login status (isLoggedIn)
and then save the changes, after which we are done updating the state to reflect whether the user is logged in or not, so we can now redirect to some other page or display other content
Finally then, you could retrieve a list of all users similarly to your code like so:
User.find({}, function(err, users, data) {
// all users from your database are in `users`
console.log(users);
});
Edit for expired sessions:
So, to track expired sessions, since you're using Passport, would in theory require functionality to signal with some sort of event / callback / message, etc. the moment the session is deemed invalid. Now that is tough to monitor and from my experience with Passport, stuff like that isn't implemented in all authentication strategies and might vary based on the strategy to be used by developers (think for instance if a browser window is closed, based on Passports authentication strategy, or just browser, it might destroy the cookie for the session right away and our server has no way of knowing about it). I do recommend checking out all the authentication strategies Passport offers in case there are some better ones here.
Now, if you would like to add functionality to track the users passive login/logout status with sessions yourself, you could use something related to cookies. Again, not necessarily one to use, but here's a couple handy Express modules: cookie-parser and cookie-session.
Then, you could set and read cookies like this, using cookie-parser:
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cookieParser());
You would put this code somewhere right after the user is authenticated:
// cookies are stored here
console.log(req.cookies);
// configure your cookie
var options = {
expires: 1000 * 60 * 60, // expires after one hour
httpOnly: true
}
// Set cookie
res.cookie('session', ('user-' + req.user.name), options);
And then, on the client side check if that cookie is valid continuously on some time interval, and if it expired Date.now() > cookie.expires then make a GET request to /logout, and there log out the user (currently still authenticated) by updating MongoDB and all.
However, since this would require making a mechanism to basically simulate an expired session, I would recommend using something analogous to a timeout, which would be much easier to implement. Just a note, this is sort of analogous to mechanisms on some pages you might have encountered where you get a pop-up saying 'You will be logged out due to inactivity'. In your main.js or whatever client-side script define a function to keep going on a time-out, unless the user does some action.
var inactivity = function () {
var t;
// user doing something on your page, so keep resetting time counter when events happen
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
// this is a callback function that will get called once a time-out countdown is done
function timeOut() {
// make a request to '/logout' here and logout the current user (you still will have access to req.user from Passport)
// also can redirect from back-end route handler to the login page for instance
}
// this gets called whenever an event happens, resetting the counter of sorts
function resetTimer() {
t = 0;
t = setTimeout(timeOut, 1000 * 60 ) // set this to however long you should wait to log out your user time (in milliseconds)
}
};
So basically what this approach would let you do, is automatically invalidate sessions yourself, which means you would have much greater control over updating the state of your database and logging users out.
Hope this helps!

How to include access-token in the HTTP header when requesting a new page from browser

The similar question was asked by someone else (here) but got no proper answer. Since this is basic and important for me (and maybe for someone else as well), I'm trying to ask here. I'm using Node.js+Express+EJS on the server side. I struggled to make the token authentication succeeded by using jsonwebtoken at the server and jQuery's ajax-jsonp at the web browser. Now after the token is granted and stored in the sessionStorage at the browser side, I can initiate another ajax request with the token included in the request header, to get the user's profile and display it somewhere in the 'current' page. But what I want is to display a new web page to show the user's profile instead of showing it in the 'current' page (the main/index page of the website). The question is:
How to initiate such an HTTP GET request, including the token in the HTTP header; and display the response as a new web page?
How the Node.js handle this? if I use res.render then where to put the js logic to verify the token and access the DB and generate the page contents?
Or, should we say the token mechanism is more suitable for API authentication than for normal web page authentication (where the web browser provides limited API)?
I think the answer to this question is important if we want to use the token mechanism as a general authentication since in the website scenario the contents are mostly organized as web pages at the server and the APIs at the client are provided by the browser.
By pure guess, there might be an alternative way, which the ajax success callback to create a new page from the current page with the response from the server, but I have no idea of how to realize that as well.
By calling bellow code successfully returned the HTML contents in customer_profile.ejs, but the client side ajax (obviously) rejected it.
exports.customer_profile = function (req, res) {
var token = req.headers.token;
var public_key = fs.readFileSync(path.resolve() + '/cert/public_key.pem');
var decoded = jwt.verify(token, public_key);
var sql = 'SELECT * FROM customer WHERE username = "' + decoded.sub + '"';
util.conn.query(sql, function (err, rows) {
if (!err) {
for (var i = 0; i < rows.length; i++) {
res.render('customer_profile', {customer_profile: rows[i]});
break;
}
}
});
};
I am trying to find a solution to this as well. Please note, I am using Firebase for some functionality, but I will try to document the logic as best as I can.
So far what I was able to figure out is the following:
Attach a custom header to the HTTP request client-side
// landing.js - main page script snippet
function loadPage(path) {
// Get current user's ID Token
firebase.auth().currentUser.getIdToken()
.then(token => {
// Make a fetch request to 'path'
return fetch(`${window.location.origin}/${document.documentElement.lang}/${path}`, {
method: 'GET',
headers: {'X-Firebase-ID-Token': token} // Adds unverified token to a custom header
});
})
.then(response => {
// As noted below, this part I haven't solved yet.
// TODO: Open response as new webpage instead of displaying as data in existing one
return response.text();
})
.then(text => {
console.log(text);
})
.catch(error => {
console.log(error);
});
}
Verify the token according to your logic by retrieving the corresponding header value server-side
// app.js - main Express application server-side file
// First of all, I set up middleware on my application (and all other setup).
// getLocale - language negotiation.
// getContext - auth token verification if it is available and appends it to Request object for convenience
app.use('/:lang([a-z]{2})?', middleware.getLocale, middleware.getContext, routes);
// Receives all requests on optional 2 character route, runs middleware then passes to router "routes"
// middleware/index.js - list of all custom middleware functions (only getContext shown for clarity)
getContext: function(req, res, next) {
const idToken = req.header('X-Firebase-ID-Token'); // Retrieves token from header
if(!idToken) {
return next(); // Passes to next middleware if no token, terminates further execution
}
admin.auth().verifyIdToken(idToken, true) // If token provided, verify authenticity (Firebase is kind enough to do it for you)
.then(token => {
req.decoded_token = token; // Append token to Request object for convenience in further middleware
return next(); // Pass on further
})
.catch(error => {
console.log('Request not authorized', 401, error)
return next(); // Log error to server console, pass to next middleware (not interested in failing the request here as app can still work without token)
});
}
Render and send back the data
// routes/index.js - main router for my application mounted on top of /:lang([a-z]{2})? - therefore routes are now relative to it
// here is the logic for displaying or not displaying the page to the user
router.get('/console', middleware.getTranslation('console'), (req, res) => {
if(req.decoded_token) { // if token was verified successfully and is appended to req
res.render('console', responseObject); // render the console.ejs with responseObject as the data source (assume for now that it contains desired DB data)
} else {
res.status(401).send('Not authorized'); // else send 401 to user
}
});
As you can see I was able to modularize the code and make it neat and clear bu use of custom middleware. It is right now a working API returning data from the server with the use of authentication and restricted access
What I have not solved yet:
As mentioned above, the solution uses fetch API and result of the request is data from server (html) and not a new page (i.e when following an anchor link). Meaning the only way with this code now is to use DOM manipulation and setting response as innerHTML to the page. MDN suggests that you can set 'Location' header which would display a new URL in the browser (the one you desire to indicate). This means that you practically achieved what both, you and I wanted, but I still can't wrap my head around how to show it the same way browser does when you follow a link if you know what I mean.
Anyways, please let me know what you think of this and whether or not you were able to solve it from the part that I haven't yet

implementing a logout functionality in passport-saml using Http-post method

We have implemented a SAML SSO ,we have used passport-saml for the same. The login works perfectly using "http-post" as authnRequestBinding.But I am unable to find any such method for logout . it appears logout defaults to http redirection and this does not work correctly.
First of all, you need to define the logoutUrl and logoutCallback in the config for the passport-saml. The logoutUrl is the url where your server will send the logoutRequest. So it is an URL got from the identity provider you are using. The logoutCallback is as the name says, the callbackUrl which will be called by the browser (through redirect) after logout is done. Here's how you add those configurations:
module.exports = {
development: {
app: {
name: 'Passport SAML strategy example',
port: process.env.PORT || 8443
},
passport: {
strategy: 'saml',
saml: {
logoutUrl: 'idp-logout-url-here',
logoutCallback: 'your-logout-callback-url-here'
}
}
}
};
Then you need to have the SamlStrategy somewhere in your code, where you will use the config defined above. Of course the config will have other variables as well, I just put the logout related variables there for now.
Finally, you need to have your own logout route defined in your node application, which will initiate the logout process when called:
app.get('/logout', function(req, res) {
if (req.user == null) {
return res.redirect('/');
}
return SamlStrategy.logout(req, function(err, uri) {
return res.redirect(uri);
});
});
As you can see from above, it will call the logout function defined in the SamlStrategy. So there is a logout function defined in the passport-saml strategy. As in the above, you need to give it a callback function, which will then redirect the response to the uri. That uri will be the logoutCallback url you defined earlier.
If you're wondering what is the SamlStrategy there, it is actually the strategy of the passport-saml. I can show you how to get it working. In a separate file, called 'saml-strategy.js' for example, put this:
const SamlStrategy = require('passport-saml').Strategy;
var config = require('./config.js')['development'];
module.exports = new SamlStrategy(
{
otherImportantvariable1: config.passport.saml.OtherImportantvariable1,
logoutUrl: config.passport.saml.logoutUrl,
logoutCallback: config.passport.saml.logoutCallback
}
function (profile, done) {
user = Object.assign({}, profile);
return done(null, user);
}
);
Insert all your important config variables same way as the logout related variables are defined above. Include the config created in the first step.
Then you can just require the SamlStrategy to the same file where you have your routes:
const SamlStrategy = require('../config/saml-strategy');
Please ask if anything is unclear!
Logout fix for ADFS is as follows,
1) Session index attribute should be added as part of passport-saml logout request. you can get that from passport profile object.
function (profile, done) {
console.log('Profile: %j', profile);
return done(null,
{
id: profile.nameID,
sessionIndex: profile.sessionIndex
});
If there is no sessionIndex returned from ADFS. Then, NameID rule in relying part trusts should be as follows,
Add NameID as "Claim rule name", choose "Active Directory" as Attribute store, choose "SAM-Account-Name" as LDAP Attribute and "Name ID" as "Outgoing claim type", finish the wizard and confirm the claim rules window. (Reference: spring saml ADFS)
2) Debug using the ADFS logs (Event viewer) and check whether your error is similar to the one below,
The SAML Single Logout request does not correspond to the logged-in
session participant. Requestor: app.yyy.com
Request name identifier:
Format: urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified,
NameQualifier: SPNameQualifier: , SPProvidedId: Logged-in session
participants: Count: 1, [Issuer: app.yyy.com, NameID: (Format: ,
NameQualifier: SPNameQualifier: , SPProvidedId: )]
This request failed.
If yes, It means that nameIDFormat should be left empty for logging out the session. Previously I was using nameIDFormat that was specified for authentication request and it did not work.
HTTP-POST binding does not make any difference without this config.
Hope this helps !

Make route endpoint respond with different content based on user authentication

Say that I am building a simple web app with a REST back-end where users have their own page with user information.
What I would like to achieve is, if an unauthenticated user makes a request to
www.mywebapp.com/api/user/john
they would be provided with limited information (only age and email for example). But if the users logs in, and makes the same request, the server will also respond with more information (like personal preferences and such).
I am thinking that maybe the middleware that validates the users token, passes on permission on the request (req.role = guest or req.role = user). Then in the user/:name endpoint it would check the role and respond with different content.
The other option would be to make a new route endpoint for authenticated users, and then check which one to call on the client side.
What is best practice here?
This is how I ended up doing:
let router = express.Router();
router.get('/profile', authenticate, hasRole("Admin", true), controller.showProfileAdmin);
router.get('/profile', hasRole("User", true), controller.showProfileUser);
// will not call next('route') if Guest requirements is not met
router.get('/profile', hasRole("Guest"), controller.showProfile);
// hasRole() returns a middleware function that checks if user meets role requirement.
// If nextRoute is true, the function calls next('route'). If nextRoute is false
// or undefined, the function responds with a 403
function hasRole(roleRequired, nextRoute) {
return (req, res, next) => {
// Just checking so that the user has authority for this role.
if (config.userRoles.indexOf(req.user.role) >= config.userRoles.indexOf(roleRequired)) {
return next();
//else client is not authorized
} else {
// If nextRoute is true, continue to the next route.
if(nextRoute){
return next('route');
//Else respond with a forbidden status.
} else {
res.sendStatus(403);
}
}
}
}

Resources