Build

Implementing OAuth 2.0 Google Sign-in: DIY Secure Smart Lock

Namratha Subramanya on Jun 13, 2018
Implementing OAuth 2.0 Google Sign-in: DIY Secure Smart Lock

In Part One, we built our own IoT smart lock with secure access management and learned how to implement a secure smart lock system using Access Manager. In this part, let's implement OAuth 2.0 using Google sign-in, a secure authentication system. This reduces the burden of login for the users of your app, by enabling them to sign-in with their Google account.

A full GitHub repo for the project is available here.

What is OAuth?

OAuth is an open standard for authorization of which any developer can implement it. OAuth provides access to your personal data without exposing user’s password. OAuth 2.0 is the latest and most conventional version of OAuth. The key thing to note is that OAuth 2.0 is a completely new protocol and it is not backward compatible with the previous versions. Google APIs use the OAuth 2.0 protocol for authentication and authorization.

Getting Started

Before you can integrate Google sign-in into your app, you need to create Client ID. Client ID is a unique string representing the registration information provided by Google. You can configure your project here.

In the Authorized JavaScript origins field, enter the origin for your app. I am running this demo on Apache server and hence I have set my origin to http://localhost:8080/

You also need to include the following line into your HTML page:

<script src="https://apis.google.com/js/platform.js" async defer></script>

Go to Developers Console and click on Enable APIs and Services and search for “Google+ API”. Enable this API and copy your Client ID. This goes into the below line:

<meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com">

You could use the following lines of code in your HTML page to create the sign-in button.

<div id="gSignInWrapper"> 
    <div id="customBtn" class="customGPlusSignIn">
      <span class="icon"></span>
    </div>
</div>
Getting Started

You can customize the button according to Google standard by checking this link.

Google Authentication

OAuth

The first step is to initialize your client ID in JavaScript code. It can be done using the code below:

var startApp = function() {
    gapi.load('auth2', function() {
      auth2 = gapi.auth2.init ({
        client_id: 'YOU_CLIENT_ID.apps.googleusercontent.com',
        cookiepolicy: 'single_host_origin',
      });
      attachSignin(document.getElementById('customBtn'));
    });
};

You must send the ID token to your server with an HTTPS POST request. After you receive the ID token, you must verify the integrity of the token. To validate the ID token using tokeninfo endpoint, you need to make a GET or POST request to the following:

https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123

function attachSignin(element) { 
   auth2.attachClickHandler(element, {}, function(googleUser) { 
     document.getElementById('name').innerText = "Signed in: " + googleUser.getBasicProfile().getName()+" "+googleUser.getBasicProfile().getId()+" "+googleUser.getAuthResponse().id_token; 
      var id_token = googleUser.getAuthResponse().id_token; 
      var xhr = new XMLHttpRequest(); 
      xhr.open('POST', 'https://www.googleapis.com/oauth2/v3/tokeninfo'); 
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 
      xhr.onload = function() { 
         document.getElementById('name').innerText = googleUser.getBasicProfile().getName(); 
      }
      xhr.send('id_token=' + id_token); 
   }, function(error) { 
      alert(JSON.stringify(error, undefined, 2)); 
   });
}

Functions

Functions is a Function-as-a-Service platform that allows you to program the PubNub Data Stream Network. Functions enables users to quickly and easily spin up micro-services in the PubNub Data Stream Network (DSN) without the requirement of any additional supporting infrastructure. You can call these functions on request, after presence, after publish or before publish according to your convenience.

In this demo, we are going to use Functions with our login and signup functions to create new accounts, verify if the accounts exist and check their passwords so that the device can get access to operate the smart lock.

Sign Up Function

In the signup function, we check if the user already has a smart lock account. If the user doesn't have one, we create a smart lock account by saving the user credentials into the kvstore database which can be entered by the user every time they log in.

The kvstore is a persistent key-value store that acts as a database to the Functions. You can read more about kvstore in this link.

export default (request, response) => {
    const pubnub = require('pubnub');
    const db = require('kvstore');
    const xhr = require('xhr');
    let headersObject = request.headers;
    let paramsObject = request.params;
    let methodString = request.method;
    let bodyString = request.body;
   
    response.status = 200;  
    response.headers['X-Custom-Header'] = 'CustomHeaderValue';
    var id = paramsObject.id.toString();
    var password = paramsObject.password.toString();
    return db.get(id).then((googleid) => {
        if (!googleid) {
            googleid = id;
            db.set(googleid, password)
            .catch((err) => {
                console.log("An error occured saving the account details.", err);
            });
            console.log("Smart Lock Account details saved!");
            return response.send("SAVED");
        } else {
            console.log("Smart Lock Account exists:", id);
            return response.send("ACCOUNT EXISTS");
         }
    });
};

Login Function

In the login function, we check if the user has an account and if they do, we check if the password entered is matching with the password stored in kvstore.

export default (request, response) => {
    const pubnub = require('pubnub');
    const db = require('kvstore');
    const xhr = require('xhr');
    let headersObject = request.headers;
    let paramsObject = request.params;
    let methodString = request.method;
    let bodyString = request.body;
    
    response.status = 200;
    response.headers['X-Custom-Header'] = 'CustomHeaderValue';
    var id = paramsObject.id.toString();
    var password = paramsObject.password.toString();
    return db.get(id).then((googleid) => {
        if (!googleid) {
            console.log("Smart Lock Account does not exist. Access Denied!");
            return response.send("ACCOUNT DOES NOT EXIST");
        } else if(googleid == password) {
            console.log("Smart Lock Account exists:", id);
            return response.send("ACCOUNT EXISTS");
        } else
            return response.send("ERROR");
    });
};

Google Signup

Once your Function for signup is ready, you can copy the URL and do POST request by passing the ID and password as parameters.

var xhr1 = new XMLHttpRequest(); 
var url ='COPY_URL'; 
xhr1.open('POST', url); 
xhr1.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 
xhr1.onload = function() { 
   document.getElementById('name').innerText = xhr1.responseText; 
}; 
xhr1.send('id='+googleUser.getBasicProfile().getId()+'&password='+document.getElementById('password').value);

Google Login

Similar to signup, a POST request is done with ID and password as parameters. If the username and password exist then the webpage is directed to the screen where you can lock and unlock the smart lock.

var xhr1 = new XMLHttpRequest(); 
var url ='COPY_URL'; 
xhr1.open('POST', url); 
xhr1.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 
xhr1.onload = function() { 
   document.getElementById('name').innerText = xhr1.responseText; 
   if(xhr1.responseText.trim() == "ACCOUNT EXISTS") 
      window.open('/validkey.html','_self'); 
   else if(xhr1.responseText.trim() == "ACCOUNT DOES NOT EXIST") 
     //handle error
}; 
xhr1.send('id='+googleUser.getBasicProfile().getId()+'&password='+document.getElementById('password').value);

UUID

When you instantiate a PubNub instance (i.e. new PubNub()), the PubNub SDK generates a new UUID for you. So this UUID could be used as your authKey to which you can grant access in your Python script. Different ways to generate UUID can be found in this link.

u = str(uuid.uuid4())
pubnub.publish().channel('Raspberry').message(u).async(my_publish_callback)
pubnub.addListener({
        status: function(statusEvent) {
            if (statusEvent.category === "PNConnectedCategory") {
                //publishSampleMessage();
            }
        },
        message: function(msg) {
            console.log(msg.message);
            pubnub.setAuthKey(msg.message);
        },
        presence: function(presenceEvent) {
            // handle presence
        }
})      
console.log("Subscribing..");
    pubnub.subscribe({
        channels: ['Raspberry'] 
});

Once you set your UUID in Python script, you can subscribe to the message in your login page so that the smart lock sends UUID to your login page. After the UUID is set as authKey, Access Manager grants access to the UUID so that your device can access the smart lock.

var pubnub = new PubNub({
   subscribeKey: 'Enter your subscribe key here',
   publishKey: 'Enter your publish key here',
   secretKey: 'Enter your secret key here'
});
pubnub.grant({
   channels: ["Raspberry"],
   read: true,
   write: true,
   ttl: 5
},
function (status) {
   // handle state setting response
});

Wrapping Up

As you can see, OAuth and Access Manager go hand in hand to provide two layers of security to your smart lock. In order to make your Google authentication more secure, you can make sure that you do not accept plain user IDs. Instead, you can send the token that you acquired on the client side to the server. Then you can make a server-side API call using that token to get the user ID.

You can download my demo project from my Github here.