auth0 / passport-wsfed-saml2

passport strategy for both WS-fed and SAML2 protocol

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Problem with multple servers

clluiz opened this issue · comments

In my production environment we have 3 servers. When I set a strategy in one of them, when the redirect comes it may go to a server where the strategy was not set causing the Unknown authenticate strategy error.
How can I solve this problem?

Can you elaborate a bit more on what you mean by having the "strategy set"? And why they are not all similar?

I have two routes:
router.get('/auth/adfs', function () { // there is other code in this function passport.use('strategy name', strategy); });

In this route I create and set the strategy dynamically because some parameters are send from another api.

My second url:
router.post('/auth/adfs/callback/:parameter', function () { passport.authenticate('strategy name', function()) });

This is the callback url. The exception Unknown authentication strategy is thrown in this line: passport.authenticate('strategy name', function()).

I think this happens because the callback url is being called in a server other than the one the strategy was initially set.

Are you using some sort of in-memory state storage?

We are using redis.

This sounds like a configuration issue in how you're selecting the strategy based off the information I have here.


  let config = {
    realm: tenant.authMethods.adfs.realm,
    identityProviderUrl: tenant.authMethods.adfs.identityProviderUrl + (userName ? '?userName=${userName}' : ''),
    protocol: tenant.authMethods.adfs.protocol,
    checkDestination: false,
    checkExpiration: false,
    checkRecipient: false,
    checkAudience: false,
    passReqToCallback: true,
    path: '${nconf.get('KENOBY_BACKEND_URL')}/social/auth/adfs/callback/${tenant._id}'
  };

  if(!_.isEmpty(tenant.authMethods.adfs.certificate)) {
    config.cert = processCertificate(tenant.authMethods.adfs.certificate);
  } else {
    config.thumbprint = tenant.authMethods.adfs.thumbprint;
  }

  return new Wsfedsaml2(config, function (req, profile, done) {
    co(function *() {
      let propertyPrefix = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/';
      let email = _.get(profile, '${propertyPrefix}emailaddress', '').toLowerCase();
      let domain = email.substring(email.indexOf('@'), email.length);
      let user = yield User.findOne({'email' : email}).lean();

      if (!user) {
        let name = _.get(profile, '${propertyPrefix}nameidentifier', '');
        let tenant = yield Tenant.findOne({'domains' : domain}).select('_id name').lean();
        if (!tenant) return done(null, false);
        user = new User({
          'name'     : name,
          'email'    : email,
          'tenant'   : tenant._id,
          'profiles' : [{
            'tenant' : tenant._id,
            'roles'  : ['INDICATION']
          }]
        });
        yield user.save(); 
        user = yield User.findOne(user._id).lean();
      }
      return done(null, user);
    });
  });
};

let authorizeLoginPassport = function* (next) {
  var ctx = this;
  let query = {};
  let destination = `${nconf.get('KENOBY_FRONTEND_URL')}/#/login`;
  if (this.query.tenant) {
    query = {_id : this.query.tenant};
  } else {
    let splited = this.query.email ? this.query.email.split('@') : [];
    let domain = splited.length > 1 ? splited[1] : '';
    query = {domains: {$in : [`@${domain}`]}};

    if(this.query.jobsite) {
      destination = `${nconf.get('KENOBY_JOBSITE_URL')}/${this.query.jobsite}`;
      yield setUserDestination(this.query.email, destination);
    } 
  }

  let tenant = yield Tenant.findOne(query).select('name authMethods.adfs').lean();
  let strategy = loginStrategy(tenant, this.query.email);
  passport.use(`adfs-${tenant._id.toString()}`, strategy);
  yield passport.authenticate(`adfs-${tenant._id.toString()}`, function* (err, user) {
    
    if (err) { 
      return err;  // eslint-disable-line
    } 
    if (!user) { 
      redisClient.expire(`adfs-${this.query.email}`);
      return ctx.redirect(destination); 
    }
  }).call(this, next);
};

let authorizeLoginCallback = function* (next) {
  var ctx = this;
  yield passport.authenticate(`adfs-${this.params.tenant}`, function* (err, user) {
    
    if (err) { return next(err); }
    
    let destination = yield shouldRedirectUserToJobsite(user.email);
    if(!destination) {
      destination = `${nconf.get('KENOBY_FRONTEND_URL')}/#/login`;
    }
    if (!user) { 
      return ctx.redirect(destination); 
    }

    if (!ctx.session) this.throw(401);
    if (!ctx.params.tenant) this.throw(401);
    let userId = user._id;
    let token = ctx.state.generateToken(userId);
  
    yield new Promise((resolve, reject) => {
      redisClient.set(token, userId, function (err) {
        if (err) return reject(err);
        redisClient.expire(token, 60 * 60); // cache for 60m
        resolve();
      });
    });
  
    let url = `${destination}?token=${token}`;
    redisClient.del(`adfs-${user.email}`);
    ctx.redirect(url);
    yield next;

  }).call(this, next);
};
router.post('/auth/adfs/callback/:tenant', AdfsController.authorizeLoginCallback);

router.get('/auth/adfs', AdfsController.authorizeLoginPassport);

@machuga The flow starts by calling the /auth/adfs passing the parameters. Then after the user enter user and password the callback are called by the adfs.
Note that the startegy is set at execution time.

  let tenant = yield Tenant.findOne(query).select('name authMethods.adfs').lean();
  let strategy = loginStrategy(tenant, this.query.email);
  passport.use(`adfs-${tenant._id.toString()}`, strategy);

Have you verified the other machines still have these query params available?

They don't have it. Because the strategy is set inside a route.