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
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.
The step above will run the application on the selected device.
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.
Add platform support
To add support for our Android app, navigate to the Home menu and click the Android App button.
Next, we must modify the application by inputting android_auth
as the Name and com.example.android_auth
as the Package Name.
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.
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.
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.
Navigate to the OAuth consent screen under APIs & Services, select External, and Create.
Input Quickstart
as the App name, fill in other required details, and Save and Continue.
PS: We must name our application Quickstart
to avoid the “An error occurred while saving your app” error.
Click on the Add or Remove Scopes, add email
and profile
as shown below and Update.
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.
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.
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.
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.
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")
}
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>
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,
)
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")
}
}
}
The snippet above does the following:
- Imports the required dependencies
- Creates a private class
ClientInit
to initialize Appwrite instance - Creates a
Client
class with aloginWithGoogle
andlogOut
method that uses the Appwrite instance to login with Google usinggoogle
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"))
}
}
}
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,
)
}
}
};
}
The snippet above does the following:
- Updates the required dependencies
- Creates
client
,context
, andcoroutineScope
properties to initialize Appwrite instance, get application context, and perform an asynchronous operation, respectively - Modifies the button’s
onClick
property to use theloginWithGoogle
method, handles the error appropriately, and navigates to theHome
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,
)
}
}
}
The snippet above does the following:
- Updates the required dependencies
- Modifies the function definition to accept
name
andemail
as parameters - Creates
client
,context
, andcoroutineScope
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 thelogOut
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.
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.
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: