Working with Ionic Native - Using Secure Storage

Working with Ionic Native - Using Secure Storage

This post is more than 2 years old.

Today I'm reviewing another Ionic Native feature, the Secure Storage wrapper. As the plugin docs explain, this is a plugin that allows for encrypted storage of sensitive data. It follows an API similar to that of WebStorage, with a few differences.

First, the plugin lets you define a 'bucket' for your data. So your app could have multiple different sets of data that are separated from each other. (The plugin refers to it as 'namespaced storage', but buckets just made more sense to me.)

Second, you can't get all the keys like you can with WebStorage. That's probably related to the whole 'secure' thing, but in general, I can't imagine needing that functionality in a real application. You could also use a key that represents a list of keys.

Secure Storage is a key/value storage system, and like WebStorage, you can only store strings, but you can use JSON to get around that.

With that out of the way - let's build a simple demo. I created a simple two page app to represent a login screen and main page.

Let's start by looking at the first page, our login screen.


<ion-header>
	<ion-navbar>
		<ion-title>
			Secure Storage Example
		</ion-title>
	</ion-navbar>
</ion-header>

<ion-content padding>

	<ion-list>

		<ion-item>
			<ion-label fixed>Username</ion-label>
			<ion-input type="text" [(ngModel)]="username"></ion-input>
		</ion-item>

		<ion-item>
			<ion-label fixed>Password</ion-label>
			<ion-input type="password" [(ngModel)]="password"></ion-input>
		</ion-item>

	</ion-list>

	<button primary block (click)="login()">Login</button>

</ion-content>

Now we'll look at the code behind this.


import {Component} from '@angular/core';
import {NavController} from 'ionic-angular';
import {LoginProvider} from '../../providers/login-provider/login-provider';
import { Dialogs } from 'ionic-native';
import {MainPage} from '../main-page/main-page';

@Component({
  templateUrl: 'build/pages/home/home.html',
  providers:[LoginProvider]
})
export class HomePage {

  public username:string;
  public password:string;
  private loginService:LoginProvider;
  
  constructor(public navCtrl: NavController) {
    this.loginService = new LoginProvider();
  }

  login() {

    console.log('login',this.username,this.password);
    this.loginService.login(this.username,this.password).subscribe((res) => {

      console.log(res);

      if(res.success) {
        
        //thx mike for hack to remove back btn
        this.navCtrl.setRoot(MainPage, null, {
          animate: true
        });

      } else {
        Dialogs.alert("Bad login. Use 'password' for password.","Bad Login");
      }

    });

  }

}

All we've got here is a login handler that calls a provider to verify the credentials. There's one interesting part - the setRoot call you see there is used instead of navCtrl.push as it lets you avoid having a back button on the next view. Finally, let's look at the provider, even though it's just a static system.


import { Injectable } from '@angular/core';
import 'rxjs/add/operator/map';
import {Observable} from 'rxjs';
//import 'rxjs/Observable/from';

@Injectable()
export class LoginProvider {

  constructor() {}

  public login(username:string,password:string) {
    let data = {success:1};

    if(password !== 'password') data.success = 0;

    return Observable.from([data]);

  }
}

Basically - any login with "password" as the password will be a succesful login. That's some high quality security there!

You can view this version of the code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/securestorage_ionicnative/app_v1

Ok, so let's kick it up a notch. My plan with Secure Storage is to modify the code as such:

  • When you login, JSON encode the username and password and store it as one value.
  • When the app launches, first create the 'bucket' for the system, which will only actually create it one time.
  • See if pre-existing data exists, and if so, get it, decode it, put the values in the form, and automatically submit the form.

Since I'm using a plugin, I know now that my app has to wait for Cordova's deviceReady to fire. I've got a login button in my view that I can disable until that happens. So one small change to the view is to show/hide it based on a value I'll use based on the ready status. Here is the new login button:


<button primary block (click)="login()" *ngIf="readyToLogin">Login</button>

Now let's look at the updated script. I'll share the entire update and then I'll point out the updates.


import {Component} from '@angular/core';
import {NavController,Platform} from 'ionic-angular';
import {LoginProvider} from '../../providers/login-provider/login-provider';
import { Dialogs } from 'ionic-native';
import {MainPage} from '../main-page/main-page';
import {SecureStorage} from 'ionic-native';

@Component({
  templateUrl: 'build/pages/home/home.html',
  providers:[LoginProvider]
})
export class HomePage {

  public username:string;
  public password:string;
  private loginService:LoginProvider;
  public readyToLogin:boolean;
  private secureStorage:SecureStorage;

  constructor(public navCtrl: NavController, platform:Platform ) {
    this.loginService = new LoginProvider();
    this.readyToLogin = false;

    platform.ready().then(() => {

      this.secureStorage = new SecureStorage();
      this.secureStorage.create('demoapp').then(
        () => {
          console.log('Storage is ready!');

          this.secureStorage.get('loginInfo')
          .then(
            data => {
              console.log('data was '+data);
              let {u,p} = JSON.parse(data);
              this.username = u;
              this.password = p;
              this.login();
            },
            error => {
              // do nothing - it just means it doesn't exist
            }
          );

          this.readyToLogin = true;
        },
        error => console.log(error)
      );

    });

  }

  login() {

    this.loginService.login(this.username,this.password).subscribe((res) => {

      console.log(res);

      if(res.success) {

        //securely store
        this.secureStorage.set('loginInfo', JSON.stringify({u:this.username, p:this.password}))
        .then(
        data => {
          console.log('stored info');
        },
        error => console.log(error)
        );

        //thx mike for hack to remove back btn
        this.navCtrl.setRoot(MainPage, null, {
          animate: true
        });

      } else {
        Dialogs.alert('Bad login. Use \'password\' for password.','Bad Login','Ok');
        this.secureStorage.remove('loginInfo');
      }

    });

  }

}

So let's start at the top. Don't forget that your Ionic views can fire before the Cordova deviceReady event has fired. I still wish there was a simple little flag I could give to my Ionic code to say "Don't do anything until then", but until then, you can use the Platform class and the ready event.

I create my Secure Storage bucket "demoapp", and in the success handler, I immediately look for the key loginInfo. Obviously on the first run it won't exist, but the bucket will be created. On the second (and onward) run, the bucket will already exist, and the data may or may not exist.

If it does - I decode it, set the values, and login. That last operation was optional of course. Maybe your app will just default the values. There's a few different ways of handling this.

Finally, in the login handler I both set the value (after encoding it) and clear it based on the result of the login attempt. Notice that both calls are asynchronous, but I really don't need to wait for them, right? Therefore I treat them both as 'fire and forget' calls.

They could, of course, error. And there is a very good reason why it could. In the docs, they mention that this plugin works just fine on iOS, but on Android it will only work if the user has a secure pin setup. That's unfortunate, but the plugin actually provides an additional API to bring up that setting for Android users, which is pretty cool I think.

You can find the code for this version here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/securestorage_ionicnative/app

How about a few final thoughts?

  • While you can store a username and password, and the docs even say this, I still feel a bit wonky about doing so. I'd maybe consider storing a token instead that could be used to automatically login just that user. And it could have an automatic timeout of some sort.
  • If you read the blog post, Ionic Native: Enabling Login with Touch ID for iOS, then this plugin would be a great addition to that example.
  • A bit off topic, but I would normally have added a "loading" indicator on login to let the user know what's going on. And of course, Ionic has one. I was lazy though and since my login provider was instantaneous, I didn't feel like it was crucial.

As always - let me know what you think in the comments below.

p.s. I'm loving Ionic 2, and Angular 2, and TypeScript, but wow, it is still a struggle. For this demo, I'd say 80% of my time was spent just building the first version. I'm still struggling with Observables, still struggling with Angular 2 syntax. Heck, it took me a few minutes to even just bind the form fields to values. That being said, and I know I've said this before, I still like the code in v2 more than my Angular 1 code.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Quentin Ng posted on 1/3/2017 at 6:14 AM

have you had any issues running this with IOS?
I'm having issues when I try and set, get the created storage it errors.

The only error I get back is the following:
{"line":71,"column":45,"sourceURL":"http://192.168.1.3:8100/plugins/cordova-plugin-secure-storage/www/securestorage.js"}

Comment 2 (In reply to #1) by Raymond Camden posted on 1/3/2017 at 1:53 PM

You don't see anything else in the console? You said it errors, so therefore the error must show up.

Comment 3 (In reply to #2) by Quentin Ng posted on 1/3/2017 at 2:19 PM

Error message is "Failure in SecureStorage.set() - Refer to SecBase.h for description"
https://github.com/Crypho/c... I think it's related to emulator issue. I'll have to try and test out on a real device and see if I reproduce.

Comment 4 (In reply to #3) by Raymond Camden posted on 1/3/2017 at 2:39 PM

Ok - and if you can - you should file that w/ the plugin repo.

Comment 5 (In reply to #4) by Quentin Ng posted on 1/4/2017 at 10:25 PM

Yep confirmed it's a bug in the emulator. Was able to progress when deployed to a real device.

Comment 6 by Vishnu Rapposol posted on 1/26/2017 at 5:07 AM

It doesn't works on some devices like HTC , Samsung. But working on Asus. What I do?
my code is
this.secureStorage.create('storeroom')
.then(
()=>{
this.secureStorage.set('userId',this.username)
.then(
data=>{
loading.dismiss();

},
error=>{
loading.dismiss();
console.log('Your device issue');
}
)

},
error=>{
loading.dismiss();
console.log(error.error);
}
);

Comment 7 (In reply to #6) by Raymond Camden posted on 1/26/2017 at 12:13 PM

Report it as a bug to the plugin author.

Comment 8 by Kingsley Simon posted on 5/31/2017 at 12:45 AM

Question what is the main difference between ionic native storage and ionic secure storage?

Comment 9 (In reply to #8) by Raymond Camden posted on 5/31/2017 at 8:06 PM

There is no Ionic Native Storage. Ionic Native is a group name for plugins that have been 'wrapped' and made more 'Angular friendly'.

Comment 10 by Vaibhav Sharma posted on 6/2/2017 at 7:01 PM

Can we see encrypted data in console ?

Comment 11 (In reply to #10) by Raymond Camden posted on 6/2/2017 at 7:58 PM

Did you try?

Comment 12 (In reply to #11) by Vaibhav Sharma posted on 6/2/2017 at 8:42 PM

No, I can't see because when I try to get data using this.secureStorage.get(.......) then it returns me decrypted data.
So how we can see encrypted data.

Comment 13 (In reply to #12) by Raymond Camden posted on 6/2/2017 at 9:13 PM

I'd ask the plugin author - I believe it is stored in a platform specific manner. But yea - check on the plugin site.

Comment 14 (In reply to #13) by Raymond Camden posted on 6/2/2017 at 9:13 PM

I just looked and the plugin docs talk about this.

Comment 15 (In reply to #14) by Vaibhav Sharma posted on 6/2/2017 at 9:31 PM

Thanks.

Comment 16 by Nicolas posted on 8/15/2017 at 8:55 AM

Thanks for the nice blog

ive got the problem, when try to login, then ive got a runtime error
with Cannot read proberty 'set' of undefindet

the same happens if i typ the wrong pass, but then i got insteadt of 'set' 'remove'

TypeError: Cannot read property 'set' of undefined
at SafeSubscriber._next (http://localhost:8100/build/main.js:146:30)
at SafeSubscriber.__tryOrSetError (http://localhost:8100/build/vendor.js:15697:16)
at SafeSubscriber.next (http://localhost:8100/build/vendor.js:15637:27)
at Subscriber._next (http://localhost:8100/build/vendor.js:15575:26)
at Subscriber.next (http://localhost:8100/build/vendor.js:15539:18)
at ArrayObservable._subscribe (http://localhost:8100/build/vendor.js:28853:28)
at ArrayObservable.Observable._trySubscribe (http://localhost:8100/build/vendor.js:211:25)
at ArrayObservable.Observable.subscribe (http://localhost:8100/build/vendor.js:199:27)
at HomePage.webpackJsonp.227.HomePage.login (http://localhost:8100/build/main.js:143:63)
at Object.eval [as handleEvent] (ng:///AppModule/HomePage.ngfactory.js:517:24)
Ionic Framework: 3.6.0
Ionic App Scripts: 2.1.3
Angular Core: 4.1.3
Angular Compiler CLI: 4.1.3
Node: 6.11.2
OS Platform: Windows 10
Navigator Platform: Win32
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36

Comment 17 (In reply to #16) by Raymond Camden posted on 8/15/2017 at 4:23 PM

Not sure I understand. You type the wrong password for my login service?

Comment 18 (In reply to #17) by Nicolas posted on 8/16/2017 at 5:37 AM

i mean, in your example, you coded the password to 'password'

so if i write anything other then 'passwort' i get the error with 'remove'

Comment 19 (In reply to #18) by Raymond Camden posted on 8/16/2017 at 11:21 AM

What remove call though? If you put in a bad password it should keep you on the login page, right?

Comment 20 (In reply to #19) by Raymond Camden posted on 8/16/2017 at 11:22 AM

Oh wait - I think I see the issue. My logic assumes that you had already logged in once. So this is a bug in my code. It should say (in pseudo-code)

if login failed, AND if I had stored info before, then remove it.

Comment 21 by Ranjana posted on 10/12/2017 at 7:53 PM

how to create storage instance just once?

Comment 22 (In reply to #21) by Raymond Camden posted on 10/12/2017 at 9:48 PM

I believe (stress, BELIEVE) the create API only makes it once and after that it's just opened.

Comment 23 (In reply to #22) by Andrew Radulescu posted on 1/30/2018 at 10:22 AM

Of course, but the documentation is too light and doesn't specify it

Comment 24 (In reply to #23) by Raymond Camden posted on 1/30/2018 at 1:45 PM

Then I'd file a bug report so they know the docs are lacking in this regard.

Comment 25 by Alex Mayer posted on 7/18/2018 at 11:08 AM

Hi, thanks for the good summary!

Do you have any idea how to properly unit test this.
Is there a way I can run automated tests to make sure my credentials are saved securely?

Thanks in advance :)

Comment 26 (In reply to #25) by Raymond Camden posted on 7/18/2018 at 3:40 PM

Sorry - I haven't used this in a while.

Comment 27 (In reply to #7) by Dominique Francois posted on 8/20/2019 at 7:10 AM

what solve this issue, same issue

Comment 28 (In reply to #27) by Raymond Camden posted on 8/21/2019 at 2:17 PM

I'd offer the same advice, to report it to the plugin.