Welcome to new things

[Technical] [Electronic work] [Gadget] [Game] memo writing

Passport.js Usage Notes

I had a chance to use Passport.js, so here is my personal memo on how to use Passport.js in case I need it again.

What is Passport.js?

  • Passport.js is a library that allows users to log in to websites created with Node.js+Express, and to add user authentication.
  • There are various types of user authentication, including not only "user ID + password authentication" where you manage your own users, but also SNS authentication such as Google, Twitter, etc.
  • Various authentication implementations are library-based, allowing you to introduce user authentication without being aware of the authentication mechanism.

introduction

  • The library is divided into a main part and an authentication part called Strategy
  • Each authentication type has its own Strategy, and the Strategy for the type of authentication you want to use is installed and used on a case-by-case basis.

Example: Google Authentication

install

npm install --save passport
npm install --save passport-google-oauth20

Library loading

import * as passport from 'passport';
import * as GoogleStrategy from 'passport-google-oauth20';

logged in state

Passport.js is typically used in conjunction with sessions.

When used in conjunction with a session, once authentication is completed, the login state is saved as a session and On the second and subsequent accesses, the user data is restored from the session ID and the login state is maintained.

Flow from access to user data acquisition from the second time onward

  • passport with the Express middleware. Then, if a user is logged in, user data will be stored in "req.user
  • Check if "req.user" exists, determine if the user is logged in, and if not, write a process to redirect to the login page, etc.
  • If it is too much trouble to check whether a user is logged in each time, it can be done by middleware.
// Check login status directly from "req.user
app.get('/',
    (req: any, res) => {

        if (!req.user) {
            return res.redirect('/login');
        }

        res.render('home.html', { user: req.user });
    }
);

// Middleware to check login status
const checkLogin = (req, res, next) => {
    if (!req.user) {
        return res.redirect('/login');
    }

    return next();
};

// Leave login status checking to middleware
app.get('/userinfo',
    checkLogin,
    (req: any, res) => {
        res.render('userinfo.html', { user: req.user });
    }
);

Tie passport to Strategy

  • Linking of passport and strategy is done by passport.use(new Strategy(~))
  • The second argument to new Stragety() defines the function to be called after successful authentication

    • If the logged-in user is a valid user, call callback(null, <user data for that user>)
    • <User data for that user is the value stored in req.user
    • If the logged-in user is not a valid user, call callback(null, false)
    • If an error occurs while verifying that the logged-in user is a valid user, call callback(err)
  • The specific usage of new strategy() varies depending on the authentication method (type of Strategy), so please refer to the documentation of each Strategy for details.
//////////////////////////////
// Tie passport to Strategy
passport.use(new LocalStrategy(
    (username, password, cb: any) => {
        try {
            const user = DB_USER.find((v) => {
                return v.username === username;
            });

            // Not a reasonable login.
            if (!user) {
                return cb(null, false);
            }

            // Not a reasonable login.
            if (user.password !== password) {
                return cb(null, false);
            }

            // Reasonable login
            return cb(null, user);

        } catch (err) {
            // Error occurred
            return cb(err);
        }
    }
));

passport and session ties

Basic Policy

  • Link session IDs to unique user identifiers and store them as session data.
  • Define passport.serializerUser() as a method to retrieve unique user identifiers from user data and link them to session IDs.
  • When a site is accessed, user data is set in "req.user" in the following sequence

    • 1: Refer to session data from session ID to retrieve unique user identifier
    • 2 : Extract user data from unique user identifier
    • 3 : Set user data to "req.user
  • The method of retrieving user data from a user identifier is defined in passport.deserializeUser().
//////////////////////////////
// passport and session ties
// Extract unique user identifier from user data
passport.serializeUser( (user, cb) => {
    cb(null, user.username);
});

// Extracting user data from unique user identifiers
passport.deserializeUser( (username, cb) => {
    const user = DB_USER.find((v) => {
        return v.username === username;
    });

    if (!user) {
        return cb(`ERROR : NO USERNAME -> ${username}`);
    }

    return cb(null, user);
});

Certification

  • Prepare the pages used for authentication in a subdirectory.
  • The first argument of passport.authenticate() specifies which Strategy is used.
  • Specify the redirect destination in case of authentication failure using "failureRedirect", the second argument of passport.authenticate().
  • Page settings vary depending on the authentication method (Strategy type), so refer to the documentation of each Strategy for details.

Example: User ID and password authentication

// Certification
app.post('/login',
    passport.authenticate('local', {
        failureRedirect: '/login', // Where to jump to if authentication fails
        failureFlash: true
    }),
    (req, res) => {
        // Processing of successful authentication
        res.redirect('/');
    }
);

Log in/log out

  • Provide the URL of the login page in a subdirectory.
  • The login page settings vary depending on the authentication method (type of Strategy), so refer to the documentation of each Strategy for details.
  • Logout is performed by req.logout()." req.logout() deletes the session data, so subsequent accesses are logged out.
// logout
app.get('/logout',
    (req: any, res) => {
        req.logout();    // Session Deletion
        res.redirect('/login');
    }
);

sample

passport-local: username/password authentication

server.ts

import * as path from 'path';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as session from 'express-session';

//////////////////////////////
// Passport.js
import * as passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';

//////////////////////////////
// sample user DB
const DB_USER = [
    { username: "test01", password: "pass", email: "test01@foo.bar.com" },
    { username: "test02", password: "pass", email: "test02@foo.bar.com" },
    { username: "test03", password: "pass", email: "test03@foo.bar.com" },
];

//////////////////////////////
// Tie passport to Strategy
passport.use(new LocalStrategy(
    (username, password, cb: any) => {
        try {
            const user = DB_USER.find((v) => {
                return v.username === username;
            });

            // Not a reasonable login.
            if (!user) {
                return cb(null, false);
            }

            // Not a reasonable login.
            if (user.password !== password) {
                return cb(null, false);
            }

            // Reasonable login
            return cb(null, user);

        } catch (err) {
            // Error occurred
            return cb(err);
        }
    }
));

//////////////////////////////
// passport and session ties
// Extract unique user identifier from user data
passport.serializeUser( (user, cb) => {
    cb(null, user.username);
});

// Extracting user data from unique user identifiers
passport.deserializeUser( (username, cb) => {
    const user = DB_USER.find((v) => {
        return v.username === username;
    });

    if (!user) {
        return cb(`ERROR : NO USERNAME -> ${username}`);
    }

    return cb(null, user);
});

//////////////////////////////
// Express
const app = express();
const PORT_NO = 3000;

// etc
app.use(bodyParser.urlencoded({ extended: true }));
app.set('views', path.dirname(__dirname) + '/view');
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);

// session
// Make Passport.js use sessions
app.use(session({
    secret: 'keyboard_cat',
    resave: false,
    saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());

//////////////////////////////
// route

// Login Page
app.get('/login',
    (req, res) => {
        res.render('login.html');
    }
);

// Certification
app.post('/login',
    passport.authenticate('local', {
        failureRedirect: '/login', // Where to jump to if authentication fails
        failureFlash: true
    }),
    (req, res) => {
        // Processing of successful authentication
        res.redirect('/');
    }
);

// Check login status directly from "req.user
app.get('/',
    (req: any, res) => {

        if (!req.user) {
            return res.redirect('/login');
        }

        res.render('home.html', { user: req.user });
    }
);

// Middleware to check login status
const checkLogin = (req, res, next) => {
    if (!req.user) {
        return res.redirect('/login');
    }

    return next();
};

// Leave login status checking to middleware
app.get('/userinfo',
    checkLogin,
    (req: any, res) => {
        res.render('userinfo.html', { user: req.user });
    }
);

// logout
app.get('/logout',
    (req: any, res) => {
        req.logout();    // Session Deletion
        res.redirect('/login');
    }
);

app.listen(PORT_NO);

home.html

<%- include('head.html'); %>
<body>
    <h3>username</h3>
    <div><%= user.username %></div>
    <h3>email</h3>
    <div><%= user.email %></div>
    <div><a href="/userinfo">userinfo</a></div>
    <div><a href="/logout">logout</a></div>
</body>
</html>

login.html

<%- include('head.html'); %>
<body>
    <form action="/login" method="post">
        <h3>username</h3>
        <div><input type="text" name="username" /></div>
        <h3>password</h3>
        <div><input type="password" name="password" /></div>
        <div>
        </div>
    </form>
</body>
</html>

Impressions, etc.

The implementation around the session is time-consuming. However, it is helpful because it does the complicated authentication area.

There are various data conversions that make it difficult, but to summarize, data is converted in the following flow.

  • Session ID">"User Identifier->"req.user (user data)

What user data is registered in "req.user" is up to the implementation.

Understanding sessions is essential to using Passport.js, but I didn't really understand that in the first place, so I also summarized sessions.

www.ekwbtblog.com

Since I am currently building a site with a single page application, I did not need to create a website that authenticates users like this, but I was thinking of using Passport.js to get a token for a social networking service, so I started by summarizing the general usage of Passport.js I first summarized the general usage of Passport.js.

Reference Articles

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com

www.ekwbtblog.com