Parse-server: Custom authentication failed: Cannot read property 'authenticate' of undefined

Created on 15 Mar 2018  路  15Comments  路  Source: parse-community/parse-server

Issue Description

I'm trying to do the custom authentication, but got confused through the insufficient docs.
http://docs.parseplatform.org/parse-server/guide/#custom-authentication

Steps to reproduce

Here is my cloud code:

const authmyapp = require('./authmyapp')

Parse.Cloud.define('sign', function(req, res) {
  const user = new Parse.User()
  const authData = {
    myapp: {
      module: authmyapp,
      openid: 'test',
    },
  }
  user._linkWith('weapp', authData).then(userInfo => {
    res.success(userInfo)
  })
})

And to eliminate interference, I made the auth logic like this:

// authmyapp.js

function validateAuthData(authData) {
  return Promise.resolve()
}

function validateAppId() {
  return Promise.resolve()
}

module.exports = {
  validateAppId,
  validateAuthData,
}

Expected Results

A new user signed up with my custom auth-info linked.

Actual Outcome

When running this cloud code, it threw this error on the log:

error: Error generating response. TypeError: Cannot read property 'authenticate' of undefined
    at ParseUser.value (***/node_modules/parse-server/node_modules/parse/lib/node/ParseUser.js:170:18)
    at ***/cloud/main.js:18:8
    at ***/node_modules/parse-server/lib/Routers/FunctionsRouter.js:176:9
    at new Promise (<anonymous>)
.....

Environment Setup

  • Server

    • parse-server version (Be specific! Don't say 'latest'.) : 2.7.4

    • Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): localhost

Correct me if I did something wrong. Thanks. And actually the docs lack many example code to help us figure out.

All 15 comments

I think you should try to config custom authentication module in the ParseServer config when initialising it (instead of put it in the authData):

new ParseServer({
  appId: {your-app-id},
  ...
  auth: {
     'weapp': {module: authmyapp}
  }
})

This makes ParseServer recognise the custom auth method and invoke the custom validation logic when doing user. _linkWith ('weapp', authData)

@6thfdwp
Hi. Thank you! I found the doc on the README.md in this repository (https://github.com/parse-community/parse-server#advanced-options). And I've tried what you said while still getting the same error output.

Here's my config:

const authmyapp = require('./utils/authmyapp')

new ParseServer({
  appId: 'myapp',
  ...
  auth: {
    weapp: {
      module: authmyapp,
      option1: 'openid',
    },
  },
})

And in the cloud code:

Parse.Cloud.define('sign', function(req, res) {
  const user = new Parse.User()
  const authData = {
    weapp: {
      openid: 'test',
    },
  }
  user._linkWith('weapp', authData).then(userInfo => {
    res.success(userInfo)
  })
})

BTW, I tried to understand the source code of Parse.User._linkWith in the JS-SDK:
https://github.com/parse-community/Parse-SDK-JS/blob/a209282de1a2106ce8453cb3c985c77cee678304/src/ParseUser.js#L30

var authProviders = {};

https://github.com/parse-community/Parse-SDK-JS/blob/a209282de1a2106ce8453cb3c985c77cee678304/src/ParseUser.js#L84

  _linkWith(provider: any, options: { authData?: AuthData }): ParsePromise {
    var authType;
    if (typeof provider === 'string') {
      authType = provider;
      provider = authProviders[provider]; // this line causes errors
    } else {
      authType = provider.getAuthType();
    }

So, authProviders is set to an empty object, and there seems not to be any evidence that makes it changed. It should be undefined absolutely? Where do we inject our custom auth config into the object? Did I miss anything or do something wrong? Please give me some advice. Thanks a lot!~

OK, by looking at your error log again, it's from the Parse client, not server. The ParseUser _linkWith second parameter expects an options object which has authData as its key. So it will look like:

user._linkWith('weapp', {authData})

If it cannot find this key, it will ge to this else block: ParseUser.js#L102

BTW, you could directly use static method on ParseUser, ParseUser.loginWith which essentially call internal _linkWith

From @flovilmart comment above, my understanding is linkWith logic is to mainly look at if options already got authData, if it's true, it will be directly sent to Parse server, otherwise it tries to look up the registered authProvider, call authenticate to get authData first. It fails in this case as we don't have one

Yes, you just need to register a simple one, that implements the same methods as the Facebook one. If you鈥檙e passing the authData directly, you may not need to implement the body of all methods, but probably just the getAuthType() that returns your authProvuder name (weapp)

Thank you guys!
Having looked your explanations and examples, I made my code like this (with the above auth config in the ParseServer Initialization kept):

...
  const user = new Parse.User()
  const authData = {
    weapp: {
      openid: 'test',
    },
  }
  const provider = {
    authenticate(options) {
      options.success(this, {
        openid: 'test',
      })
    },
    getAuthType() {
      return 'weapp'
    },
  }
  user._linkWith(provider, authData).then(userInfo => {
    res.success(userInfo)
  })
...

where I only need to passing the authData directly.

But the error log said

Error generating response. ParseError {
  code: 252,
  message: 'This authentication method is unsupported.' }

After searching for the error generator:
https://github.com/parse-community/parse-server/blob/11c40dce97da72271e362e50016500e9f3e3a1ec/src/RestWrite.js#L230
I realized that I need to changed the 'openid' to 'id'. And it works. But I'm not sure if it's a good way.

And here's something I still don't understand:

  1. Where should I place the _registerAuthenticationProvider(provider) if I want to make a one-time initialization?(Like what you said to 'make the first parameter a string key'.) Do I need to write a file like FacebookUtils.js and import it to some config?
  2. What is the second parameter (authData) in _linkWith used for if I've got the provider.authenticate({ success }) { success(this, authData) } in the first parameter to give the auth data? (If I keep the second an empty object, it works as well. But I can't keep it undefined, otherwise it throw a error.)
  3. Again, the document seems so weak. I tried to make something like adapter to complete this but found it hard to figure it out. Any supplement, simple examples or templates would be appreciate! Have you got any plans on that? Thank you! 馃槃

You should out the registerProvider alongside the Parse.initialize call.

If your provider is providing the authData, then the second parameter is not read nor used. You can safely ignore it.

We don鈥檛 have any plans to update the documentation at the moment, but yes, it could be better.

On my side i鈥檒l Try to update the JS SDK so it鈥檚 easier and self documenting. But you can very well open a PR on the docs repository :)

@flovilmart Also, how are you setting up the username during oAuth. I see a random username generated. Any support for customizing username? Or should we just let it be and use a different field like appUsername all together?

Once the user is logged in with the 3rd party auth, you鈥檙e free to set the username to whatever you want, i鈥檇 Like to keep it in 2 steps as they both are different flows that may conflict. The login with Facebook for example can either create a new user or pull one existing in the DB. And usually at login time, you are unlikely to have prompted your user for a username or an email.

What would be your use case?

Well my requirements are like this:

  1. Obtain Facebook account kit authDataData from oAuth provider.
  2. Then obtain username field from the user.
  3. logInWith and then become followed by re-writing username field and then finally followed by a save.
    Any trade-offs of this apporach?

The issue with this approach is that any failure will happen very late in the process, at the last step as you鈥檒l gather all infos befor making a single call to your API. Which can lead to hard to debug scenarios. You should probably just do the logIn first, calling parse-server to ensure the auth data is valid, then try to set the username and save immediately, if the username is already taken, you can prompt for another one.

Cool. I was at this. Thanks.

@flovilmart
Why is there a check that the authData object has a key named id?

Shouldn't it be up to the developer of a custom auth provider to handle whatever is passed in the authData object? I've struggled with this for quite some time and eventually stumbled upon this issue. Haven't found anything in the documentation on this. I would happily make a pull request. But first I wonder if:
A. The check should be removed?
B. The check should be left in the code but clarified in the documentation. And if so, why it there.

I have no opinion on this. Check with the maintained of this project, open a new issue or a pull request (I guess)

Was this page helpful?
0 / 5 - 0 ratings