Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problem with multple servers #112

Open
clluiz opened this issue Nov 6, 2018 · 8 comments
Open

Problem with multple servers #112

clluiz opened this issue Nov 6, 2018 · 8 comments

Comments

@clluiz
Copy link

clluiz commented Nov 6, 2018

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?

@machuga
Copy link
Contributor

machuga commented Nov 6, 2018

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

@clluiz
Copy link
Author

clluiz commented Nov 6, 2018

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.

@machuga
Copy link
Contributor

machuga commented Nov 6, 2018

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

@clluiz
Copy link
Author

clluiz commented Nov 6, 2018

We are using redis.

@machuga
Copy link
Contributor

machuga commented Nov 6, 2018

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

@clluiz
Copy link
Author

clluiz commented Nov 6, 2018


  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.

@machuga
Copy link
Contributor

machuga commented Nov 8, 2018

  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?

@clluiz
Copy link
Author

clluiz commented Jan 30, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants