sjhoeksma / cordova-plugin-keychain-touch-id

Touch ID plugin with saving password in keychain for IOS and android
87 stars 160 forks source link

Abandoned Plugin #57

Open MatthewPringle opened 5 years ago

MatthewPringle commented 5 years ago

This plugin no longer seems to be getting updates.

It has numerous issues that can cause build failures and crashes on both iOS and Android.

The docs are incorrect.

I would recommend not using this plugin as it will only cause you issues.

vvarda commented 5 years ago

@MatthewPringle some alternatives..?

jacobweber commented 5 years ago

Does anyone know of an alternative plugin? I need one that supports keychain storage like this one, not just fingerprint authentication.

MatthewPringle commented 5 years ago

Removed

danicholls commented 5 years ago

What about using one or more fingerprint/face auth plugins with https://github.com/Crypho/cordova-plugin-secure-storage ? Looking into it myself (raised an issue about its underlying dependency, but the repo itself seems good).

mzealey commented 5 years ago

https://github.com/niklasmerz/cordova-plugin-fingerprint-aio looks good am about to try that in an app

edubskiy commented 5 years ago

@mzealey , did you try?

mzealey commented 5 years ago

Yes works great out of the box.

edubskiy commented 5 years ago

How do you save/retrieve user passwords in keychain using this plugin, it seems to have no option to handle this case, am I wrong? If yes, code sample would be great.

MatthewPringle commented 5 years ago

@edubskiy I can put up an example of this plugin cordova-plugin-keychain-touch-id working.

It is in Ember though and uses computed values / services so wouldn't be so straight forward. But it does work and has been used in a couple of applications I have used.

MatthewPringle commented 5 years ago

/ App - Service - Biometrics / / ---------------------------------------------------------------------------------------------------- /

/ Import / import EmberService from '@ember/service'; import { inject as service } from '@ember/service'; import { computed } from '@ember/object'; import { isPresent } from '@ember/utils';

/ Export / / ---------------------------------------------------------------------------------------------------- / export default EmberService.extend({

/* Setup */
/* ------------------------------------------------------------------------------------------------ */
type: false, saved: false,

/* Services */
/* ------------------------------------------------------------------------------------------------ */
authService:    service( 'authentication' ),
dialogService:  service( 'dialog'         ),
storageService: service( 'storage'        ),

/* Active */
/* ------------------------------------------------------------------------------------------------ */
active: computed( 'type' , 'saved' , function() {
    return this.get( 'type' ) && this.get( 'saved' );
}),

/* Label */
/* ------------------------------------------------------------------------------------------------ */
label: computed( 'type' , function() {

    /* Touch ID */
    if ( this.get( 'type' ) === 'touch' ) {
        return 'Touch ID';

    /* Face ID */
    } else if ( this.get( 'type' ) === 'face' ) {
        return 'Face ID';

    /* Biometric ID */
    } else {
        return 'Biometric ID';
    }

}),

/* Status */
/* ------------------------------------------------------------------------------------------------ */
status: function() {

    /* Check Plugin */
    if ( window.cordova !== undefined && window.plugins !== undefined && window.plugins.touchid !== undefined ) {

        /* Setup */
        var self = this;

        /* Available */
        window.plugins.touchid.isAvailable( function( type ) {

            /* Set Type */
            self.set( 'type' , type );

        /* Unavailable */
        }, function() {

            /* Reset Type */
            self.set( 'type' , false );

        });

    }

},

/* Authenticate */
/* ------------------------------------------------------------------------------------------------ */
authenticate: function() {

    /* Check Plugin */
    if ( window.cordova !== undefined && window.plugins !== undefined && window.plugins.touchid !== undefined ) {

        /* Setup */
        var self = this;

        /* Load Password */
        window.plugins.touchid.verify( this.get( 'storageService' ).load( 'username' ) , 'Access your App Name account' , function( password ) {

            /* Authenticate */
            self.get( 'authService.authenticate' ).perform( self.get( 'storageService' ).load( 'username' ) , password );

        /* Error */
        }, function() {

            /* Reset */
            self.set( 'saved' , false );

            /* Reset Question */
            self.get( 'storageService' ).save( 'biometrics' , '' );

        });

    }

},

/* Restore */
/* ------------------------------------------------------------------------------------------------ */
restore: function() {

    /* Update Status */
    this.status();

    /* Check Response */
    if ( window.cordova !== undefined && this.get( 'storageService' ).load( 'biometrics' ) !== 'rejected' && isPresent( this.get( 'storageService' ).load( 'username' ) ) ) {

        /* Check Plugin */
        if ( window.plugins !== undefined && window.plugins.touchid !== undefined ) {

            /* Setup */
            var self = this;

            /* Key Exists */
            window.plugins.touchid.has( this.get( 'storageService' ).load( 'username' ) , function() {

                /* Set Saved */
                self.set( 'saved' , true );

            /* Key Doesnt Exist */
            }, function() {

                /* Reset Saved */
                self.set( 'saved' , false );

                /* Reset Question */
                self.get( 'storageService' ).save( 'biometrics' , '' );

            });

        }

    }

},

/* Save */
/* ------------------------------------------------------------------------------------------------ */
save: function( username , password ) {

    /* Check Response */
    if ( window.cordova !== undefined && this.get( 'storageService' ).load( 'biometrics' ) !== 'rejected' && this.get( 'type' ) ) {

        /* Check Plugin */
        if ( window.plugins !== undefined && window.plugins.touchid !== undefined ) {

            /* Ask Question */
            if ( !this.get( 'saved' ) ) {

                /* Ask Question */
                this.question( username , password );

            /* Save */
            } else {

                /* Save Password */
                window.plugins.touchid.save( username , password , false , function() {} , function() {} );

            }        

        }

    }

},

/* Reset */
/* ------------------------------------------------------------------------------------------------ */
reset: function() {

    /* Check Plugin */
    if ( window.cordova !== undefined && window.plugins !== undefined && window.plugins.touchid !== undefined ) {

        /* Check Saved */
        if ( this.get( 'saved' ) ) {

            /* Delete Password */
            window.plugins.touchid.delete( this.get( 'storageService' ).load( 'username' ) , function() {} , function() {} );

            /* Reset */
            this.set( 'saved' , false );

        }

        /* Reset Question */
        this.get( 'storageService' ).save( 'biometrics' , '' );

    }

},

/* Question */
/* ------------------------------------------------------------------------------------------------ */
question: function( username , password ) {

    /* Check Plugin */
    if ( window.cordova !== undefined && window.plugins !== undefined && window.plugins.touchid !== undefined ) {

        /* Setup */
        var self = this;

        /* Show Question */
        this.get( 'dialogService' ).confirm( 'App Name' , 'Do you want to use your ' + this.get( 'label' ) + ' to log in next time?',

            /* Answer - Yes */
            /* ------------------------------------------------------------------------------------ */
            function() {

                /* Save Password */
                /* -------------------------------------------------------------------------------- */
                window.plugins.touchid.save( username , password , true , function() {

                    /* Set Saved */
                    self.set( 'saved' , true );

                    /* Show Notification */
                    self.get( 'dialogService' ).notification( 'App Name' , 'Continue' , 'Your password has been saved.' );

                /* Error */
                /* -------------------------------------------------------------------------------- */
                }, function() {

                    /* Set Saved */
                    self.set( 'saved' , false );

                    /* Show Notification */
                    self.get( 'dialogService' ).notification( 'App Name' , 'Continue' , 'An error has occurred. Your password has not been saved.' );

                });

            /* Answer - No */
            /* ------------------------------------------------------------------------------------ */
            }, function() {

                /* Set Saved */
                self.set( 'saved' , false );

                /* Save Response */
                self.get( 'storageService' ).save( 'biometrics' , 'rejected' );

            }

        );

    }

}

});

MatthewPringle commented 5 years ago

For the above...

Storage Service just saves a cookie with the username and if the user has rejected using biometrics

Dialog Service is just a wrapper for the Cordova notifications plugin ( the code has to work on a website as well and would adapt to use javascript notifications in place of Cordova notifications )

Auth Service is a separate service for authenticating users with the server, you can replace this with your own once you have retrieved the password from the keychain.

MatthewPringle commented 5 years ago

@edubskiy no I use window.plugins.touchid.save( username , password , false , function() {} , function() {} );

I save the username in a cookie / local storage. I recall the username and then I can pass that to

window.plugins.touchid.has( this.get( 'storageService' ).load( 'username' ) , function() {

To check if the user has a stored username / password protected by biometrics

And then I use

window.plugins.touchid.verify( this.get( 'storageService' ).load( 'username' ) , 'Access your App Name account' , function( password ) {

To retrieve the password using the username + biometrics

MatthewPringle commented 5 years ago

The basic idea is that on a login page, you would save a cookie with the username after first login, ask about using Biometrics for future logins etc... ( see question: function( username , password ) { )

When the user sees the login page again, having checked to see if a cookie exists, it autofills the username part of the login form.

I then query the touch id plugin to see if a username exists in the touch id password store.

If so I can present the user with a "Login with Biometrics" button rather than asking them for a password.

They can then provide biometric authentication and the device passes me back the password.

MatthewPringle commented 5 years ago

You would also want to fire the status: function() on app init so you know about the capabilities of the device.

edubskiy commented 5 years ago

@MatthewPringle , thank you, but I thought you showed the code for this plugin https://github.com/niklasmerz/cordova-plugin-fingerprint-aio We also have similar code you showed, and 95% of the time it really works ok, but in some cases we have complains from users when touchid.save is not working correctly and it seems there is no support for plugin any more that is why we have to replace it with alternative.

MatthewPringle commented 5 years ago

@edubskiy no it is for this plugin, after a lot of testing the code we used seems to work fine across all devices and platforms we tested on. Due to problems with other plugins we decided to use this one for the current releases.

The code we wrote is a little different from their examples, such as

window.plugins.touchid.save( username , password , false , function() {} , function() {} );

Notice the save function has one more param than the one in their example