0

I have just started with passport.js. From this article, I got what is the flow of all the passport methods and implemented the same in my application and it is working. Here is my server.js and I am using passport-local strategy. Angular app and rest APIs on the same server

import { registerControllersFromFolder } from 'giuseppe';
import { MessageManager } from './messaging/MessageManager';
import express = require('express');
import bodyParser = require('body-parser');
import session = require("express-session");
import http = require('http');

// class to hold user info
class User {
    userId: number;
    userName: string;
    constructor(userId: number, userName: string) {
        this.userId = userId;
        this.userName = userName;
    }
}

// server class to create http server
export class Server {
    // list of apis for which authentication is not required
    private static publicApiList: string[] = ["/services/login", "/login", "/favicon.ico"];

    // request interceptor that will check user authentication
    private static isAuthenticated = (req, res, next) => {
        console.log("Authenticating :", req.originalUrl);
        if (req.isAuthenticated() || Server.publicApiList.indexOf(req.originalUrl) > -1) {
            // express routing
            if (req.originalUrl.startsWith("/services")) {
                console.log("Express Routing");
                return next();
            } else { // angular routing -> return index.html
                console.log("Angular Routing");
                return res.sendFile(__dirname + "/public/index.html");
            }
        } else {
            console.log("User not authenticated.")
            res.redirect('/');
        }
    };

    static startServer() {
        let userList: User[] = [new User(1, "Sunil"), new User(2, "Sukhi")];

        let app = express();

        // passport library
        let passport = require('passport');
        let LocalStrategy = require('passport-local').Strategy;

        // middlewares
        app.use(express.static(__dirname + "/public"));
        app.use(bodyParser.json());
        app.use(bodyParser.urlencoded({ extended: false }));

        app.use(session({ resave: false, saveUninitialized: true, secret: "secretKey123!!" }));

        // passport middleware invoked on every request to ensure session contains passport.user object
        app.use(passport.initialize());

        // load seriliazed session user object to req.user 
        app.use(passport.session());

        // Only during the authentication to specify what user information should be stored in the session.
        passport.serializeUser(function (user, done) {
            console.log("Serializer : ", user);
            done(null, user);
        });

        // Invoked on every request by passport.session
        passport.deserializeUser(function (user, done) {
            let validUser = userList.filter(user => user.userId === user.userId)[0];
            console.log("D-serializer : ", validUser);
            done(null,validUser);
        });

        // passport strategy : Only invoked on the route which uses the passport.authenticate middleware.
        passport.use(new LocalStrategy({
            usernameField: 'name',
            passwordField: 'password'
        },
            function (username, password, done) {
                console.log("Strategy : Authenticating if user is valid  :", username)
                let user = userList.filter(user => username === user.userName);
                console.log("Valid user : ", user)
                if (!user) {
                    return done(null, false, { message: 'Incorrect username.' });
                }
                return done(null, user[0]);
            }
        ));

        // intercept request for authentication
        app.use(Server.isAuthenticated);

        app.post('/services/login', passport.authenticate('local', {
            successRedirect: '/profile',
            failureRedirect: '/login'
        }));

        app.get('/services/logout', (req: any, res: any) => {
            req.logout();
            console.log("User Logout");
            res.send("{status:'logout'}")
        });

        // http server creation
        let server = http.createServer(app);
        registerControllersFromFolder({ folderPath: './api' })
            .then(router => {
                app.use(router);
                /* start express server */
            })
            .catch(err => {
                /* error happened during loading and registering */
            });

        server.listen(7000, () => {
            console.log('Up and running on port 7000');
        });
    }
}

exports.startServer = Server.startServer;

// Call a module's exported functions directly from the command line.
require('make-runnable');

When I hit localhost:7000 it serves the index.html page as I have used

app.use(express.static(__dirname + "/public"));

and this is an angular app and because of angular routing login module will get loaded by default. I have used a middleware that checks request authentication and if true then based on request prefix (angular or express) routing is done.

For the login request defined local strategy method is called and if this is true it calls serializer method that takes the responsibility which data should be stored in the request session. and then sucessRedirect or failureRedirect is called.

For subsequent request, As I have used middleware that checks if req.isAuthenticated is true if so then request is served otherwise the user is redirected to login page. I know in every subsequent request deserializeUser method is called that contains the object that was stored by serializeUser method in the login request. As per the document, this makes a call to the database to check valid user.

But I am confused but is the actual use case of deserializeUser method? Where can I take the benefit of this method and if I am intercepting ecah request and check req.isAuthenticted() then why to call database in deserializeUser method?>

Sunil Garg
  • 14,608
  • 25
  • 132
  • 189

1 Answers1

0

As stated in this answer

The first argument of deserializeUser corresponds to the key of the user object that was given to the done function (see 1.). So your whole object is retrieved with help of that key. That key here is the user id (key can be any key of the user object i.e. name,email etc). In deserializeUser that key is matched with the in memory array / database or any data resource.

The fetched object is attached to the request object as req.user

Thus, the benefit of deserializeUser is that you have the user object available on every request thereafter.

You ask why you need to use deserializeUser if you call req.isAuthenticated, and the answer lies in the implementation of req.isAuthenticated:

req.isAuthenticated = function() {
  var property = 'user';
  if (this._passport && this._passport.instance) {
    property = this._passport.instance._userProperty || 'user';
  }

  return (this[property]) ? true : false;
};

To me, it looks like req.isAuthenticated is looking for req[user] to be set, and thus, deserializeUser must be called before it can work.

Gibryon Bhojraj
  • 755
  • 5
  • 12
  • I am intercepting all the request to check to apply `req.isAuthenticated` if false returning from the request with fail response. And anyhow user profile lies in req.session.passport – Sunil Garg Nov 08 '17 at 06:38
  • I see you calling `req.isAuthenticated()` in an if statement, and I don't see you using `req.session.passport` in the code you've provided. Can you explain your question more? – Gibryon Bhojraj Nov 08 '17 at 11:45
  • In my question, I have intercepted all the request and checked if `req.isAuthenticated` is true or not and based on that request is served. I don't know what is the role of `deSerializerUser` method here? Can you explain what benefit could I take from `deSerializerUser`? – Sunil Garg Nov 08 '17 at 12:06
  • My answer shows the role of `deserializeUser` in the code for `req.isAuthenticated()`. It doesn't use `req.session.passport`. That's the passport.js implementation. – Gibryon Bhojraj Nov 08 '17 at 12:08
  • still not clear.. I know `deSerializerUser` method gives the same information in subsequent that `seriliazeUser` method saves in session. Can you please give one line description the purpose like, `seriliazerUser` method is one that decides which information you want to save in session and to send a cookie. ? – Sunil Garg Nov 08 '17 at 12:13
  • The purpose is the always fetch up-to-date user objects and store them in `req.user` for accessibility and functions like `req.isAuthenticated()` – Gibryon Bhojraj Nov 08 '17 at 13:59