Troubleshooting
Common Problems
When implementing Identity Vault, you need to avoid scenarios in your application where two native calls are made at the same. Here are a few common patterns with suggestions on how to solve for them.
- Calling
new Vault()
in a Constructor - Calling `new Vault(config)
- Using Auth Guards and
lockafterbackgrounded
- Using Auth Guards and
unlockVaultOnLoad
- Not handling error code 0
- Missing an
await
on a vault method - Not using
try/catch
- Not testing common scenarios
- User canceled auth attempt
- Biometric prompt does not show
Calling new Vault() in a Constructor
A call to new Vault()
involves native code and will take some time to complete. During that time there is usually other constructors and startup logic (like an Auth Guard) that will also attempt to call the Vault (for example to get a token). This will result in an error:
_10export class VaultService {_10 constructor() {_10 this.vault = new Vault(this.config);_10 }_10}
The Fix
Instead, ensure Vault creation happens serially by creating the Vault using an APP_INITIALIZER
(for Angular) or by starting your app on a page that establishes the Vault before proceeding.
In your app.module.ts
:
_11const appInitFactory =_11 (vaultService: VaultService): (() => Promise<void>) =>_11 async () =>_11 await vaultService.init();_11_11@NgModule({_11..._11 providers: [_11 { provide: APP_INITIALIZER, useFactory: appInitFactory, deps: [VaultService], multi: true }],_11..._11})
In the above example the init
method will create the Vault (and perform any other setup needed). Using APP_INITIALIZER
will ensure that it is called before any other constructor is called or any routing logic is called.
Use Initialize
Instead of calling new Vault(config)
use a combination of new Vault()
and await Vault.initialize(config)
. The later will ensure that you can await
the completion of vault initialization before you make any calls to the vault. This is a common mistake where you get native errors on startup of an application.
Using Auth Guards and lockafterbackgrounded
The lockAfterBackgrounded property will ensure that the Vault is locked when the application is resumed from the background. However, your auth guard is likely trying to read from the Vault at the same time. This will result in an error.
The Fix
There are a few options:
- On resuming an app, you can route to a page which establishes its auth state.
- Remove
lockafterbackgrounded
and implement your own solution provide this feature. - In the auth guard,
await
a method which will delay for a second while the app is resuming
Option 1 would be preferred as its implementation is simple and has the added benefit of avoiding showing any private information on the page during the time the auth status is being established.
Using Auth Guards and unlockVaultOnLoad
The unlockVaultOnLoad property will ensure that an unlock attempt is made when the application is started. However, your auth guard or startup logic may also try to read from the Vault at the same time resulting in an error.
The Fix
There are a few options:
- On starting an app, you can route to a page which establishes its auth state.
- Remove
unlockVaultOnLoad
and implement your own solution provide this feature.
Option 2 is usually the easiest here as your auth guard will likely try to read the Vault and it will need to unlock it to do so. In most cases removing the unlockVaultOnLoad
property is all that is needed.
Not handling error code 0
Identity Vault interfaces with native libraries that are device specific and depending on the device there may be flaws in the devices implementation. As a fallback mechanism you should handle the Unknown Vault Error by clearing the Vault and routing the app to a login page. Without this logic your user could be stuck unable to login and would need to delete the app and reinstall.
Missing an await on a Vault method
All Vault methods are asynchronous and if you call 2 methods without awaiting then you will get unknown results. For example, calling setValue and getValue like:
_10 this.vault.setValue("foo", "bar");_10 await this.vault.getValue("foo"); // Will this return bar?
Instead always use await
:
_10 await this.vault.setValue("foo", "bar");_10 await this.vault.getValue("foo"); // This will return "bar" (maybe)
However, there is a problem in the above code, which leads to the next common mistake...
Not using try/catch
What happens if you need to get a value from a biometric Vault and the user cancels the prompt? or there is another error?
These are common scenarios that you need to cater for using try
, catch
and finally
.
_12try {_12 const value = await this.vault.getValue("foo");_12} catch (error) {_12 // Handle specific Vault Error_12 switch (e.code) {_12 case VaultErrorCodes.UserCanceledInteraction:_12 // maybe retry? or take them back to login_12 break;_12 default:_12 throw error;_12 }_12}
Not testing common scenarios
There are many ways a user can interact with biometrics that you may not have tested or considered. Try to handle these scenarios in your application when the user:
- Adds a fingerprint or face
- Removes a fingerprint or face
- Disables biometrics for your app
- Removes the lock PIN of their device (or adds one)
- Has an Android device with only weak Face Biometrics
- Backgrounds the app during the biometric prompt
- Resumes the app after backgrounding the app during the biometric prompt
- Attempts the biometric prompt too many times
- Restores the app on a new device from a backup
- Is locked out of biometrics because of too many failed attempts in another app
User canceled auth attempt
The error {code: 8, message: 'User canceled auth attempt.'}
on startup is often caused by 2 simultaneous attempts to use a biometric vault - one attempt is successful (showing the biometric prompt) and the other is cancelled (because there is already a biometric prompt being shown).
If you have unlockVaultOnLoad
set to true
then you need to make sure there is no other place in your code that is attempting to read or unlock the vault on startup of the application.
If you have lockAfterBackgrounded
set to a value then you need to make sure that when your application is resumed (opens after being backgrounded) that there is no other place in your code that is attempting to read or unlock the vault. This could include Auth Guard in Angular, http intercepts using the vault to access a token, etc.
Biometric Prompt does not show
If you are reading or unlocking a vault that is of type VaultType.DeviceSecurity
/ DeviceSecurityType.Biometrics
and the biometric prompt does not show, then it is likely that the vault on the device was previously saved as type VaultType.SecureStorage
. Remember that your vault configuration is only used if the device does not already have a vault. So, if you have a previously run your app with a different configuration then you may run into this situation.
To test if this is the case try changing the key
of your vault and see if it now works.
Known Device Issues
Due to the nature of device fragmentation on Android, there could be scenarios where not all security features are supported on certain devices. View the table below for a list of currently known issues:
Device | Known Issue |
---|---|
Samsung S9 / Samsung S9+ | Using a weak biometric (face) prompt with a system passcode is not supported on this device. The prompt will always show the system passcode prompt. |
Devices using Gesture Navigation | Hide screen on background will not work in the recent apps switcher due to a bug in the Android gesture navigation. |