A beginner's guide to OAuth 2.0 with Google sign-in: Enhance Android app authentication

Demola Malomo - Aug 4 '23 - - Dev Community

Android mobile operating system is the most popular operating system in the world, with approximately 2.3 Billion users and a market share of 71.4%. It has even seen further adoption due to the recent introduction of Jetpack Compose, an Android modern toolkit for building native UI with less code and powerful Kotlin API. Developers can now build native and cross-platform applications for phones, TVs, computers, etc.

Beyond building these applications, developers must also ensure that they are secure and that only authorized users can access protected resources.

In this post, we will follow a step-by-step guide on enhancing an Android app authentication using Google as the provider. The project repository can be found here.

Prerequisites

To fully grasp the concepts presented in this tutorial, the following are required:

  • Basic understanding of Kotlin and Android
  • IntelliJ IDEA or Android Studio properly set up
  • Appwrite account sign-up is completely free
  • Google account

Getting started

To get started, we need to clone the project by navigating to the desired directory and running the command below:



git clone https://github.com/Mr-Malomz/android_auth.git


Enter fullscreen mode Exit fullscreen mode

The project's com.example.android_auth directory consists of the following packages:

  • navigation consists of files used in setting up the application navigation
  • screens consist of the application UI screens

Running the project

We can run the project by selecting the preferred emulator and clicking the Run button.

Run app

The step above will run the application on the selected device.

Login screen
Home screen

Set up a project on Appwrite

To get started, we need to log into our Appwrite console, click the Create project button, input android_auth as the name, and Create.

Create project

Add platform support

To add support for our Android app, navigate to the Home menu and click the Android App button.

Add platform

Next, we must modify the application by inputting android_auth as the Name and com.example.android_auth as the Package Name.

Add platform support

Enable Google as the preferred OAuth 2.0 provider

To do this, we must navigate to the Auth menu, click the Settings tab, and enable Auth0 as a provider.

Navigate to the provider
Google OAuth

We will fill in the required details later. However, we must copy and keep the redirect URI, it will come in handy when setting up our application on the Android developer portal.

Copy URI

Set up Google OAuth 2.0

With our project partly configured on Appwrite, we must also set up our project on the Google console.

Create an OAuth consent screen

The OAuth consent screen helps us configure the application scope, policy, and other relevant details required for OAuth 2.0 implementation. It also shows our users a summary of the project and the kind of permissions our application requires. To do this, we need to log into the Google console and create a project.

Create project

Navigate to the OAuth consent screen under APIs & Services, select External, and Create.

navigate to OAuth consent screen
Select external and create

Input Quickstart as the App name, fill in other required details, and Save and Continue.

App info

PS: We must name our application Quickstart to avoid the “An error occurred while saving your apperror.

Click on the Add or Remove Scopes, add email and profile as shown below and Update.

Add scope

Next, we need to add test users. Only the users we registered here will be able to test our application. To do this, click the + Add User button, input the test user’s emails (Gmail only) and select Add.

Add test users.

Then click the Save and Continue button and review the configuration.

Create Credential

Credentials on Google console help applications securely access Google’s enabled services. We must create a credential for our application to use the OAuth 2.0 functionality. To do this, navigate to the Credentials tab, click the + Create Credentials button and select the OAuth client ID.

Navigate
Select OAuth

Select Web application as the Application type, input android_auth as the Name, input the Redirect URI we copied from Appwrite earlier as the Authorised redirect URIs, and Create.

On creation, we should see our application Client ID and Client Secret. We need to copy and keep these credentials as they will be useful when finishing up our application set-up on Appwrite.

ID and Secret

Putting it together on Appwrite

With our application configured on Google, we must create a link to it on Appwrite. To do this, we must update the Google provider details with the copied Client ID, Client secret, and Update.

Fill and Update

Integrating Google OAuth 2.0 with Android using Appwrite

With all that done, let’s build OAuth 2.0 into our application. First, we need to install the required dependency. To do this, we need to navigate to the build.gradle file inside the app dependency and add the snippet below under the dependencies section:



dependencies {
    //remaining dependencies here
    constraints {
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") {
            because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib")
        }
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") {
            because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib")
        }
    }
    implementation("io.appwrite:sdk-for-android:2.0.0")
}


Enter fullscreen mode Exit fullscreen mode

The snippet above adds a dependency constraint to the Kotlin version and Appwrite SDK as a required dependency. We must also re-sync the dependencies to ensure our Android build has no problems.

Configure OAuth callback

In order for our application to redirect to the configured screen after authenticating through OAuth 2.0, we must capture the Appwrite OAuth callback URL. To do this, we need to update the app/main/AndroidManifest.xml file and add the snippet below under the <application> section:



    <activity android:name="io.appwrite.views.CallbackActivity" android:exported="true">
        <intent-filter android:label="android_web_auth">
            <action android:name="android.intent.action.VIEW"/>
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
            <data android:scheme="appwrite-callback-[PROJECT_ID]"/>
        </intent-filter>
    </activity>


Enter fullscreen mode Exit fullscreen mode

We must also replace the [PROJECT_ID] string with our Appwrite Project ID.

Create services

Next, we need to create service files to separate our application logic from the UI. To do this, we need to create a utils package inside the com.example.android_auth directory and inside this package, create a Resource.kt file and add the snippet below:



package com.example.android_auth.utils

data class ResourceResponse(
    var name: String? = null,
    var email: String? = null,
    var error: Boolean? = null,
)


Enter fullscreen mode Exit fullscreen mode

The snippet above creates a data class to represent the response we get from Appwrite when authenticating using OAuth 2.0.

Lastly, we need to create a Kotlin Class Client.kt inside the same utils package and add the snippet below:



package com.example.android_auth.utils

import android.content.Context
import com.example.android_auth.MainActivity
import io.appwrite.Client
import io.appwrite.exceptions.AppwriteException
import io.appwrite.services.Account
import java.lang.Exception


private class ClientInit(context: Context) {
    var clientInstance = Client(context)
        .setEndpoint("https://cloud.appwrite.io/v1")
        .setProject("REPLACE WITH PROJECT ID")
}

class Client {
    suspend fun loginWithGoogle(activity: MainActivity, context: Context): ResourceResponse {
        var account = Account(ClientInit(context).clientInstance)
        try {
            account.createOAuth2Session(activity = activity, provider = "google")
            var data = account.get()
            return ResourceResponse(name = data.name, email = data.email, error = false)
        } catch (e: AppwriteException) {
            return ResourceResponse(error = true)
        }
    }

    suspend fun logOut(context: Context) {
        var account = Account(ClientInit(context).clientInstance)
        try {
            account.deleteSessions()
        } catch (e: AppwriteException) {
            throw Exception("error login out")
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

The snippet above does the following:

  • Imports the required dependencies
  • Creates a private class ClientInit to initialize Appwrite instance
  • Creates a Client class with a loginWithGoogle and logOut method that uses the Appwrite instance to login with Google using google as the provider and log out by deleting active sessions, respectively

Using the services

With that done, we can start using the created services to build our application. To do this, first, we need to modify the Navigation.kt file as shown below:



//depencies goes here

@Composable
fun Navigation(activity: MainActivity) {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = Screen.Login.route) {
        composable(route = Screen.Login.route) {
            Login(navController, activity)
        }
        composable(route = "${Screen.Home.route}/{name}/{email}") { it ->
            Home(navController, it.arguments?.getString("name"), it.arguments?.getString("email"))
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

The snippet above modifies the route to accept name and email as part of the arguments and adds them as an argument to the Home screen. We will get an error after passing the arguments, but we will fix this shortly.

Secondly, we need to modify the Login.kt file inside the screens folder as shown below:



package com.example.android_auth.screens

import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.android_auth.MainActivity
import com.example.android_auth.R
import com.example.android_auth.navigation.Screen
import com.example.android_auth.utils.Client
import kotlinx.coroutines.launch

@Composable
fun Login(navController: NavController, activity: MainActivity) {
    var client = Client()
    val context = LocalContext.current
    val coroutineScope = rememberCoroutineScope()

    Column(
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.padding(20.dp)
            .fillMaxWidth()
            .fillMaxHeight()
    ) {
        Column(horizontalAlignment = Alignment.CenterHorizontally) {
            Image(painterResource(id = R.drawable.lock), contentDescription = "lock image")
            Spacer(modifier = Modifier.padding(30.dp))
            Button(
                onClick = {
                    coroutineScope.launch {
                        var data = client.loginWithGoogle(activity, context)
                        if (data.error == true) {
                            Toast.makeText(activity, "Error login in with google", Toast.LENGTH_SHORT).show()
                        } else {
                            navController.navigate("${Screen.Home.route}/${data.name}/${data.email}")
                        }
                    }
                },
                colors = ButtonDefaults.buttonColors(Color(0xFF0165E1)),
                modifier = Modifier
                    .height(45.dp)
                    .fillMaxWidth()
            ) {
                Text(
                    text = "Login with Google",
                    color = Color.White,
                    fontSize = 14.sp,
                    fontWeight = FontWeight.Bold,
                )
            }
        }
    };
}


Enter fullscreen mode Exit fullscreen mode

The snippet above does the following:

  • Updates the required dependencies
  • Creates client, context, and coroutineScope properties to initialize Appwrite instance, get application context, and perform an asynchronous operation, respectively
  • Modifies the button’s onClick property to use the loginWithGoogle method, handles the error appropriately, and navigates to the Home screen by passing the returned data as route arguments

Lastly, we need to modify the Home.kt file as shown below:



package com.example.android_auth.screens

import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.android_auth.navigation.Screen
import com.example.android_auth.utils.Client
import kotlinx.coroutines.launch

@Composable
fun Home(navController: NavController, name: String?, email: String?) {
    var client = Client()
    val context = LocalContext.current
    val coroutineScope = rememberCoroutineScope()

    Column(
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier
            .padding(20.dp)
            .fillMaxWidth()
            .fillMaxHeight()
    ) {
        Text(
            text = "Welcome $name",
            color = Color(0xFF0165E1),
            fontSize = 20.sp,
            fontWeight = FontWeight.Bold,
        )
        Spacer(modifier = Modifier.padding(10.dp))
        Text(text = "$email")
        Spacer(modifier = Modifier.padding(10.dp))
        Button(
            onClick = {
                coroutineScope.launch {
                    client.logOut(context);
                    navController.navigate(Screen.Login.route) {
                        popUpTo(Screen.Login.route)
                    }
                }
            }, colors = ButtonDefaults.buttonColors(Color.Black),
            modifier = Modifier
                .height(45.dp)
        ) {
            Text(
                text = "Log out",
                color = Color.White,
                fontSize = 14.sp,
                fontWeight = FontWeight.Bold,
            )
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

The snippet above does the following:

  • Updates the required dependencies
  • Modifies the function definition to accept name and email as parameters
  • Creates client, context, and coroutineScope properties to initialize Appwrite instance, get application context, and perform an asynchronous operation, respectively
  • Updates the UI placeholder name and email with the parameters
  • Modifies the button’s onClick property to use the logOut method to log users out of the application and redirect them to the Login screen

With that done, we can test our application by clicking the Run button.

We can also confirm logged-in users on our Appwrite console.

logged-in users

Missing redirect URL error

When testing our application, we might notice an error screen complaining about a missing URL. It’s an open issue that should be resolved soon.

missing url

Conclusion

This post detailed a step-by-step guide to implementing Google OAuth 2.0 login in an Android application using Appwrite. Beyond what was discussed above, Appwrite’s Android SDK also has robust functionalities that developers can leverage to build even more robust authorization and authentication mechanisms.

These resources may also be helpful:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .