React-redux-universal-hot-example: Hookup to passport?

Created on 31 Aug 2015  路  43Comments  路  Source: erikras/react-redux-universal-hot-example

Awesome example. Thanks a lot for putting it together.

For authentication, do you have any examples of connecting this up to something like passport? (Do you even need to do that for simple cases?) How is authentication handled for each API request (i.e. tokens)?

Most helpful comment

I made it work but I don't think it's the optimal solution.

I solved the cors problem using a direct link instead of using the ApiClient:

// src/containers/Login/Login.js

<a href="/api/login/facebook/">Facebook AUTH</a>

The API setup is pretty much the same as the example:

// api/api.js

passport.use(new FacebookStrategy({
    clientID: 'xxx',
    clientSecret: 'xxx',
    callbackURL: 'http://localhost:3000/api/login/facebook/return'
  },
  function(accessToken, refreshToken, profile, cb) {
    // In this example, the user's Facebook profile is supplied as the user
    // record.  In a production-quality application, the Facebook profile should
    // be associated with a user record in the application's database, which
    // allows for account linking and authentication with other identity
    // providers.
    return cb(null, profile);
  }
));

passport.serializeUser(function(user, cb) {
  cb(null, user);
});

passport.deserializeUser(function(obj, cb) {
  cb(null, obj);
});

app.use(bodyParser.urlencoded({ extended: true }));
app.use(session({
  secret: 'react and redux rule!!!!',
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 60000 }
}));

app.use(passport.initialize());
app.use(passport.session());

app.get('/login/facebook',
  passport.authenticate('facebook')
);

app.get('/login/facebook/return',
  passport.authenticate('facebook', { failureRedirect: '/login' }),
  function(req, res) {
    res.redirect('/');
  }
);

And the last change was on loadAuth to access the passport user

// api/actions/loadAuth.js

export default function loadAuth(req) {
  return Promise.resolve(req.session.passport.user || null);
}

All 43 comments

In this example, the session lives on the API server, and the rendering server proxies all cookies through to the API server. Then it's just a matter of doing the normal Passport setup (express middleware or whatnot) on the api server. Make sense?

Sorry about the lag. Yep that males sense. Working on it atm. Thanks!

:+1: Let me know if you have any further questions.

Hi. I will start by saying you did a hell of a job on this project, thanks alot for contributing your hard work.

I have been trying also to integrate passport and have had quite a hard time. Its not clear to me how it is possible to add new routes considering the api is promise based and I dont see how to use the normal passport authentication middleware. Any help you could provide would be highly appreciated

example: where would the standard passport boilerplate code fit into the api server?

app.get('/oauth/facebook/callback', passport.authenticate('facebook', {
failureRedirect: '/login',
successRedirect: '/',
scope:['email']
}));

app.get('/oauth/google',
    passport.authenticate('google', { scope: 'profile' }),
    function(req, res){
        // The request will be redirected to Google for authentication, so this
        // function will not be called.
    });

Hi @cpttripzz, did you crack this one? If so do you have the code for how you did it?

@AndrewBestbier there is no way to get promises working with passport imo since the oauth requires a redirect which cannot be resolved as a promise. T
Fortunately it is very easy to strip out all api code and replace it with standard express boilerplate

this is my version of api.js:

require('../server.babel'); // babel registration (runtime transpilation for node)
var mongoose = require('./config/mongoose');
const pretty = new PrettyError();
var db = mongoose();
import express from 'express';
import session from 'express-session';
import bodyParser from 'body-parser';
import config from './config';
import PrettyError from 'pretty-error';
import passport from 'passport';
const app = express();
require('./config/passport')(passport);

app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());
require('./routes/auth')(app);
app.listen(config.apiPort);

thx for sharing. I also have a great interest in this topic in the next 4 weeks. Will share my experiences here then as well.

Thanks :) I'll also be looking into it @snackycracky. It would be great to have an react-redux-universal-hot-example with auth/mongodb set up.

@snackycracky any luck?

funny, because I start exactly today with that topic.

Maybe this helps?

// api/actions/login.js
import passport from 'passport'

export default function login( req, res, next ) {

    return new Promise( ( resolve, reject ) => {

        passport.authenticate( 'local-login', function authenticate( err, user, info ){

            if( err )    return reject( err )
            if( ! user ) return reject( info )

            req.logIn( user, err => {
                if( err ) reject( err )
                else resolve( user )
            })

        })( req, res, next )
    })
}

next and res can probably be omitted

@snackycracky @AndrewBestbier there are some examples here, but did either of you end up incorporating auth/mongoDB?

I did. What do you need?

I'm really just looking for some examples, as I'm just starting to work this into my project. Would love to see what you've done on client + server

Its quite straight forward actually:

  • Setup login api action as posted above
  • Add bodyparser and passport.initialize and passport.session middleware
  • Setup its session store like redis
  • Setup passport strategies as posted in all passport tutorials on the web

alright will do, thanks :+1:

Anyone have any examples of this working with oauth2 or a similar flow?

I got it working.. not sure if it's the optimal flow, but in case anyone is interested, I just used redirects (and otherwise followed the standard passport-twitter documentation).

So I have an endpoint called /api/auth/twitter that I redirect the user to, then I have my TwitterStrategy set-up so the user is redirected back to another endpoint /api/auth/twitter/callback, which redirects back to my app (filling in the session in the normal manner). The first redirect is redundant but for now it'll keep all my auth code on the server.

(based on request in discord channel)

  • After installing passport, I created a User model (mine is a Mongo model and I'm using mongoose).
  • I added these two methods to serialize / deserialize user
passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser((id, done) => {
  User.findById(id, (error, user) => {
    done(error, user);
  });
});
  • I replaced the login method with Twitter auth endpoints
router.get('/twitter', passport.authenticate('twitter'));
router.get('/twitter/callback',
  passport.authenticate('twitter', { successRedirect: '/',
                                     failureRedirect: '/' }));
exports.setup = (User) => {
  passport.use(new TwitterStrategy({
    consumerKey: '...',
    consumerSecret: ''...',
    callbackURL: 'http://localhost:3000/api/auth/twitter/callback'
  },
  (token, tokenSecret, profile, done) => {
    User.findOne({ 'twitter.id_str': profile.id }, (userFindErr, user) => {
      if (userFindErr) {
        return done(userFindErr);
      }
      if (user) {
        return done(null, user);
      }

      const newUser = new User();
      newUser.twitter = profile._json;
      newUser.name = profile._json.name;
      newUser.alias = profile._json.screen_name;
      newUser.image = profile._json.profile_image_url_https;
      newUser.twitter.tokenSecret = tokenSecret;
      newUser.save( (userSaveErr, savedUser) => {
        if (userSaveErr) {
          throw userSaveErr;
        }
        return done(null, savedUser);
      });
    });
  }));
};

Now the user object is available in req.user (passport is doing the serialization/deserialization as necessary). I'm still using the loadAuth method to access the user object from the client, but now I rewrite it to use the passport object:

export default function loadAuth(req) {
  return Promise.resolve(req.user || null);
}

Hope this helps

@oaosman84 Could you please help me in setting up Oauth2 flow.

I have setup my own Oauth 2 server using https://github.com/jaredhanson/oauth2orize and have also implemented the flow into this example. I am getting the passport session in callback function but I am not able to pass it to LoadAuth method to access the user object from the client. Could you please provide me some example as how you did it ?

P.S. I am using Express for Oauth2 implementation. My code is like this:

src/server.js

app.use(cookieParser());
app.use(session({secret: '1234567890QWERTY'}));
app.use(passport.initialize());
app.use(passport.session());
app.use('/login', require('../api/actions/login'));

api/actions/loadAuth.js

export default function loadAuth(req) {  
  return Promise.resolve(req.session.user || null);
}

api/actions/login/index.js

'use strict';

import {Router} from 'express';
import * as controller from './login.controller.js';
import passport from 'passport';
var router = new Router();
router.get('/', passport.authenticate('hotelxauth', { scope: ['email'] }));
router.get('/callback',  passport.authenticate('hotelxauth', { failureRedirect: '/' }));
router.get('/callback', controller.callback);
export default router;

api/actions/login/index.controller.js

'use strict';
var passport = require('passport')
  , User = require('./user')
  , HotelxStrategy = require('../../hotelx-passport/strategy').Strategy
  , config = require('../../config')
  , express = require('express');

passport.serializeUser(function(user, done) {
  //Users.create(user);
  done(null, user);
});

passport.deserializeUser(function(obj, done) {
  var user = obj // Users.read(obj)
    ;
  done(null, user);
});

passport.use(new HotelxStrategy({
    clientID: config.oauth2.client.client_id
    , clientSecret: config.oauth2.client.client_secret
    , callbackURL: config.oauth2.callbackUrl.scheme + "://" + config.oauth2.callbackUrl.host + config.oauth2.callbackUrl.path
  }
  , function (accessToken, refreshToken, profile, done) {
    User.findOrCreate({ profile: profile }, function (err, user) {
      user.accessToken = accessToken;
      return done(err, user);
    });
  }
));

export function callback(req, res) {  
  //showing session variables
  console.log(req.session.passport);
  var url = '/loginSuccess'  
  res.statusCode = 302;
  res.setHeader('Location', url);
  res.end('hello');  
}

Regards,

  • Manoj

hey @manoj150283 , I actually don't have time to look through the code in too much detail, but I think passport generally stores the user in req.user not req.session.user. That might be your problem.

@oaosman84 You are right. I was looking into req.session.user while it was stored inside req.user. Thank you for your help.

Has anyone implemented an oauth using strategy?

@alex-fernandez I did, what do you need?

How did you manage to set the user from the callback? I was able to login, my problem is after login, it will be redirected to the index, my question is since the user is authenticated, where can we set the user in redux.

Where did you implement this, Is it in the API?

Not sure if I understand correctly.. Here's how I implemented it

1) Setup routes (i got multiple services supporting oauth)

import passport from 'passport'
import { SERVICES } from '../shared/constants'

export default function( app ) {

    for ( const s of SERVICES ){

        app.get( '/auth/' + s, passport.authorize( s ) )

        app.get( `/auth/${ s }/callback`, passport.authorize( s ), ( req, res, next ) => {

            console.log( JSON.stringify( req.account, null, 2 ) )

            const u = req.user
            u.sources[ s ] = req.account
            u.save( err  => {
                if( err )
                    next( err )
                else
                    res.redirect( '/sources?synchronize=' + s )
            })
        })
    }
}

2) Link to route on client:

if( ! user.sources[ service ].id ) {
    action =  (
        <a
            href={ 'api/auth/' + service }
            className="btn btn-info"
        >
            Connect
        </a>
    )
}

Once the user arrives at the callback endpoint you can store tokens etc on his account.

Is this implemented in server.js?

First part is implemented on the API, where passport is hooked up.

What does '/sources?synchronize=s do in code?

@trueter I was able to make it work, thanks

Using @trueter implementation I'm getting a CORS error:

XMLHttpRequest cannot load https://www.facebook.com/dialog/oauth[...]. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.

Did anyone get this? I need some help please.

@rcalabro the server complaing belongs to facebook. Are you sticking to the documentation? You might want to post a bit more code.

Hi,

I`m using exactly your code:
On api.js

app.use(bodyParser.json());
app.use(passport.initialize());
app.use(passport.session());

// Use the FacebookStrategy within Passport.
passport.use(new FacebookStrategy({
clientID: 'xxx',
clientSecret: 'xxx',
callbackURL: 'http://localhost:3000/api/auth/facebook/callback'
},
function(accessToken, refreshToken, profile, done) {
console.log('done');
}
));

const SERVICES = [
'facebook'
];

for ( const s of SERVICES ){

app.get( '/auth/' + s, passport.authorize( s ) )

app.get( '/auth/${ s }/callback', passport.authorize( s ), ( req, res, next ) => {

    console.log( JSON.stringify( req.account, null, 2 ) )

    const u = req.user
    u.sources[ s ] = req.account
    u.save( err  => {
        if( err )
            next( err )
        else
            res.redirect( '/sources?synchronize=' + s )
    })
})

}

And my facebook settings:

Valid OAuth redirect URIs
http://localhost:3000/api/auth/facebook/callback

I tried with both
Valid OAuth redirect URIs
http://localhost:3000/api/auth/facebook/callback
http://localhost:3000/api/auth/facebook

If you're authenticating your users through facebook you need to use authenticate instead of authorize. It also looks like your facebook strategy configuration is quite minimal compared to the examples on the repo. I'd recommend reading up there.

Thanks @trueter,

Investigating with the examples you provided, I noticed that the request made form this boilerplate send a "origin" header:
:authority:www.facebook.com
:method:GET
:path:/dialog/oauth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Flogin%2Ffacebook%2Freturn&client_id=1674169192835494
:scheme:https
accept:_/_
accept-encoding:gzip, deflate, sdch
accept-language:en-US,en;q=0.8,pt-BR;q=0.6,pt;q=0.4
cache-control:no-cache
origin:http://localhost:3000
pragma:no-cache
referer:http://localhost:3000/login
user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36

And the example does not:
:authority:www.facebook.com
:method:GET
:path:/dialog/oauth?response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Flogin%2Ffacebook%2Freturn&client_id=1674169192835494
:scheme:https
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,_/_;q=0.8
accept-encoding:gzip, deflate, sdch
accept-language:en-US,en;q=0.8,pt-BR;q=0.6,pt;q=0.4
cache-control:no-cache
cookie:datr=yzjsVhXHE53oGDZjiqArB1ym; lu=gAHot1kx8qNni3TnkoBpkHig; p=-2; presence=EDvF3EtimeF1459302761EuserFA2604819073A2EstateFDt2F_5b_5dElm2FnullEuct2F1459302062BEtrFA2loadA2EtwF3845426500EatF1459302761600G459302761871CEchFDp_5f604819073F12CC; c_user=604819073; fr=0K2Fd81nKfqZ6ZEGJ.AWW_F3XCsXXxyndtV4SfdU2tSEQ.BW7Djj._2.AAA.0.AWWzTmeB; xs=245%3ASzPyvSyeNaIKRA%3A2%3A1458321651%3A5723; csm=2; s=Aa4kJWWuXeePJ7_U.BW-tnz; act=1459359000784%2F0
pragma:no-cache
referer:http://localhost:3000/login
upgrade-insecure-requests:1
user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.87 Safari/537.36

Now I have to figure where this header is set.

Thank you very much!

Origin headers are usually added automatically by HTTP request implementations. You might have to tell Facebook to allow authentication from localhost:3000.

I always used https://ngrok.com/ https://ngrok.com/ for oauth redirects

This example works perfectly from localhost, pointing to the same facebook app, and facebook responds correctly with the user profile.

It uses the same code I tried within the boilerplate

app.get('/login/facebook',
passport.authenticate('facebook'));

app.get('/login/facebook/return',
passport.authenticate('facebook', { failureRedirect: '/login' }),
function(req, res) {
res.redirect('/');
});

But no origin header is sent.

I tried to find where the origin header is added in the boilerplate but culdn't. I think the API is blocking cors request because '/login/facebook/return' is never reached.

Curiously if I paste the request URL directly in the browser address bar it works perfectly, both using the API directly http://localhost:3030/login/facebook or through the proxy http://localhost:3000/api/login/facebook.

The error occurs only when the request is made by clicking on the button

// containers/Login/Login.js

          <button className="btn btn-success" onClick={this.handleFacebookAuth}><i className="fa fa-facebook"/>{' '}Facebook Auth
          </button>
handleFacebookAuth = (event) => {
      event.preventDefault();
      this.props.facebookAuth();
    }

// redux/modules/auth.js

export function facebookAuth() {
  return {
    types: [FACEBOOKAUTH, FACEBOOKAUTH_SUCCESS, FACEBOOKAUTH_FAIL],
    promise: (client) => client.get('/login/facebook')

  };
}

I can't seem to figure this out ....

I made it work but I don't think it's the optimal solution.

I solved the cors problem using a direct link instead of using the ApiClient:

// src/containers/Login/Login.js

<a href="/api/login/facebook/">Facebook AUTH</a>

The API setup is pretty much the same as the example:

// api/api.js

passport.use(new FacebookStrategy({
    clientID: 'xxx',
    clientSecret: 'xxx',
    callbackURL: 'http://localhost:3000/api/login/facebook/return'
  },
  function(accessToken, refreshToken, profile, cb) {
    // In this example, the user's Facebook profile is supplied as the user
    // record.  In a production-quality application, the Facebook profile should
    // be associated with a user record in the application's database, which
    // allows for account linking and authentication with other identity
    // providers.
    return cb(null, profile);
  }
));

passport.serializeUser(function(user, cb) {
  cb(null, user);
});

passport.deserializeUser(function(obj, cb) {
  cb(null, obj);
});

app.use(bodyParser.urlencoded({ extended: true }));
app.use(session({
  secret: 'react and redux rule!!!!',
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 60000 }
}));

app.use(passport.initialize());
app.use(passport.session());

app.get('/login/facebook',
  passport.authenticate('facebook')
);

app.get('/login/facebook/return',
  passport.authenticate('facebook', { failureRedirect: '/login' }),
  function(req, res) {
    res.redirect('/');
  }
);

And the last change was on loadAuth to access the passport user

// api/actions/loadAuth.js

export default function loadAuth(req) {
  return Promise.resolve(req.session.passport.user || null);
}

Hello,

Does someone has a repo where I can see an implementation of passport ?
Because it's a bit hard for me to know where to write the code.
Can you guide me for what I need to write in api.js, in my sequelize client.js and in my login action ?
Im lost.

I use sequelize model for my user instances :

module.exports = function(sequelize, DataTypes) {
  var Client = sequelize.define("Client", {
    login: DataTypes.STRING,
    email: DataTypes.STRING,
    password: DataTypes.STRING,
    token: DataTypes.STRING,
  }, {
    classMethods: {
      associate: function(models) {
          Client.hasMany(models.User, {
          onDelete: "CASCADE",
          foreignKey: 'c_id'
        }),

        Client.hasMany(models.Group, {foreignKey: 'c_id'}),
        Client.hasMany(models.Subscription, {foreignKey: 'c_id'})
      }
    },
    tableName: 'client',
    timestamps: false
  });

  return Client;
};

In api.js I just wrote that :

app.use(passport.initialize());
app.use(passport.session());

//passport config
passport.use(new LocalStrategy(
    {
        usernameField: 'username',
        passwordField: 'password',
        session: false
    },

    function(req, username, password, done) {
        User.findOne({ username: username }, function (err, user) {
          if (err) { return done(err); }
          if (!user) {
            return done(null, false, { message: 'Incorrect username.' });
          }
          if (!user.validPassword(password)) {
            return done(null, false, { message: 'Incorrect password.' });
          }
          return done(null, user);
        });
    }

));


Was this page helpful?
0 / 5 - 0 ratings

Related issues

massimopibiri picture massimopibiri  路  5Comments

sunkant picture sunkant  路  4Comments

skywickenden picture skywickenden  路  4Comments

ansonla3 picture ansonla3  路  4Comments

glennr picture glennr  路  6Comments