Search code examples
node.jstypescriptexpressflashpassport.js

Express + PassportJS can't read flash messages


I have my Express + Passport + Firebase project where I handle authentication with a local stratetegy. Since I found that Passport would take care of the authentication process, so I also found that it would accept flash messages as third parameter for the done() function (in the strategy). But I am not sure how to read them:

I guess the flow I made to set and read flash messages were:

  1. Install connect-flash with NPM.

  2. Set the Express middleware after importing it:

import * as flash from 'connect-flash';
...
const app = express();
...
app.use(flash());
  1. Configure Passport Authentication in the Express route according to the documentation:
// POST - /api/v1/admin/oauth/login
router.post(
    '/login',
    async (req: Request, res: Response) => { /* middleware function to validate input */ },
    passport.authenticate('local', {
        failureRedirect: '/api/v1/admin/oauth/login',
        failureFlash: true
    }),
    async (req: Request, res: Response) => { /* function after success login */
);
  1. Include the flash messages in the done() method, according to Passport configuration documentation:
import { Strategy as LocalStrategy } from 'passport-local';
import db from '../../config/database';
import * as bcrypt from 'bcryptjs';

export default new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
    const ref = db.collection('users').doc(email);
    try {
        const doc = await ref.get();
        if (!doc.exists) {
            return done(null, false, { error: 'Wrong email' });
        }

        const user = doc.data();

        const match: boolean = await bcrypt.compare(password, user.password);
        if (!match) {
            return done(null, false, { error: 'Wrong password' });
        }

        user.id = doc.id;
        delete user.password;

        return done(null, user);

    } catch(error) {
        return done(error);
    }
});
  1. Read the flash messages using req.flash('error'):
// GET - /api/v1/admin/oauth/login
router.get('/login', (req: any, res: Response) => {
    const result: IResult = {
        message: '',
        data: null,
        ok: false
    };
    if (req.flash('error')) {
        resultado.message = req.flash('error');
        console.log(req.flash('error'));
    }
    return res.status(400).json(result);
});

I thought it was theroically working in my mind, until step 5, where req.flash('error') has an empty array in it. What I am doing wrong?


Solution

  • I keep searching and I found a solution but it works in the second login attempt.

    Steps from my question I modified to make it work:

    1. Install connect-flash with NPM.

    2. Set the Express middleware after importing it:

    import * as flash from 'connect-flash';
    ...
    const app = express();
    ...
    app.use(flash());
    
    1. Configure Passport Authentication in the Express route according to the documentation:
    // POST - /api/v1/admin/oauth/login
    router.post(
        '/login',
        async (req: Request, res: Response) => { /* middleware function to validate input */ },
        passport.authenticate('local', { 
            failureFlash: true,
            failureRedirect: '/api/v1/admin/oauth/login' 
        }),
        async (req: Request, res: Response) => { /* function after success login */
    );
    
    1. Create another route so it can display the flash message, thanks to @Codebling:
    // GET - /api/v1/admin/oauth/login
    router.get('/login', (req: Request, res: Response) => {
        const result: IResult = {
            message: 'Auth ok',
            data: null,
            ok: true
        };
    
        let status: number = 200;
        const flashMessage: any = req.flash('error');
    
        if (flashMessage.length) {
            resultado.message = flashMessage[0];
            resultado.ok = false;
            status = 400; 
        }
    
        return res.status(status).json(result);
    });
    
    1. Include the flash messages in the done() method, according to Passport configuration documentation:
    import { Strategy as LocalStrategy } from 'passport-local';
    import db from '../../config/database';
    import * as bcrypt from 'bcryptjs';
    
    export default new LocalStrategy({ usernameField: 'email' }, async (email, password, done) => {
        const ref = db.collection('users').doc(email);
        try {
            const doc = await ref.get();
            if (!doc.exists) {
                return done(null, false, { message: 'Wrong email' });
            }
    
            const user = doc.data();
    
            const match: boolean = await bcrypt.compare(password, user.password);
            if (!match) {
                return done(null, false, { message: 'Wrong password' });
            }
    
            user.id = doc.id;
            delete user.password;
    
            return done(null, user);
    
        } catch(error) {
            return done(error);
        }
    });