I'm developing an app in Kotlin and want to use Google Drive to upload files. Initially, Google authentication works, but when I request authorization for Drive, I don't get it.
Supposedly, I already have the app configured in the Google Cloud Console, in test mode, with my personal credentials as a test user, as well as Google Drive API enabled as a Library, and a web user.
I'm using credentialManager, and even though the code enters addOnSuccessListener, I don't receive an accessToken, nor are any scopes allowed. Any ideas?
class GoogleAuthHelper(private val context: Context) {
private val credentialManager = CredentialManager.create(context)
suspend fun signIn(activity: Activity): Pair<String, String>? = withContext(Dispatchers.Main) {
try {
val googleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(BuildConfig.WEB_GOOGLE_CLIENT_ID)
.setFilterByAuthorizedAccounts(false)
.setAutoSelectEnabled(true)
.setNonce(UUID.randomUUID().toString())
.build()
val request = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.setPreferImmediatelyAvailableCredentials(false)
.build()
val response = credentialManager.getCredential(
request = request,
context = activity
)
processResponse(response, activity)
} catch (e: GetCredentialException) {
Log.e("AUTH_ERROR", "Error: ${e.type} - ${e.message}")
null
}
}
private fun processResponse(response: GetCredentialResponse, activity: Activity): Pair<String, String>? {
return try {
val credential = response.credential
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
val googleIdTokenCredential = GoogleIdTokenCredential
.createFrom(credential.data)
val driveScope = Scope("https://www.googleapis.com/auth/drive.file")
val authorizationRequest = AuthorizationRequest.Builder()
.setRequestedScopes(listOf(driveScope))
.build()
val authorizationClient = Identity.getAuthorizationClient(activity)
authorizationClient.authorize(authorizationRequest)
.addOnSuccessListener { authorizationResult ->
// Log in but grantedScopes is empty
Log.e("AUTH", "TENGO AUTORIZACION OK")
Log.d("AUTH", "Authorization successful. Granted scopes: ${authorizationResult.grantedScopes}")
val token = authorizationResult.accessToken
if (token != null) {
val driveHelper = GoogleDriveHelper(context)
driveHelper.subirArchivoADrive(token)
}
}
.addOnFailureListener { exception ->
Log.e("AUTH", "Error: ${exception.message}")
}
return Pair(googleIdTokenCredential.id, googleIdTokenCredential.idToken)
}
throw Exception("Credential type is not valid")
} catch (e: Exception) {
Log.e("AUTH_ERROR", "Error al procesar la respuesta: ${e.message}")
null
}
}
}
For future readers, Google prioritise user's privacy and consent so to access any google service. I have created a re-usable class to access google service after authentication. Once you get the authentication, you can below class to access google services :
/**
* An abstract class to handle Google Service Authentication for various APIs like Drive and Calendar.
* This class provides methods to authorize the user, obtain credentials, and initialize API clients.
*
* @param context The application context used to initialize the authorization client.
*/
abstract class GoogleServiceAuth(context: Context) {
/** List of scopes required for authorization. Must be provided by subclasses. */
abstract val scopeList: List<Scope>
private var drive: Drive? = null
private var calendarService: Calendar? = null
private val authRequest by lazy {
AuthorizationRequest.builder().setRequestedScopes(scopeList).build()
}
private val authClient by lazy {
Identity.getAuthorizationClient(context)
}
/**
* Starts the authorization process for Google APIs.
*
* @param onError Callback when authorization fails.
* @param onResolution Callback when user interaction is required (e.g., consent screen).
* @param onResult Callback when authorization is successful and resolution is not needed.
*/
open fun authorizeForGoogleApi(
onError: (Throwable) -> Unit,
onResolution: (IntentSender?) -> Unit,
onResult: (AuthorizationResult) -> Unit
) {
authClient.authorize(authRequest).addOnSuccessListener { authResult ->
if (authResult.hasResolution()) {
onResolution(authResult.pendingIntent?.intentSender)
} else
onResult(authResult)
}.addOnFailureListener { exception ->
onError(exception)
}
}
/**
* Extracts the [AuthorizationResult] from the given intent after user interaction.
*
* @param intent The intent returned from the user consent screen.
* @return The parsed [AuthorizationResult].
*/
open fun getAuthorizationResultFromIntent(intent: Intent): AuthorizationResult {
return authClient.getAuthorizationResultFromIntent(intent)
}
/**
* Converts an [AuthorizationResult] to [Credentials] for accessing Google APIs.
*
* @param authorizationResult The result received from authorization.
* @param onError Callback if conversion fails.
* @param onObtainingCredentials Callback when credentials are obtained successfully.
*/
open fun onAuthorizationResult(
authorizationResult: AuthorizationResult,
onError: (Throwable) -> Unit = {},
onObtainingCredentials: (Credentials) -> Unit
) {
try {
val credentials =
GoogleCredentials.create(AccessToken(authorizationResult.accessToken, null))
onObtainingCredentials(credentials)
} catch (ex: Exception) {
onError(ex)
}
}
/**
* Initializes and returns the Google Drive client.
*
* @param credential Credentials obtained after authorization.
* @param appName Application name used for API requests.
* @param onError Callback when initialization fails.
* @param onGetDrive Callback when the Drive client is ready.
*/
open suspend fun getGoogleDrive(
credential: Credentials,
appName: String,
onError: (Throwable) -> Unit = {},
onGetDrive: suspend (Drive) -> Unit
) {
try {
if (drive != null) {
onGetDrive(drive!!)
} else {
drive = Drive.Builder(
NetHttpTransport(),
GsonFactory.getDefaultInstance(),
HttpCredentialsAdapter(credential)
)
.setApplicationName(appName)
.build()
onGetDrive(drive!!)
}
} catch (ex: Exception) {
onError(ex)
}
}
/**
* Initializes and returns the Google Calendar client.
*
* @param credential Credentials obtained after authorization.
* @param appName Application name used for API requests.
* @param onError Callback when initialization fails.
* @param onGetCalendarService Callback when the Calendar client is ready.
*/
open suspend fun getGoogleCalendarService(
credential: Credentials,
appName: String,
onError: (Throwable) -> Unit = {},
onGetCalendarService: suspend (calendarService: Calendar) -> Unit
) {
try {
if (calendarService != null) {
onGetCalendarService(calendarService!!)
} else {
calendarService = Calendar.Builder(
NetHttpTransport(),
GsonFactory.getDefaultInstance(),
HttpCredentialsAdapter(credential)
)
.setApplicationName(appName)
.build()
onGetCalendarService(calendarService!!)
}
} catch (ex: Exception) {
onError(ex)
}
}
}