How to Build Secure Login on Android with Google OAuth 2.0 and PubNub

It is estimated that over 2.7 billion people have at least one social media profile. So it comes as no surprise that a majority of applications have some sort of social sign-in as an easy way for their users to get on-board. From a development perspective, there is a major commonality between all these social sign-in providers, like Facebook, Twitter and Google. That common puzzle piece is OAuth, more specifically OAuth 2.0.

What is the difference between Authentication and Authorization?

In simple terms, authentication (authn) is the part where the user’s identity is verified and authorization (authz) is the part where the application determines whether the authenticated user has the permissions needed to access a particular resource.

Both authn and authz are tightly related and typically are found within the OAuth 2.0 process. Keep in mind that for both parts of the process, we’re going to need a server to keep our client ID and client secret.

How Authn and Authz fit into OAuth

A major misconception among developers is that authentication is a part of OAuth. In reality, OAuth is strictly the standard for authorization, though we’ll find that social platforms like Google will incorporate authentication and authorization in succession.

Here is an overview of the process:

Oauth 2.0 Flow Breakdown

Going step-by-step, we can see that there are two primary components. First, we need to have the required authorization from the user. This is only possible if the user is signed in, and this is where a lot of APIs will tie in the authentication piece of OAuth. Once we get the grant from the user account authority, we can begin talking to the resource owner. We send the grant to the authorization server and receive an access token back. Now, with the access token, we can finally begin accessing the information we need pertaining to the user.

Authenticating with Google Sign-in on Android

We have a brief understanding of OAuth and the difference between authentication and authorization, so let’s get into coding the authentication portion and discovering how to complete the authorization. In this particular case, we’ll use Google sign-in. For our server, we’ll be using PubNub Functions.

To get started with PubNub Functions, first get your pub/sub keys through the embedded form below:

Make sure you also have your Google API account set up and have the client ID and secret where you can easily access them.

Next, we’re going to add Google to our Android application so that we can start the sign-in process. First, make sure you have this line in you project level Gradle file.

repositories {
        google()
}

Now head over to the app level Gradle file and add the Google Play services auth dependency.

implementation 'com.google.android.gms:play-services-auth:16.0.1'

Even though we have the dependency added to our project, we won’t be able to go through the sign-in process quite yet. You have to edit the Android manifest file to add the Internet permissions.

<uses-permission android:name="android.permission.INTERNET" />

Adding the Google Sign-in Button

This is fairly straightforward. Add the sign-in component to the activity_main.xml file. The sign-in button is styled by Google, but make sure you take a look at Google’s brand guideline.

<com.google.android.gms.common.SignInButton
            android:id="@+id/sign_in_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="8dp"
            app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
            android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
            app:layout_constraintVertical_bias="0.665"
/>

Authenticating Google Account on Android

Now we can check for events on the sign-in button and look at the results to see whether the person has logged in or not.

private val RC_SIGN_IN = 9001

    private var mGoogleSignInClient: GoogleSignInClient? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.server_client_id))
            .requestEmail()
            .build()
        mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
    }

    public override fun onStart() {
        super.onStart()

        val mGmailSignIn = findViewById<SignInButton>(R.id.sign_in_button)

        val account = GoogleSignIn.getLastSignedInAccount(this)
        Log.w("Sign In: ", account.toString())

        mGmailSignIn.setOnClickListener {
                signIn()
        }
    }

    public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == RC_SIGN_IN) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            handleSignInResult(task)
        }
    }
    private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
        Log.w("Sign In: ", completedTask.toString())
        try {
            val account = completedTask.getResult(ApiException::class.java)
            val idToken = account!!.idToken
            Log.w("Sign In: ", idToken.toString())
        } catch (e: ApiException) {
            Log.w("Sign In: ", "signInResult:failed code=" + e.statusCode)
        }

    }

    private fun signIn() {
        val signInIntent = mGoogleSignInClient!!.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
    }

There are three main methods that we have to look at here and those are the onCreate, onStart and onActivityResult.

In the onCreate method, we create the Google sign-in object and add the options that we want.

The onStart method will then link our sign-in button we create earlier and create an onClickListener. Once a click is made, it will launch the Google sign-in flow.

Then, in the onActivityResult, we’ll need to verify the authenticated user. This is where PubNub Functions comes in.

We’ll need to create a function to act as an endpoint. The REST API will take in the user’s ID token and return a response that tells the app if the user is verified. If so, it will also show the name of the verified user.

export default (request, response) => {
    const pubnub = require('pubnub');
    const xhr = require('xhr');

    let headersObject = request.headers;
    let paramsObject = request.params;
    let methodString = request.method;
    let bodyString = request.body;
      
    
    const clientID = "your-client-id  from google";

    // Set the status code - by default it would return 200
    response.status = 200;
    // Set the headers the way you like
    response.headers['X-Custom-Header'] = 'CustomHeaderValue';

    const url = `https://oauth2.googleapis.com/tokeninfo?id_token=${paramsObject.token}`;
    return xhr.fetch(url).then((x) => {
        const body = JSON.parse(x.body);
        if (body.aud == clientID){
            return response.send(body.name);
        }else{
            return response.send("User couldn't be verified");
        }
    }).catch((err) => {
        // console.log(err)
        return response.send("Malformed JSON body.");
    });
       
};

In our app, we can add the required methods to our app to verify the user.

class MainActivity : AppCompatActivity() {


    private val RC_SIGN_IN = 9001

    private var mGoogleSignInClient: GoogleSignInClient? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.server_client_id))
            .requestEmail()
            .build()
        mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
    }

    public override fun onStart() {
        super.onStart()

        val mGmailSignIn = findViewById<SignInButton>(R.id.sign_in_button)

        val account = GoogleSignIn.getLastSignedInAccount(this)
        Log.w("Sign In: ", account.toString())

        mGmailSignIn.setOnClickListener {
                signIn()
        }
    }

    public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (requestCode == RC_SIGN_IN) {
            val task = GoogleSignIn.getSignedInAccountFromIntent(data)
            handleSignInResult(task)
        }
    }
    private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
        Log.w("Sign In: ", completedTask.toString())
        try {
            val account = completedTask.getResult(ApiException::class.java)
            val idToken = account!!.idToken
            Log.w("Sign In: ", idToken.toString())
            authCheck(idToken.toString())
        } catch (e: ApiException) {
            Log.w("Sign In: ", "signInResult:failed code=" + e.statusCode)
        }

    }

    private fun signIn() {
        val signInIntent = mGoogleSignInClient!!.signInIntent
        startActivityForResult(signInIntent, RC_SIGN_IN)
    }

    private fun authCheck(token: String) {
        // Instantiate the RequestQueue.
        val queue = newRequestQueue(this)
        val url = "https://pubsub.pubnub.com/v1/blocks/sub-key/sub-c-87dbd99c-e470-11e8-8d80-3ee0fe19ec50/google?token=$token"

        // Request a string response from the provided URL.
        val stringRequest = StringRequest(
            Request.Method.GET, url,
            Response.Listener<String> { response ->
                // Display the first 500 characters of the response string.
                Log.d("Response: ", response)
            },
            Response.ErrorListener {Log.d("Response: ", "Didn't Work!")})

        // Add the request to the RequestQueue.
        queue.add(stringRequest)

    }

}

Now you have an app that’s fully able to authenticate and verify a user! The next step is retrieving the Access token which is done in the exact same way. Instead, this time you’d be doing a POST request to the token URL – https://www.googleapis.com/oauth2/v4/token. 

Your PubNub Function will look something like this.

export default (request, response) => {
    const pubnub = require('pubnub');
    const xhr = require('xhr');

    let headersObject = request.headers;
    let paramsObject = request.params;
    let methodString = request.method;
    let bodyString = request.body;

    // Set the status code - by default it would return 200
    response.status = 200;
    // Set the headers the way you like
    
    var client_id = "your google client id"
    var client_secret = "your google client secret";
    var redirect = "https://pubsub.pubnub.com/v1/blocks/sub-key/sub-c-87dbd99c-e470-11e8-8d80-3ee0fe19ec50/Auth";
    
    response.headers['X-Custom-Header'] = 'CustomHeaderValue';
    var dataString = `client_id=${client_id}&client_secret=${client_secret}&redirect_uri=${redirect}&grant_type=authorization_code&code=CODE`;
    var url = `https://www.googleapis.com/oauth2/v4/token${dataString}`;
    var options = {
        "method": 'POST',
        "body": dataString,
    };
    
    console.log(dataString);
    return xhr.fetch(url, options).then((x) => {
        const body = JSON.parse(x.body);
        console.log(body);
         return response.send(body);
    }).catch((err) => {
        // console.log(err)
        return response.send("Malformed JSON body.");
    });

};

In this case, you would need to get the authorization code from your Android user which you could obtain by adding this code to your MainActivity.kt file.

Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {
    GoogleSignInAccount account = task.getResult(ApiException.class);
    String authCode = account.getServerAuthCode();

    // Show signed-un UI
    updateUI(account);

    // TODO(developer): send code to server and exchange for access/refresh/ID tokens
} catch (ApiException e) {
    Log.w(TAG, "Sign-in failed", e);
    updateUI(null);
}
Android Google Sign In

 

Give your app a spin by clicking the play button and see that it works now. You should be able to go through the whole sign up flow with no problem!

Going through all the steps, you should now have more clarity on the differences between authentication and authorization. With that knowledge, you can start looking at what you can do after your user signs in.

 

 

 

 

 

 

 

 

Try PubNub Today

Share this on facebookShare this on TwitterShare this on Linkedin