Custom login handler - unrecognized options for login request 400

Hey there,

this might be an odd request. I’m looking for someone who has experience with custom Meteor login handler and might be able to help me solve a basic problem. I’ve spent way to much time on building a simple ldap authentication handler myself. It shouldn’t be a big deal.

To explain the problem I need to give you some context. First the client code:

client/main.js

...
Meteor.loginUserWithLDAP = function (email, password, callback) {
  let loginRequest = {
    email: email,
    pass: password,
    ldap: true
  }
  Accounts.callLoginMethod({
    methodArguments: [loginRequest],
    userCallback: callback
  })
}
...

When executing this method, an error is thrown immediately: unrecognized options for login request 400
And the user is not authenticated.

The error is thrown by the Accounts package (line 479): https://github.com/meteor/accounts/blob/master/packages/accounts-base/accounts_server.js

And the same time the server code is executed successfully.

server/ldap.js

import ldap from 'ldapjs'
import assert from 'assert'
import { Accounts } from 'meteor/accounts-base'

var client = ldap.createClient({
  url: 'ldap://ldap.forumsys.com'
})

var bound = Meteor.bindEnvironment((callback) => {
  return callback()
})

Accounts.registerLoginHandler('ldap', (loginRequest) => {

  console.log('Login request received.', loginRequest)

  if (!loginRequest.ldap) {
    return undefined // don't handle
  }

  let bindUser = (dn, email, password, profile) => {
    client.bind(dn, password, (error) => {
      if(error) throw error

      bound(() => {
        var userId = null
        var user = Meteor.users.findOne({ "emails.address" : email })
        if (!user) {
          userId = Accounts.createUser({
            email: email,
            password: password,
            profile: profile,
            roles: ['user'],
          })
          Meteor.users.update(userId, { $set: { 'emails.0.verified': true } })
        } else {
          userId = user._id
        }
        let stampedToken = Accounts._generateStampedLoginToken()
        let hashStampedToken = Accounts._hashStampedToken(stampedToken)
        Meteor.users.update(userId,
          { $push: { 'services.resume.loginTokens': hashStampedToken } }
        )

        console.log('Loggin')
        return {
          id: userId,
          token: stampedToken.token
        }
      })
    })
  }

  let options = {
    filter: `(mail=${loginRequest.email})`,
    scope: 'sub'
  }

  client.search('dc=example,dc=com', options, (error, result) => {
    if(error) throw error

    result.on('searchEntry', (entry) => {
      let profile = {
        firstname: entry.object.cn,
        lastname: entry.object.sn
      }
      bindUser(entry.object.dn, entry.object.mail, loginRequest.pass, profile)
    })
  })
})

The server console tells me that the return statement has been reached.

I did quite a lot of research trying to solve this problem, but no success so far.

I’m not sure wether I’ve used the bind environment functions the right way, so this might be a source for mistakes.

Would be great if somebody could give me a strategy on how to solve this problem.

I solved it by using the following server auth handler:

import ldap from 'ldapjs'
import assert from 'assert'
import { Accounts } from 'meteor/accounts-base'
import Future from 'fibers/future'

var ldapAuth = {
  url: 'ldap://ldap.forumsys.com',
  searchOu: 'dc=example,dc=com',
  searchQuery: (email) => {
    return {
      filter: `(mail=${email})`,
      scope: 'sub'
    }
  }
}

ldapAuth.checkAccount = (options) => {
  options = options || {}

  ldapAuth.client = ldap.createClient({
    url: ldapAuth.url
  })

  let dn = []
  var future = new Future()

  ldapAuth.client.search(ldapAuth.searchOu, ldapAuth.searchQuery(options.email), (error, result) => {
    assert.ifError(error)

    result.on('searchEntry', (entry) => {
      dn.push(entry.objectName)
      return ldapAuth.profile = {
        firstname: entry.object.cn,
        lastname: entry.object.sn
      }
    })

    result.on('error', function(error){
      throw new Meteor.Error(500, "LDAP server error")
    })

    return result.on('end', function(){

      if (dn.length === 0) {
        future['return'](false)
        return false
      }

      return ldapAuth.client.bind(dn[0], options.pass, (error) => {

        if (error) {
          future['return'](false)
          return false
        }

        return ldapAuth.client.unbind((error) => {
          assert.ifError(error)
          return future['return'](!error)
        })
      })
    })
  })
  return future.wait()
}

Accounts.registerLoginHandler('ldap', (loginRequest) => {

  if (!loginRequest.ldap) {
    return undefined
  }

  if (ldapAuth.checkAccount(loginRequest)) {
    var userId = null
    var user = Meteor.users.findOne({ "emails.address" : loginRequest.email })
    if (!user) {
      userId = Accounts.createUser({
        email: loginRequest.email,
        password: loginRequest.pass,
        profile: ldapAuth.profile,
        roles: ['user'],
      })
      Meteor.users.update(userId, { $set: { 'emails.0.verified': true } })
    } else {
      userId = user._id
    }

    let stampedToken = Accounts._generateStampedLoginToken()
    let hashStampedToken = Accounts._hashStampedToken(stampedToken)
    Meteor.users.update(userId,
      { $push: { 'services.resume.loginTokens': hashStampedToken } }
    )

    return {
      userId: userId,
      token: stampedToken.token
    }
  }
})
1 Like

is this solution working for meteor 1.4.3 + ? …
in older solutions there was method Meteor.loginWithLdap …
in you solution is Accounts.registerLoginHandler…
neither is working…
thx for answer

Yes it working for 1.4.3

What do you mean by older solution? loginWithLdap was never part of the offical Meteor package. It is a custom function that calls a specific login handler and is attached to a global variable (Meteor, Accounts or whatever … )

Checkout the step by step tutorial of my final solution: https://janikvonrotz.ch/2017/02/08/meteor-register-ldap-login-request-handler/

1 Like