androidkotlingoogle-drive-api

Notice [RequestTokenManager] getToken() -> NEED_REMOTE_CONSENT when authorizing Google Drive


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
        }
    }
}

Solution

  • 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)
            }
        }
    
       
    }