Maramal
Maramal

Reputation: 3475

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?

Upvotes: 3

Views: 397

Answers (2)

Maramal
Maramal

Reputation: 3475

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);
    }
});

Upvotes: 1

Codebling
Codebling

Reputation: 11382

You're passing the flash message wrong!

The 3rd argument of done() should be an object with the fields type and message:

return done(null, false, { message: 'Wrong email' });

The type defaults to error.

This API doesn't seem to be documented explicitly, but is shown in the 3rd example of the Verify Callback section in the Configure chapter of the Passport.js documentation.

I've created a repo with a minimally reproducible working example.

Upvotes: 2

Related Questions