Custom code login working with Android but not iOS

I have used https://gonative.io/ to package the app and add native functionality including biometric log in.

The full custom code block accesses the GoNative functionality to return the user email and password from the stored promise that is set when a user first logs in to the app (you will see I have used email calls to test the stages)

This works with Android phone and the user is logged in. iOS returns the error.

gonative.auth.status({‘callbackFunction’: gonative_status_beforelogin}); // returns promise

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “1”;
bodyParts.htmlmessage = “1”;

Backendless.Messaging.sendEmail( “promise has been returned”,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})

function gonative_status_beforelogin(data) {
if (data && data.hasTouchId && data.hasSecret) {
// Prompt the user to use the fingerprint to log in
gonative.auth.get({‘callbackFunction’: gonative_secret_callback});
}
}

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “2”;
bodyParts.htmlmessage = “2”;

Backendless.Messaging.sendEmail( “fingerprint prompt works”,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})

function gonative_secret_callback(data) {
if (data && data.success && data.secret) {
var credentials = JSON.parse(data.secret);
var username = credentials.username;
var password = credentials.password;

    // Use username and password to do login here,
    // e.g. an http post or ajax request
   // const Backendless = require('backendless')

/*
Or use import Backendless from 'backendless' for client side.
If you don’t use npm or yarn to install modules, you can add the following line

to your index.html file and use the global Backendless variable.
*/

//Backendless.initApp(‘8518B92E-A6AA-5589-FFA0-B863E4A63F00’, ‘xxx’)

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “3”;
bodyParts.htmlmessage = “3”;

Backendless.Messaging.sendEmail( “secret callback happened”,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “4”;
bodyParts.htmlmessage = “4”;

Backendless.Messaging.sendEmail( username,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})

const onSuccess = user => {
const pageName = minaPageURL
const pageData = { logedUser: user }

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “6”;
bodyParts.htmlmessage = “6”;

Backendless.Messaging.sendEmail( “on success email”,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})

//BackendlessUI.Navigator.goToPage(pageName)
BackendlessUI.Navigator.goToPage(pageName, pageData)

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “7”;
bodyParts.htmlmessage = “7”;

Backendless.Messaging.sendEmail( “gotopage executed”,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})
}

const onError = error => {
console.error('Server reported an error: ', error.message)
//console.error('error code: ', error.code)
//console.error('http status: ', error.status)

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “8”;
bodyParts.htmlmessage = “8”;

Backendless.Messaging.sendEmail( error.message,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})
}

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “5”;
bodyParts.htmlmessage = “5”;

Backendless.Messaging.sendEmail( “on success and on error set”,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})

Backendless.UserService.login(username, password, true)
.then(onSuccess)
.catch(onError)

var bodyParts = new Backendless.Bodyparts();
bodyParts.textmessage = “9”;
bodyParts.htmlmessage = “9”;

Backendless.Messaging.sendEmail( “completed”,
bodyParts,
[ “luczentar@mina.group” ],
)
.then( function( response ) {
console.log( “message has been sent” );
})
.catch( function( error ) {
console.log( "error " + error.message );
})

} else {
    // Allow manual entry
}

}

@Luc_Zentar ,

Thank you for providing your code. Now I better understand the situation.
It seems to me that there can be a problem on GoNative side since it is the only place where that call can be made.

Could you please try to use your CloudCode API key during initialization of your app logic and try one more time to check login? If problem on their side login should work fine with CloudCode key.

Regards, Andriy

The calls to GoNative are working.

They return the promise and prompt the biometric login option.

I know GoNative is returning the correct data because this works with Android devices.

The call that is not working is the call to Backendless from within the Backendless application when accessing the webview from an iOS device.

Backendless.UserService.login(username, password, true)
.then(onSuccess)
.catch(onError)

And the error that is returned is a backendless error. I don’t understand how this can be a problem with GoNative?

@Luc_Zentar ,

It was just a guess from my side.

Do you have any Backendless event handlers for login operation?

Regards, Andriy

@Andriy_Konoz thanks Andriy, no i don’t have any event handlers for login.

Hi @Luc_Zentar ,

Could you please add additional checks for password presence before calling login method?
I discussed your situation with our JS team and they say that if password was not in data.secret or was there with an undefined value then login method would try to perform “login by user ID” instead of regular login.

Regards, Andriy

@Andriy_Konoz

Thank you for checking for me and for all the help so far. You’re right that the password isn’t returned when an iOS user attempts login.

GoNative told me:

On the GoNative side it is possible the gonative.auth module is not loaded when you are calling the status function. Try adding this within a gonative_library_ready() function. Regarding the JavaScript you are using to facilitate the login it is likewise possible that function is not yet loaded so you could check it is available before calling. iOS and Android both vary across devices on these points.

I have added this to the code and now the custom code block doesn’t execute at all. Can you see why this is a problem? The custom code block is set to execute on page load.

function gonative_library_ready(){
gonative.auth.status({‘callbackFunction’: gonative_status_beforelogin}); // returns promise

function gonative_status_beforelogin(data) {
if (data && data.hasTouchId && data.hasSecret) {
// Prompt the user to use the fingerprint to log in
gonative.auth.get({‘callbackFunction’: gonative_secret_callback});
}
}

function gonative_secret_callback(data) {
if (data && data.success && data.secret) {
var credentials = JSON.parse(data.secret);
var username = credentials.username;
var password = credentials.password;

const onSuccess = user => {
const pageName = minaPageURL
const pageData = { logedUser: user }

BackendlessUI.Navigator.goToPage(pageName, pageData)

const onError = error => {
console.error('Server reported an error: ', error.message)
console.error('error code: ', error.code)
console.error('http status: ', error.status)

Backendless.UserService.login(username, password, true)
.then(onSuccess)
.catch(onError)

} else {
    // Allow manual entry
}

}
}

@Luc_Zentar ,

Provided by you code seems to be invalid. You placed all your functions inside of gonative_library_ready function and didn’t close function body at the end. I think that your app currently doesn’t work due to this syntax error

Regards, Andriy

@Andriy_Konoz

Thank you, I have further feedback from GoNative which is:

As far as the iOS secure storage is concerned it is only storing the “secret” and there is no differentiation on username/password/token/any other property set on the secret. If you are retrieving one property of the secret successfully then any issue with another property would likely be due to the data type of the property not being a properly formed string or another JavaScript code issue.

I’ve reverted my code to:

gonative.auth.status({‘callbackFunction’: gonative_status_beforelogin}); // returns promise

function gonative_status_beforelogin(data) {
if (data && data.hasTouchId && data.hasSecret) {
// Prompt the user to use the fingerprint to log in
gonative.auth.get({‘callbackFunction’: gonative_secret_callback});
}
}

function gonative_secret_callback(data) {
if (data && data.success && data.secret) {
var credentials = JSON.parse(data.secret);
var username = credentials.username;
var password = credentials.password;

const onSuccess = user => {
const pageName = minaPageURL
const pageData = { logedUser: user }

BackendlessUI.Navigator.goToPage(pageName, pageData)
}

const onError = error => {
console.error('Server reported an error: ', error.message)
console.error('error code: ', error.code)
console.error('http status: ', error.status)
}

Backendless.UserService.login(username, password, true)
.then(onSuccess)
.catch(onError)

} else {
    // Allow manual entry
}

}

If you have any idea why this might be happening on iOS devices within I’d be really grateful.

@Luc_Zentar ,

I can only recommend you to clarify in GoNative support how to obtain login and password from that “secret” token.

Unfortunately I can’t help you more there since cause of the problem lies beyond Backendless platform. Also I have little knowledge in IOS internals and how GoNaive utilize mobile OS features.

BTW, why you decided to use GoNative instead of Backendless Native Shell? Just curious about their features which influenced your choice :slightly_smiling_face:

Regards, Andriy

@Andriy_Konoz

Thanks, it seems that the problem lies somewhere between Backendless and GoNative. I wondered if the issue would be the format of the password return from an iOS promise being different to that of Android, and this not being compatible with what Backendless expects.

Is there any way to be able to see this in error logs somewhere? To see if it is returning null value or some format that isn’t accepted?

I did try the native shell but it just isn’t as easy as using GoNative. You need knowledge of flutter to use it. I didn’t have time to learn that so I brought in a consultant, but he didn’t know Backendless so we had integration problems. I also had to understand how to package the app and release on the stores.

v1 of the app was released this way, but it was too slow with the consultant trying to learn Backendless and native functionality was minimal. I was just looking for speed and ease of release.

v2 GoNative took care of the biometric (almost!) and the native menus, and the release to the app stores.

A big advantage for me as well is that any changes to Backendless code update the app without the need for a new version release. I don’t think the native shell approach allows this.

As we’re product testing all the time, being able to quickly change parts of the app and test their impact is super important.

If you guys did a similar solution to GoNative where you wrapped up my web app and released it for me, adding and integrating native functions like menu, biometric, push and social login, I would want to use you ahead of GoNative because you also have the knowledge of the Backendless system that hosts the web app. It would help avoid situations like this one!

What I think is so amazing about Backendless is that I’ve gone from zero coding experience to building a somewhat complex app that has paying customers. This has been a combination of excellent functionality, great support documentation and the quality of the support on this forum. The flutter shell didn’t feel the same, it was ultimately too difficult for me and I ended up paying a lot of money to another company that I would rather have paid to you :slight_smile:

@Luc_Zentar ,

Is there any way to be able to see this in error logs somewhere? To see if it is returning null value or some format that isn’t accepted?

Using Backendless you have two options to collect logs:

  1. You can use Backendless logger for that. You can check overview and JS code examples. This is recommended way for implementation of logging in applications.
  2. You can simply create “Logs” table and create entry (store record with log message) to it using Data Service. This is “fast and dirty” way to do temporal logging for development purposes. It should not be used as permanent approach.

In both cases you need to initialize Backendless JS SDK before making any SDK method calls.

Also I want to thank you for your feedback. I will pass it to our team so we could improve our “weak” sides. I really appreciate it :grinning:

Regards, Andriy

@Luc_Zentar ,

BTW, we also provide consulting services for our customers. You can contact our sales department to get more information about pricing. Maybe it would be cheaper and more convenient for you to move back to our platform completely in the future.

Regards, Andriy

Thanks @Andriy_Konoz - I was speaking to your sales team in January about two smaller pieces of work before I was ready to package the app. I was quoted $21,000 for the projects that I was able to get done for $1,300 using Upwork, so I didn’t think of it as a realistic option for my stage of business.

Hope this feedback is helpful.

@Andriy_Konoz

In both cases you need to initialize Backendless JS SDK before making any SDK method calls.

Does this need to be done if using custom code blocks in UI Builder, or is it already initialised in there?

It’s already initialized there

1 Like

Thanks all for your help, with the troubleshooting and logging I’ve been able to find the problem.

I’ll put the solution here in the event that anyone else finds this issue:

When storing the secret, I was using this code in a custom codeless block:

var username = email
var password = password;

gonative.auth.status({‘callbackFunction’: gonative_status_afterlogin}); // returns promise

function gonative_status_afterlogin(data) {
if (data && data.hasTouchId) {
var secret = JSON.stringify({
username: username,
password: password
});

    gonative.auth.save({'secret': secret});
}

}

In the first 2 lines I am passing in the username and password values from the codeless block.

I’ve changed the code to:

var username = email
var password = pw;

gonative.auth.status({‘callbackFunction’: gonative_status_afterlogin}); // returns promise

function gonative_status_afterlogin(data) {
if (data && data.hasTouchId) {
var secret = JSON.stringify({
username: username,
password: password
});

    gonative.auth.save({'secret': secret});
}

}

And this now works. It seems iOS didn’t like the var password value being named password, but Android didn’t mind.

1 Like