Memberi otorisasi akses ke data pengguna Google

Autentikasi menetapkan identitas seseorang, dan biasanya disebut sebagai pendaftaran atau login pengguna. Otorisasi adalah proses pemberian atau penolakan akses ke data atau resource. Misalnya, aplikasi Anda meminta izin pengguna Anda untuk mengakses Google Drive pengguna.

Panggilan autentikasi dan otorisasi harus berupa dua alur terpisah dan berbeda berdasarkan kebutuhan aplikasi.

Jika aplikasi Anda memiliki fitur yang dapat menggunakan data Google API, tetapi tidak diperlukan sebagai bagian dari fitur inti aplikasi, Anda harus mendesain aplikasi agar dapat menangani kasus dengan baik saat data API tidak dapat diakses. Misalnya, Anda dapat menyembunyikan daftar file yang baru-baru ini disimpan saat pengguna belum memberikan akses Drive.

Anda hanya boleh meminta akses ke cakupan yang diperlukan untuk mengakses Google API saat pengguna melakukan tindakan yang memerlukan akses ke API tertentu. Misalnya, Anda harus meminta izin untuk mengakses Drive pengguna setiap kali pengguna mengetuk tombol "Simpan ke Drive".

Dengan memisahkan otorisasi dari autentikasi, Anda dapat menghindari pengguna baru yang merasa kewalahan, atau pengguna yang bingung mengapa mereka diminta izin tertentu.

Untuk autentikasi, sebaiknya gunakan Credential Manager API. Untuk mengizinkan tindakan yang memerlukan akses ke data pengguna yang disimpan oleh Google, sebaiknya gunakan AuthorizationClient.

Menyiapkan project

  1. Buka project Anda di , atau buat project jika Anda belum memilikinya.
  2. Di , pastikan semua informasi sudah lengkap dan akurat.
    1. Pastikan aplikasi Anda memiliki Nama Aplikasi, Logo Aplikasi, dan Halaman Beranda Aplikasi yang ditetapkan dengan benar. Nilai ini akan ditampilkan kepada pengguna di layar izin Login dengan Google saat mendaftar dan di layar Aplikasi & layanan pihak ketiga.
    2. Pastikan Anda telah menentukan URL kebijakan privasi dan persyaratan layanan aplikasi Anda.
  3. Di , buat ID klien Android untuk aplikasi Anda jika Anda belum memilikinya. Anda harus menentukan nama paket dan tanda tangan SHA-1 aplikasi Anda.
    1. Buka .
    2. Klik Buat klien.
    3. Pilih jenis aplikasi Android.
  4. Di , buat ID klien "Aplikasi web" baru jika Anda belum melakukannya. Anda dapat mengabaikan kolom "Authorized JavaScript Origins" dan "Authorized redirect URIs" untuk saat ini. ID klien ini akan digunakan untuk mengidentifikasi server backend Anda saat berkomunikasi dengan layanan autentikasi Google.
    1. Buka .
    2. Klik Buat klien.
    3. Pilih jenis Web application.

Mendeklarasikan dependensi

Dalam file build.gradle modul Anda, deklarasikan dependensi menggunakan versi terbaru library Google Identity Services.

dependencies {
  // ... other dependencies

  implementation "com.google.android.gms:play-services-auth:21.4.0"
}

Meminta izin yang diperlukan oleh tindakan pengguna

Setiap kali pengguna melakukan tindakan yang memerlukan cakupan tambahan, panggil AuthorizationClient.authorize(). Misalnya, jika pengguna melakukan tindakan yang memerlukan akses ke penyimpanan aplikasi Drive-nya, lakukan hal berikut:

Kotlin

val requestedScopes: List<Scope> = listOf(DriveScopes.DRIVE_FILE)
val authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .build()

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequestBuilder.build())
    .addOnSuccessListener { authorizationResult ->
        if (authorizationResult.hasResolution()) {
            val pendingIntent = authorizationResult.pendingIntent
            // Access needs to be granted by the user
            startAuthorizationIntent.launchIntentSenderRequest.Builder(pendingIntent!!.intentSender).build()
        } else {
            // Access was previously granted, continue with user action
            saveToDriveAppFolder(authorizationResult);
        }
    }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }

Java

List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .build();

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequest)
    .addOnSuccessListener(authorizationResult -> {
        if (authorizationResult.hasResolution()) {
            // Access needs to be granted by the user
            startAuthorizationIntent.launch(
                new IntentSenderRequest.Builder(
                    authorizationResult.getPendingIntent().getIntentSender()
                ).build()
            );
        } else {
            // Access was previously granted, continue with user action
            saveToDriveAppFolder(authorizationResult);
        }
    })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));

Saat menentukan ActivityResultLauncher, tangani respons seperti yang ditunjukkan dalam cuplikan berikut, dengan asumsi bahwa hal ini dilakukan dalam fragmen. Kode ini memeriksa bahwa izin yang diperlukan telah berhasil diberikan, lalu melakukan tindakan pengguna.

Kotlin

private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            try {
                // extract the result
                val authorizationResult = Identity.getAuthorizationClient(requireContext())
                    .getAuthorizationResultFromIntent(activityResult.data)
                // continue with user action
                saveToDriveAppFolder(authorizationResult);
            } catch (ApiException e) {
                // log exception
            }
        }
}

Java

private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;

@Override
public View onCreateView(
    @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
    registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(),
        activityResult -> {
            try {
            // extract the result
            AuthorizationResult authorizationResult =
                Identity.getAuthorizationClient(requireActivity())
                    .getAuthorizationResultFromIntent(activityResult.getData());
            // continue with user action
            saveToDriveAppFolder(authorizationResult);
            } catch (ApiException e) {
            // log exception
            }
        });
}

Jika Anda mengakses Google API di sisi server, panggil metode getServerAuthCode() dari AuthorizationResult untuk mendapatkan kode otorisasi yang Anda kirim ke backend untuk ditukar dengan token akses dan token refresh. Untuk mempelajari lebih lanjut, lihat artikel Mempertahankan akses berkelanjutan ke data pengguna.

Mencabut izin ke data atau resource pengguna

Untuk mencabut akses yang diberikan sebelumnya, panggil AuthorizationClient.revokeAccess(). Misalnya, jika pengguna menghapus akunnya dari aplikasi Anda, dan aplikasi Anda sebelumnya diberi akses ke DriveScopes.DRIVE_FILE, gunakan kode berikut untuk mencabut akses:

Kotlin

val requestedScopes: MutableList<Scope> = mutableListOf(DriveScopes.DRIVE_FILE)
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
    .setAccount(account)
    .setScopes(requestedScopes)
    .build()

Identity.getAuthorizationClient(activity)
    .revokeAccess(revokeAccessRequest)
    .addOnSuccessListener { Log.i(TAG, "Successfully revoked access") }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to revoke access", e) }

Java

List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
    .setAccount(account)
    .setScopes(requestedScopes)
    .build();

Identity.getAuthorizationClient(activity)
    .revokeAccess(revokeAccessRequest)
    .addOnSuccessListener(unused -> Log.i(TAG, "Successfully revoked access"))
    .addOnFailureListener(e -> Log.e(TAG, "Failed to revoke access", e));

Menghapus cache token

Token akses OAuth di-cache secara lokal setelah diterima dari server, sehingga mempercepat akses dan mengurangi panggilan jaringan. Token ini otomatis dihapus dari cache saat masa berlakunya berakhir, tetapi juga dapat menjadi tidak valid karena alasan lain. Jika Anda menerima IllegalStateException saat menggunakan token, hapus cache lokal untuk memastikan bahwa permintaan otorisasi berikutnya untuk token akses akan dikirim ke server OAuth. Cuplikan berikut menghapus invalidAccessToken dari cache lokal:

Kotlin

Identity.getAuthorizationClient(activity)
    .clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
    .addOnSuccessListener { Log.i(TAG, "Successfully removed the token from the cache") }
    .addOnFailureListener{ e -> Log.e(TAG, "Failed to clear token", e) }

Java

Identity.getAuthorizationClient(activity)
    .clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
    .addOnSuccessListener(unused -> Log.i(TAG, "Successfully removed the token from the cache"))
    .addOnFailureListener(e -> Log.e(TAG, "Failed to clear the token cache", e));

Mendapatkan informasi pengguna selama otorisasi

Respons otorisasi tidak berisi informasi apa pun tentang akun pengguna yang digunakan; respons hanya berisi token untuk cakupan yang diminta. Misalnya, respons untuk mendapatkan token akses guna mengakses Google Drive pengguna tidak mengungkapkan identitas akun yang dipilih oleh pengguna meskipun dapat digunakan untuk mengakses file di drive pengguna. Untuk mendapatkan informasi seperti nama atau email pengguna, Anda memiliki opsi berikut:

  • Login pengguna dengan Akun Google mereka menggunakan Credential Manager API sebelum meminta otorisasi. Respons autentikasi dari Pengelola Kredensial mencakup informasi pengguna seperti alamat email dan juga menetapkan akun default aplikasi ke akun yang dipilih; jika diperlukan, Anda dapat melacak akun ini di aplikasi Anda. Permintaan otorisasi berikutnya menggunakan akun sebagai default dan melewati langkah pemilihan akun dalam alur otorisasi. Untuk menggunakan akun lain untuk otorisasi, lihat Otorisasi dari akun non-default.

  • Dalam permintaan otorisasi Anda, selain cakupan yang Anda inginkan (misalnya, Drive scope), minta cakupan userinfo, profile, dan openid. Setelah token akses ditampilkan, dapatkan info pengguna dengan membuat permintaan HTTP GET ke endpoint info pengguna OAuth (https://www.googleapis.com/oauth2/v3/userinfo) menggunakan library HTTP pilihan Anda dan sertakan token akses yang telah Anda terima di header, yang setara dengan perintah curl berikut:

    curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"
    

    Responsnya adalah UserInfo, yang dibatasi pada cakupan yang diminta, diformat dalam JSON.

Otorisasi dari akun non-default

Jika Anda menggunakan Pengelola Kredensial untuk mengautentikasi, dan menjalankan AuthorizationClient.authorize(), akun default aplikasi Anda akan disetel ke akun yang dipilih oleh pengguna Anda. Artinya, setiap panggilan berikutnya untuk otorisasi akan menggunakan akun default ini. Untuk memaksa menampilkan pemilih akun, buat pengguna logout dari aplikasi menggunakan clearCredentialState() API dari Credential Manager.

Mempertahankan akses berkelanjutan ke data pengguna

Jika Anda perlu mengakses data pengguna dari aplikasi, panggil AuthorizationClient.authorize() satu kali; pada sesi berikutnya, dan selama izin yang diberikan tidak dihapus oleh pengguna, panggil metode yang sama untuk mendapatkan token akses guna mencapai tujuan Anda, tanpa interaksi pengguna. Namun, jika Anda perlu mengakses data pengguna dalam mode offline, dari server backend, Anda harus meminta jenis token lain yang disebut "token refresh".

Token akses sengaja didesain agar berumur pendek dan memiliki masa aktif selama satu jam. Jika token akses dicegat atau disusupi, jendela validitasnya yang terbatas akan meminimalkan potensi penyalahgunaan. Setelah masa berlakunya berakhir, token menjadi tidak valid, dan setiap upaya untuk menggunakannya akan ditolak oleh server resource. Karena token akses memiliki masa berlaku singkat, server menggunakan token refresh untuk mempertahankan akses berkelanjutan ke data pengguna. Token refresh adalah token dengan masa berlaku yang lama yang digunakan oleh klien untuk meminta token akses yang masa berlakunya singkat dari server otorisasi, saat token akses lama telah berakhir, tanpa interaksi pengguna.

Untuk mendapatkan token refresh, Anda harus mendapatkan kode autentikasi (atau kode otorisasi) terlebih dahulu selama langkah otorisasi di aplikasi Anda dengan meminta "akses offline", lalu menukar kode autentikasi dengan token refresh di server Anda. Penting untuk menyimpan token refresh yang memiliki masa aktif yang lama dengan aman di server Anda karena token tersebut dapat digunakan berulang kali untuk mendapatkan token akses baru. Oleh karena itu, sangat tidak disarankan untuk menyimpan token refresh di perangkat karena masalah keamanan. Sebagai gantinya, kode tersebut harus disimpan di server backend aplikasi tempat penukaran token akses dilakukan.

Setelah kode autentikasi dikirim ke server backend aplikasi Anda, Anda dapat menukarnya dengan token akses jangka pendek di server dan token refresh jangka panjang dengan mengikuti langkah-langkah dalam panduan otorisasi akun. Pertukaran ini hanya boleh terjadi di backend aplikasi Anda.

Kotlin

// Ask for offline access during the first authorization request
val authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .requestOfflineAccess(serverClientId)
    .build()

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequest)
    .addOnSuccessListener { authorizationResult ->
        startAuthorizationIntent.launchIntentSenderRequest.Builder(
            pendingIntent!!.intentSender
        ).build()
    }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }

Java

// Ask for offline access during the first authorization request
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .requestOfflineAccess(serverClientId)
    .build();

Identity.getAuthorizationClient(getContext())
    .authorize(authorizationRequest)
    .addOnSuccessListener(authorizationResult -> {
        startAuthorizationIntent.launch(
            new IntentSenderRequest.Builder(
                authorizationResult.getPendingIntent().getIntentSender()
            ).build()
        );
    })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize"));

Cuplikan berikut mengasumsikan bahwa otorisasi dimulai dari fragmen.

Kotlin

private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            try {
                val authorizationResult = Identity.getAuthorizationClient(requireContext())
                    .getAuthorizationResultFromIntent(activityResult.data)
                // short-lived access token
                accessToken = authorizationResult.accessToken
                // store the authorization code used for getting a refresh token safely to your app's backend server
                val authCode: String = authorizationResult.serverAuthCode
                storeAuthCodeSafely(authCode)
            } catch (e: ApiException) {
                // log exception
            }
        }
}

Java

private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;

@Override
public View onCreateView(
    @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(
            new ActivityResultContracts.StartIntentSenderForResult(),
            activityResult -> {
                try {
                    AuthorizationResult authorizationResult =
                        Identity.getAuthorizationClient(requireActivity())
                            .getAuthorizationResultFromIntent(activityResult.getData());
                    // short-lived access token
                    accessToken = authorizationResult.getAccessToken();
                    // store the authorization code used for getting a refresh token safely to your app's backend server
                    String authCode = authorizationResult.getServerAuthCode()
                    storeAuthCodeSafely(authCode);
                } catch (ApiException e) {
                    // log exception
                }
            });
}