Mengautentikasi ke layanan OAuth2

Diagram logika token autentikasi
Gambar 1. Prosedur untuk mendapatkan token autentikasi yang valid dari Android Account Manager.

Agar dapat mengakses layanan online dengan aman, pengguna harus melakukan autentikasi ke layanan—mereka harus memberikan bukti identitasnya. Untuk aplikasi yang mengakses layanan pihak ketiga, masalah keamanan menjadi lebih rumit lagi. Pengguna tidak hanya harus diautentikasi untuk mengakses layanan, tetapi aplikasi juga harus diizinkan untuk bertindak atas nama pengguna.

Cara standar industri untuk menangani autentikasi ke layanan pihak ketiga adalah protokol OAuth2. OAuth2 memberikan nilai tunggal, yang disebut token autentikasi, yang mewakili identitas pengguna dan otorisasi aplikasi untuk bertindak atas nama pengguna. Pelajaran ini menunjukkan cara menghubungkan ke server Google yang mendukung OAuth2. Meskipun layanan Google digunakan sebagai contoh, teknik yang ditunjukkan akan berfungsi pada layanan apa pun yang mendukung protokol OAuth2 dengan benar.

OAuth2 baik digunakan untuk:

  • Mendapatkan izin dari pengguna untuk mengakses layanan online menggunakan akun mereka.
  • Mengautentikasi ke layanan online atas nama pengguna.
  • Menangani error autentikasi.

Mengumpulkan informasi

Untuk mulai menggunakan OAuth2, Anda perlu mengetahui beberapa hal khusus API tentang layanan yang Anda coba akses:

  • URL layanan yang ingin Anda akses.
  • Cakupan autentikasi, yaitu string yang menentukan jenis akses tertentu yang diminta aplikasi Anda. Misalnya, cakupan autentikasi untuk akses hanya baca ke Google Tasks adalah View your tasks, sedangkan cakupan autentikasi untuk akses baca-tulis ke Google Tasks adalah Manage your tasks.
  • Client ID dan rahasia klien, yaitu string yang mengidentifikasi aplikasi Anda ke layanan. Anda perlu mendapatkan {i>string<i} ini langsung dari pemilik layanan. Google memiliki sistem layanan mandiri untuk mendapatkan client ID dan rahasia.

Meminta izin internet

Untuk aplikasi yang menargetkan Android 6.0 (API level 23) dan yang lebih tinggi, metode getAuthToken() tidak memerlukan izin apa pun. Namun, untuk melakukan operasi pada token, Anda harus menambahkan izin INTERNET ke file manifes, seperti yang ditunjukkan dalam cuplikan kode berikut:

<manifest ... >
    <uses-permission android:name="android.permission.INTERNET" />
    ...
</manifest>

Meminta token autentikasi

Untuk mendapatkan token, panggil AccountManager.getAuthToken().

Perhatian: Karena beberapa operasi akun mungkin melibatkan komunikasi jaringan, sebagian besar metode AccountManager bersifat asinkron. Artinya, Anda harus menerapkannya sebagai serangkaian callback, bukan melakukan semua pekerjaan autentikasi dalam satu fungsi.

Cuplikan berikut menunjukkan cara menggunakan serangkaian callback untuk mendapatkan token:

Kotlin

val am: AccountManager = AccountManager.get(this)
val options = Bundle()

am.getAuthToken(
        myAccount_,                     // Account retrieved using getAccountsByType()
        "Manage your tasks",            // Auth scope
        options,                        // Authenticator-specific options
        this,                           // Your activity
        OnTokenAcquired(),              // Callback called when a token is successfully acquired
        Handler(OnError())              // Callback called if an error occurs
)

Java

AccountManager am = AccountManager.get(this);
Bundle options = new Bundle();

am.getAuthToken(
    myAccount_,                     // Account retrieved using getAccountsByType()
    "Manage your tasks",            // Auth scope
    options,                        // Authenticator-specific options
    this,                           // Your activity
    new OnTokenAcquired(),          // Callback called when a token is successfully acquired
    new Handler(new OnError()));    // Callback called if an error occurs

Dalam contoh ini, OnTokenAcquired adalah class yang mengimplementasikan AccountManagerCallback. AccountManager memanggil run() di OnTokenAcquired dengan AccountManagerFuture yang berisi Bundle. Jika panggilan berhasil, token berada di dalam Bundle.

Berikut adalah cara mendapatkan token dari Bundle:

Kotlin

private class OnTokenAcquired : AccountManagerCallback<Bundle> {

    override fun run(result: AccountManagerFuture<Bundle>) {
        // Get the result of the operation from the AccountManagerFuture.
        val bundle: Bundle = result.getResult()

        // The token is a named value in the bundle. The name of the value
        // is stored in the constant AccountManager.KEY_AUTHTOKEN.
        val token: String = bundle.getString(AccountManager.KEY_AUTHTOKEN)
    }
}

Java

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        // Get the result of the operation from the AccountManagerFuture.
        Bundle bundle = result.getResult();

        // The token is a named value in the bundle. The name of the value
        // is stored in the constant AccountManager.KEY_AUTHTOKEN.
        String token = bundle.getString(AccountManager.KEY_AUTHTOKEN);
        ...
    }
}

Jika semuanya berjalan lancar, Bundle akan berisi token yang valid dalam kunci KEY_AUTHTOKEN dan Anda sudah siap.

Permintaan pertama Anda untuk token autentikasi mungkin gagal karena beberapa alasan:

  • Terjadi error di perangkat atau jaringan yang menyebabkan AccountManager gagal.
  • Pengguna memutuskan untuk tidak memberi aplikasi Anda akses ke akun.
  • Kredensial akun yang tersimpan tidak memadai untuk mendapatkan akses ke akun.
  • Token autentikasi cache telah habis masa berlakunya.

Aplikasi dapat menangani dua kasus pertama dengan mudah, biasanya cukup dengan menampilkan pesan error kepada pengguna. Jika jaringan tidak aktif atau pengguna memutuskan untuk tidak memberikan akses, tidak banyak yang dapat dilakukan aplikasi Anda terkait hal ini. Dua kasus terakhir sedikit lebih rumit, karena aplikasi yang berperilaku baik diharapkan dapat menangani kegagalan ini secara otomatis.

Kasus kegagalan ketiga, kredensial yang tidak memadai, dikomunikasikan melalui Bundle yang Anda terima dalam AccountManagerCallback (OnTokenAcquired dari contoh sebelumnya). Jika Bundle menyertakan Intent dalam kunci KEY_INTENT, pengautentikasi akan memberi tahu Anda bahwa ia perlu berinteraksi langsung dengan pengguna agar dapat memberi Anda token yang valid.

Ada banyak alasan mengapa pengautentikasi menampilkan Intent. Mungkin ini pertama kalinya pengguna login ke akun ini. Mungkin akun pengguna telah habis masa berlakunya dan mereka harus login lagi, atau mungkin kredensial yang mereka simpan salah. Mungkin akun tersebut memerlukan autentikasi 2 langkah atau perlu mengaktifkan kamera untuk melakukan pemindaian retina. Apa pun alasannya. Jika menginginkan token yang valid, Anda harus mengaktifkan Intent untuk mendapatkannya.

Kotlin

private inner class OnTokenAcquired : AccountManagerCallback<Bundle> {

    override fun run(result: AccountManagerFuture<Bundle>) {
        val launch: Intent? = result.getResult().get(AccountManager.KEY_INTENT) as? Intent
        if (launch != null) {
            startActivityForResult(launch, 0)
        }
    }
}

Java

private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
    @Override
    public void run(AccountManagerFuture<Bundle> result) {
        ...
        Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT);
        if (launch != null) {
            startActivityForResult(launch, 0);
            return;
        }
    }
}

Perhatikan bahwa contoh tersebut menggunakan startActivityForResult(), sehingga Anda dapat menangkap hasil Intent dengan mengimplementasikan onActivityResult() dalam aktivitas Anda sendiri. Hal ini penting: Jika tidak mengambil hasilnya dari Intent respons pengautentikasi, Anda tidak akan bisa mengetahui apakah pengguna telah berhasil diautentikasi.

Jika hasilnya adalah RESULT_OK, pengautentikasi telah memperbarui kredensial yang disimpan agar memadai untuk tingkat akses yang Anda minta, dan Anda harus memanggil AccountManager.getAuthToken() lagi untuk meminta token autentikasi baru.

Kasus terakhir, saat masa berlaku token telah berakhir, sebenarnya bukan kegagalan AccountManager. Satu-satunya cara untuk mengetahui apakah masa berlaku token telah berakhir adalah dengan menghubungi server, dan akan sia-sia serta mahal bagi AccountManager jika terus online untuk memeriksa status semua tokennya. Jadi, ini adalah kegagalan yang hanya dapat dideteksi ketika aplikasi seperti milik Anda mencoba menggunakan token autentikasi untuk mengakses layanan online.

Menghubungkan ke layanan online

Contoh di bawah ini menunjukkan cara membuat sambungan ke server Google. Karena Google menggunakan protokol OAuth2 standar industri untuk mengautentikasi permintaan, teknik yang dibahas di sini dapat diterapkan secara luas. Namun, perlu diingat bahwa setiap server berbeda. Anda mungkin perlu melakukan sedikit penyesuaian pada petunjuk ini untuk memperhitungkan situasi spesifik Anda.

Google API mengharuskan Anda memberikan empat nilai bersama setiap permintaan: kunci API, client ID, rahasia klien, dan kunci autentikasi. Tiga yang pertama berasal dari situs Konsol API Google. Yang terakhir adalah nilai string yang Anda peroleh dengan memanggil AccountManager.getAuthToken(). Anda meneruskannya ke Server Google sebagai bagian dari permintaan HTTP.

Kotlin

val url = URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=$your_api_key")
val conn = url.openConnection() as HttpURLConnection
conn.apply {
    addRequestProperty("client_id", your client id)
    addRequestProperty("client_secret", your client secret)
    setRequestProperty("Authorization", "OAuth $token")
}

Java

URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + your_api_key);
URLConnection conn = (HttpURLConnection) url.openConnection();
conn.addRequestProperty("client_id", your client id);
conn.addRequestProperty("client_secret", your client secret);
conn.setRequestProperty("Authorization", "OAuth " + token);

Jika permintaan ini menampilkan kode error HTTP 401, berarti token Anda ditolak. Seperti yang disebutkan di bagian terakhir, alasan paling umum terjadinya hal ini adalah masa berlaku token telah berakhir. Perbaikannya mudah: panggil AccountManager.invalidateAuthToken() dan ulangi proses akuisisi token sekali lagi.

Karena token yang habis masa berlakunya sudah biasa terjadi, dan memperbaikinya sangatlah mudah, banyak aplikasi berasumsi bahwa token telah habis masa berlakunya bahkan sebelum memintanya. Jika memperpanjang token adalah operasi yang murah bagi server, Anda dapat memanggil AccountManager.invalidateAuthToken() sebelum panggilan pertama ke AccountManager.getAuthToken(), dan tidak perlu meminta token autentikasi dua kali.