Taxonomy Icon

IoT

A key goal of security in a cloud-based IoT application is to ensure that unauthorized users do not access sensitive, private data that comes from the devices. The application also needs to prevent the sending of unauthorized commands to the devices. This article, part 3 in a 3 part series, describes the different approaches to securing web and mobile applications that deal with IoT device data. Part 1 and Part 2 outlined detailed approaches for securing devices and securing communication between devices and the network.

Figure 1 illustrates the approach described in this article. Mobile and web clients have specific security mechanisms to ensure that the application is accessed only by authenticated users. The core of the application logic resides in the IBM Cloud-based back-end services. This application logic pulls the data from the IBM Watson IoT Platform (by using a secured Watson IoT Platform API), analyzes the data, and sends commands to control the devices.

Figure 1. Approaches to securing web and mobile apps that deal with IoT device data
Approaches to securing web and mobile apps that deal with IoT device data

This article assumes that you know how to develop and deploy IBM Cloud applications. If you are still new to IBM Cloud, you might consider working through the IBM Cloud Essentials Course. This article does not provide step-by-step instructions for how to develop the web or mobile applications; it only explains how to add security features to your web or mobile applications.

Securing IoT data that is received by an application

The IoT security demo server application that we created for this article subscribes to the Watson IoT event data and persists the received data in a Cloudant database. The IoT security demo server application encrypts some of the sensitive attributes of the stored data when it persists the data, such as the payload from the IoT devices. The payload is in JSON and is stored in an AES encrypted format in the Cloudant database. It is decrypted while it retrieves authorized users. This encryption and decryption is transparent to the user.

Secured access to the Watson IoT platform

When you add the Internet of Things Platform service in IBM Cloud, access credentials, like those credentials shown below, are generated for your application, and these credentials grant you access to the IBM Watson IoT Platform.


{ 
   "iotf‑service": 
        {
            "credentials": {
                "iotCredentialsIdentifier": "a2g6k44sl6r5",
                "mqtt_host": " yourorg.messaging.internetofthings.ibmcloud.com",
                "mqtt_u_port": 1883,
                "mqtt_s_port": 8883,
                "http_host": " yourorg.internetofthings.ibmcloud.com",
                "org": " yourorg",
                "apiKey": "a‑yourorg‑qcvoaslsqn",
                "apiToken": "weSS∗RHWxJIfa_nHLV"
            },
            "syslog_drain_url": null,
            "volume_mounts": [],
            "label": "iotf‑service",
            "provider": null,
            "plan": "iotf‑service‑asp",
            "name": "iotsecuritydemo‑iotf‑service",
            "tags": 
                "internet_of_things",
                "Internet of Things",
                "ibm_created",
                "ibm_dedicated_public",
                "lite"
                    }
    }



Subscribing to the events and status updates of the Watson IoT Platform is straightforward, as shown in this code snippet:


var IotfClient = require("ibmiotf").IotfApplication;
var iotConfig = {
        "org" : org,
        "id" : "iotsecuritydemo",
        "auth‑key" : apiKey,
        "auth‑token" : apiToken
    };
var iotfClient = new IotfClient(iotConfig);
iotfClient.connect();
iotfClient.on("error", function (err) {
    console.log("IoTF client error: "+JSON.stringify(err, null, 4));
});
iotfClient.on("connect", function () {
    // Subscribe to status from all devices
    iotfClient.subscribeToDeviceStatus();
    // Subscribe to all events from all devices
        iotfClient.subscribeToDeviceEvents();
    });
iotfClient.on("deviceEvent", function (deviceType, deviceId, eventType, format, payload) {
    // Handle events from devices
      console.log("Device event from:"+deviceType+", "+deviceId+" of event "+eventType);
      
      insertEventToDB(deviceType, deviceId, eventType, format, payload);
});
iotfClient.on("deviceStatus", function (deviceType, deviceId, payload, topic) {
    // Handle status updates from devices
      console.log("Device status from:"+deviceType+", "+deviceId);
      
      insertStatusToDB(deviceType, deviceId, payload, topic);
});


With this code, whenever an IoT event is received, the event and related payload is inserted into the Cloudant database.

Encrypting data in the Cloudant database

Data that is stored in an IBM Cloud Cloudant NoSQL DB instance is always encrypted, which is called At-rest Encryption. Learn more about Cloudant data protection and security in the IBM Cloud documentation.

However, you can implement a custom encryption approach to ensure that sensitive data is secured.

The encryption and decryption to store and retrieve data can be based on the industry standard AES-256 algorithm, as shown in the following code snippet:


var crypto = require('crypto');
var algorithm = 'aes‑256‑ctr';
var cryptoKey = 'your‑key';
  
function encrypt(text){
  var cipher = crypto.createCipher(algorithm, cryptoKey);
  var crypted = cipher.update(text,'utf8','hex');
  crypted += cipher.final('hex');
  return crypted;
}
function decrypt(text){
  var decipher = crypto.createDecipher(algorithm, cryptoKey);
  var dec = decipher.update(text,'hex','utf8');
  dec += decipher.final('utf8');
  return dec;
}


Encrypting data comes at a performance cost, especially when large volumes of data need to be handled, so this aspect must be factored in when you consider and implement the right approach.

Securely accessing data that is stored in your Cloudant database

You can access the IoT data that is stored in your Cloudant database directly by one of two methods:

  • Use a third-party application if you share the Cloudant API key,
  • Create your own custom APIs secured by a Bluemix security service.

Third-party access by using a Cloudant API Key

When you add the Cloudant DB service in IBM Cloud, access credentials like the following are generated for your program to be able to programmatically access the Cloudant database.



{
  "cloudantNoSQLDB": 
        {
            "credentials": {
                "username": "your‑username",
                "password": "your‑password",
                "host": "your‑hostname‑bluemix.cloudant.com",
                "port": 443,
                "url": "https:// your‑username:your‑password@your‑hostname‑bluemix.cloudant.com"
            },
            "syslog_drain_url": null,
            "volume_mounts": [],
            "label": "cloudantNoSQLDB",
            "provider": null,
            "plan": "Lite",
            "name": "iotsecuritydemo‑cloudantNoSQLDB",
            "tags": 
                "data_management",
                "ibm_created",
                "lite",
                "ibm_dedicated_public"
                    }
    }


You can use this user name and password to generate Cloudant API keys as follows:

POST _api/v2/api_keys

After the key is generated, an API key can be used in the same way as a normal user account, such as by granting read, write, or admin access permissions.

POST https://<username:password>.cloudant.com/_api/v2/api_keys

The response would be similar to this one:


"password": "YPNCaIX1sJRX5upaL3eqvTfi",
"ok": true,
"key": "blentfortedsionstrindigl"

The API key needs to be granted permission to access a specific database. Use a PUT request as shown below to access your database.

https://<username>.cloudant.com/_api/v2/db/<database>/_security),

Note that the API key can also be generated from the Cloudant dashboard as shown in the following figure.

Cloudant dashboard where you can generate an API key

API key/password pairs are treated like any other user account. You can pass an API key to a call to share a database with it and assign permissions to it. You use API key/password pairs for any situation where user accounts are not appropriate, such as when you have credentials in your source code, and you need to programmatically create several accounts with different permissions.

The API response contains the following properties:

  • ok – Returned if the request was successful.
  • error – Returned if the request was not successful, containing an error code.
  • key – Returns the API key.
  • password – Returns the API password.

Third-party access by using custom APIs

By providing direct access to the Cloudant database, you make the third-party integration brittle and expensive to maintain. A recommended architecture is to provide an encapsulation layer on top of the database by using custom APIs. Custom APIs hide the database-specific details and return the device information from a specific interface contract.

The first custom API in our IoT security demo server application lets you access the list of devices by using this http request:

http://<app_route>/iotf/devices

For example, to access the list of devices whose data is stored in the Cloudant database in our IoT security demo server application, use this request:

https://iotsecuritydemo2.mybluemix.net/iotf/devices

The second custom API in our IoT security demo server application lets you to access the data of a particular device by using this http request:

http://<app_route>/iotf/devices/<device>

For example, to view the last 50 events with their payload for the device ‘j.patra’ in our IoT security demo server application, use this request:

https://iotsecuritydemo2.mybluemix.net/iotf/devices/j.patra

Lastly, you can request a specific number of events by specifying an additional parameter on your request:

http://<app_route>/iotf/devices/<device>?count=<count>

For example, to view the last five events with their payload for the device ‘j.patra’ in our IoT security demo server application, use this request:

https://iotsecuritydemo2.mybluemix.net/iotf/devices/j.patra?count=5

Implementing a custom authorization provider

The user and userGroup objects are custom authentication and authorization objects that provide role-based secure access to data stored in a Cloudant database. This article uses a custom authentication provider for using these user and userGroup objects.

When you use custom APIs, you might want to have greater control over user authorization for accessing the device data and also for sending commands to the device. To gain this control, create a set of custom documents in Cloudant for user and userGroup.

This set of custom documents in Cloudant is used for role-based authorization for the custom authentication provider that is implemented as part of this IoT security demo server application.

The following is an example of a custom user document:


{
  "_id": "joypatra@gmail.com",
  "recordType": "user",
  "userName": "joypatra",
  "password": "encrypted‑password",
  "displayName": "Joy Patra",
  "attributes": {
    "Language": "English",
    "Country": "India",
    "emailAddress": "joypatra@gmail.com",
    "userGroup": "groupAdmin"
}  

The following is an example of a custom userGroup document:


{
  "_id": "groupAdmin",
  "recordType": "userGroup",
  "groupName": "groupAdmin",
  "roleAdmin": "true",
  "roleReader": "true",
  "roleWriter": "true",
  "displayName": "User authorization administrator group",
  "devices": "j.patra", "a.gantait", "a.mukherjee"}  

The custom authorization provider that is developed in this article uses the above user and userGroup documents from the Cloudant database.

Securing the custom APIs with IBM App ID

After the data is secured and exposed through custom APIs, the next step is to secure these APIs by using authentication and authorization. The web and mobile applications will have to provide the necessary authentication credentials to invoke the APIs and will only be able to access the data of the devices that they are authorized to access. To secure the custom APIs, this article uses the IBM App ID service on IBM Cloud.

The web applications in this article are developed using Node.js. To protect a web application, the authentication handler method in Node.js is augmented with a passport authentication middleware. The passport authentication middleware is a Node.js passport instance that has been initialized by using IBM App ID with the Web App Strategy object so that the authentication request will be intercepted and handled by the IBM App ID.

Web applications that provide access to IoT device data need to be secured by a user name/password combination for the application users. In our web application, a back-end user registry (such as IBM Cloud directory) is used to store all user information.

The App ID service supports three identity sources for users’ credentials:

Before an application developer can embed App ID capability into an application, the administrator must create service instances and add identity sources. Perform the following steps to add App IDcapabilities (by using IBM Cloud directory) to the web application.

  1. Set up the App ID service in IBM Cloud, and create a new IBM cloud directory.
  2. Add users into the cloud directory. See the following figure:

Implementing the API Security

The following code snippet from the IoT security demo application demonstrates how to use IBM App ID to protect the custom API endpoint (/iotf/devices).

The passport authentication middleware uses APIStrategy to validate the incoming requests. It looks for an Authorization field in the request http header. For App ID, the authorization header consists of three different tokens that are separated by white space: the keyword ‘Bearer’, the Access token, and optionally the Identity token.


const passport = require('passport');
const APIStrategy = require("bluemix‑appid").APIStrategy;

app.use(passport.initialize());

passport.use(new APIStrategy());

// Declare the API you want to protect
app.get(iotRouteBase + '/devices',
passport.authenticate(APIStrategy.STRATEGY_NAME, 
{session: true}),
function(req, res) {
        if(error) {
            res.status(400).json(error);
        }
        else {
            res.status(200).json(userGroup.devices);
            }
}
);



This protected API can be accessed from client application as REST API with proper authentication header. Subsequent sections discuss how to implement web clients and mobile clients that will invoke these APIs.

Securing IoT client applications

To secure IoT client applications, you can use unified authentication approach for web and mobile applications. The IBM Cloud platform provides security at the platform, data, and application levels.

Figure 3 illustrates the unified authentication and authorization approach based on App ID service. With this approach of unified authentication, both the mobile application and the web application use the same App ID-based authentication provider and a custom Cloudant-based authorization provider to protect the device data and to limit who can send commands to the device.

Figure 2. Same authentication, same secured application

Securing a web application

In this section we’ll see how our secured custom IoT APIs can be accessed from web applications based on the authentication and authorization associated with logged in user profile.

The steps are:

  1. A client program uses the App ID client-side SDK to initialize the session with the IBM App ID instance by providing the App ID tenant ID and client ID, along with the secret key, for the specific oauthServerUrl of the App ID instance.
  2. The client program initiates the login session with App ID using the LOGIN_URL.
  3. App ID presents a login page to the user and on submit, validates the credentials and returns the access token and identity token.
  4. The client program calls a protected endpoint on the server (/iotf/devices, in our security demo server application). While doing so, it sets the authorization header in the request with the access token and optionally, the identity token.
  5. The App ID server-side SDK, set up as a passport middleware on the protected endpoint, validates whether the provided access token and optionally, identity token, are valid.
  6. If valid, the App ID server-side SDK allows the endpoint handler to be called. Otherwise, it returns a response with HTTP 401 “Unauthorized”.

Implementing the Web Application Authentication Security

The following code snippet from the IoT security demo web application demonstrates how to use IBM App ID to protect login to the web application.


const WebAppStrategy = require("bluemix‑appid").WebAppStrategy;

// Configure express application to use passportjs
app.use(passport.initialize());
app.use(passport.session());

const LOGIN_URL = "/ibm/bluemix/appid/login";
const CALLBACK_URL = "/ibm/bluemix/appid/callback";
const LANDING_PAGE_URL = "/IOT_sample_app.html";

// Configure passportjs to use WebAppStrategy
passport.use(new WebAppStrategy({
    tenantId: "∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗",
    clientId: "∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗",
    secret: "∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗",
    oauthServerUrl: "∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗∗",
    redirectUri: "http://localhost:1234" + CALLBACK_URL
}));

// Explicit login endpoint. Will always redirect browser to login widget.
// If forceLogin is set to false redirect to login widget will not occur 
// for already authenticated users.
app.get(LOGIN_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME, {
    successRedirect: LANDING_PAGE_URL,
    forceLogin: true
}));

// Callback to finish the authorization process. Will retrieve access and 
// identity tokens from App ID service.
app.get(CALLBACK_URL, passport.authenticate(WebAppStrategy.STRATEGY_NAME));



After the user has successfully logged in, we will show a page to the user that fetches the data of the devices that the user is authorized to view. For this purpose, we invoke two protected custom APIs from the our custom Node.js server above –

  1. /iotf/devices to get the list of authorized devices
  2. /iotf/devices/:deviceid?count=n to get the last n device data (also called “deviceEvent” in IBM IoT terminology).

To access the protected APIs, we need to set the authorization token properly in the request header. The following code snippet in the web application side (client side code) demonstrates the same.


// Protected area. If current user is not authenticated ‑ redirect to the 
// login widget.
// In case user is authenticated ‑ a page with current user information
// will be returned.
app.get("/protected", passport.authenticate(WebAppStrategy.STRATEGY_NAME), 
function(req, res){
    var authContext = req.session[WebAppStrategy.AUTH_CONTEXT];
    var accessToken = authContext.accessToken;
    var identityToken = authContext.identityToken;

    console.log("AUTH_CONTEXT: " + JSON.stringify(authContext), null, '    ');

    var options = {
        url: 'http://iotsecuritydemo2.mybluemix.net/iotf/devices,
        headers: {
            'Authorization': 'Bearer '+accessToken+' '+identityToken
        }};

    request.get(options, function (error, response, body) {
        if(error) {
            console.log(error);
            res.send(error);
        }
        else {
                console.log("response ‑ " + response.code);
                console.log("body ‑ " + body);    
                 res.send(body);
        }
    });



Securing a mobile application

The IBM App ID service can also be used to provide services to ensure security of mobile applications. The flow works exactly the same way as that of securing a web application.

  1. A mobile device uses the App ID client-side SDK to initialize the session with the specific instance of your App ID by providing the App ID tenant ID and region ID.
  2. The mobile device initiates the login session with App ID specifying whether this is an anonymous login or credentials-based login.
  3. If this is an anonymous login, App ID immediately returns appropriate access token and identity token. If this is credentials-based login, App ID presents a login page to the user and on submit, validates the credentials and returns the access token and identity token.
  4. The mobile device calls a protected endpoint on the mobile app back end (/iotf/devices, in our security demo server application) using the Request class provided by the App ID client-side SDK.
  5. The App ID client-side SDK adds the cached authorization header to the request and invokes the protected Api.on the endpoint and validates whether the provided access token and optionally, identity token, are valid.
  6. If valid, the App ID server-side SDK allows the endpoint handler to be called. Otherwise, it returns a response with HTTP 401 “Unauthorized”.
  7. The authentication flow considers whether the App ID service has been configured with a Facebook, Google, or IBM Cloud Directory. It returns the result to the App ID server-side SDK.
  8. When the authentication flow succeeds for App ID server-side SDK, it invokes the endpoint handler of that protected endpoint (/iotf/devices, in our security demo server application).

Initializing the App ID Client SDK (For Android)

Following code snippet shows you how to initialize your App ID client SDK.

You can find your Tenand ID in the Service Credentials section in your IBM Cloud App ID dashboard.


// Initialize IBM Cloud AppID service SDK with tenant ID and region of the 
// AppID service to connect to
// You can find your tenant ID and region in the Service Credentials section 
//at left hand side of your AppID service dashboard
        appId = AppID.getInstance();

        appId.initialize(
                getApplicationContext(),
                DeviceIoTDemoApplication.IBMCLOUDAPPID_TENANT_ID,
                DeviceIoTDemoApplication.IBMCLOUDAPPID_REGION_ID);

        bmsClient = BMSClient.getInstance();

        bmsClient.initialize(
                getApplicationContext(),
                DeviceIoTDemoApplication.BMSCLIENT_REGION_ID
        );


After the SDK is initialized, determine if the login is going to be anonymous or for specific credentials. To initiate login, use this code snippet:


public void startLoginActivity(View view) {

    LoginWidget loginWidget = appId.getLoginWidget();
    // Using persisted access token is optional
    final String storedAccessToken = tokensPersistenceManager.getStoredAccessToken();

    AppIdAuthorizationListener appIdAuthorizationListener =
            new AppIdAuthorizationListener(
                    this,
                    appIDAuthorizationManager,
                    false);

    loginWidget.launch(this, appIdAuthorizationListener);
}



The widget will make a call back to the appIdAuthorizationListener on success or failure of the authentication process.

Making a request to the secured APIs

On successful , you can make requests to the protected APIs:

http://<app-route>/iotf/devices

To make a request to the above endpoint from a mobile application, use the Request class provided in the App ID client SDK for Android:


new Request(
    SERVER_HOSTNAME + "/iotf/devices/",     
    Request.GET).send(
        DeviceIoTDemoApplication.get(), 
        new ResponseListener() {
            @Override
            public void onSuccess(Response response) {
                Log.i("onSuccess(): Authorized devices are "+
                    response.getResponseText());
            }

            @Override
            public void onFailure(Response response, Throwable t, JSONObject extendedInfo) {
                final String errorMessage =
                    (null != response ? response.getResponseText() : "") + "\n" +
                    (null != t ? t.toString() : "") + "\n" +
                    (null != extendedInfo ? extendedInfo.toString() : "") + "\n";

                Log.e(TAG, errorMessage);
            }
        }
    );

The authorized access token and identity token received from App ID service are passed by the Request class as request headers when invoking the restricted server API on our custom Node.js server described above. The server uses APIStrategy to validate the tokens against the App ID service before executing the business logic.

Mobile Android client example

The mobile Android client example app that we developed for this article, starts with asking the user to log in. The user may proceed anonymously if so desired.

For the user wishing to log in, the mobile app obtains a LoginWidget from AppID instance and hands over control to that login widget. The login widget controls the web-based login page that collects the user name and password, ensuring that the mobile app does not direct access to these credentials.

The Android app implements a custom authorization listener, AppIdAuthorizationListener. This class handles the callback notifications from the login widget on authorization success, failure, and similar events. On success, this listener implements a custom method, getAuthorizedDevices, to invoke the restricted /iotf/devices API on our custom Node.js server.

3 screen shots of Android phone screen shot

On successful authentication, the devices API returns the list of devices that this user is authorized to view. Each of these devices is displayed in a separate tab in the app UI. Each tab displays the last event for the corresponding device, obtained by invoking another restricted API on our custom Node.js server. The time stamp and sensor payload data for the event are displayed.


public class AppIdAuthorizationListener implements AuthorizationListener {

    private static final String TAG = AppIdAuthorizationListener.class.getSimpleName();

    private NoticeHelper noticeHelper;
    private TokensPersistenceManager tokensPersistenceManager;
    private boolean isAnonymous;
    private Activity activity;

    public AppIdAuthorizationListener(Activity activity, AppIDAuthorizationManager authorizationManager, boolean isAnonymous) {
        tokensPersistenceManager = new TokensPersistenceManager(activity, authorizationManager);
        noticeHelper = new NoticeHelper(activity, authorizationManager, tokensPersistenceManager);
        this.isAnonymous = isAnonymous;
        this.activity = activity;
    }

    @Override
    public void onAuthorizationFailure(AuthorizationException exception) {
        Log.e(logTag("onAuthorizationFailure"),"Authorization failed", exception);
    }

    @Override
    public void onAuthorizationCanceled() {
        Log.w(logTag("onAuthorizationCanceled"),"Authorization canceled");
    }

    @Override
    public void onAuthorizationSuccess(AccessToken accessToken, IdentityToken identityToken) {
        Log.i(logTag("onAuthorizationSuccess"),"Authorization succeeded");
        if (accessToken == null && identityToken == null) {
            Log.i(logTag("onAuthorizationSuccess"),"Both access and identity tokens are null.");

        } else {
            Log.d(TAG, "Access Token: " + (new Gson()).toJson(accessToken));
            Log.d(TAG, "Identity Token: " + (new Gson()).toJson(identityToken));

            // Execute your success logic here
        }
    }

    private String logTag(String methodName){
        return this.getClass().getCanonicalName() + "." + methodName;
    }
}

For the purpose of this article, the payload data can be simulated with our device simulator program.

Scanning and monitoring your apps for security vulnerabilities

The IBM Cloud platform also provides services that help you scan or monitor your apps. For example, the IBM Application Security on Cloud service can ensure that there are no security loopholes in the application. Developers can use these services to scan web and mobile applications for vulnerabilities before deployment, identify security issues, and implement the recommended fixes. Also, the App ID service has a dashboard that provides monitoring and analytics on the usage of the mobile application and on the security events in the device, such as authentication successes or failures.

Conclusion

Security must be one of the key focus areas for applications that analyze sensitive data that comes from IoT devices and the applications that control the functions on those IoT devices. In this article, we focused on securing the IoT application layer that runs on the IBM Cloud platform. You learned how you can use IBM Cloud security services to implement common authentication and authorization of users across web and mobile applications, and how sensitive data can be securely stored in a Cloudant database.

Acknowledgments

The authors thank Subrata Saha, IBM Cloud Solution Architect, and Beery Holstein, IBM Cloud App ID Offering Manager, for their significant reviews and suggestions for this article.