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 tersebut—mereka harus memberikan bukti identitasnya. Untuk aplikasi yang mengakses layanan pihak ketiga, masalah keamanan menjadi lebih rumit. Pengguna tidak hanya perlu diautentikasi untuk mengakses layanan, tetapi aplikasi juga harus diotorisasi untuk bertindak atas nama pengguna.

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

OAuth2 baik digunakan untuk:

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

Mengumpulkan informasi

Untuk mulai menggunakan OAuth2, Anda perlu mengetahui beberapa hal tentang API 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 baca saja ke Google Tasks adalah View your tasks, sementara cakupan autentikasi untuk akses baca/tulis ke Google Tasks adalah Manage your tasks.
  • Client ID dan rahasia klien, yang merupakan string yang mengidentifikasi aplikasi Anda ke layanan. Anda harus mendapatkan string ini langsung dari pemilik layanan. Google memiliki sistem swalayan untuk mendapatkan ID dan rahasia klien. Artikel Mengotorisasi dan Menggunakan REST API menjelaskan cara menggunakan sistem ini dalam mendapatkan nilai tersebut untuk digunakan dengan Google Tasks API.

Meminta izin internet

Untuk aplikasi yang menargetkan Android 6.0 (API level 23) dan yang lebih baru, metode getAuthToken() itu sendiri tidak memerlukan izin apa pun. Namun, untuk menjalankan operasi pada token, Anda harus menambahkan izin INTERNET ke file manifes, seperti 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. Ini berarti, daripada melakukan semua pekerjaan autentikasi dalam satu fungsi, Anda harus mengimplementasikannya sebagai serangkaian callback.

Cuplikan berikut menunjukkan cara menangani 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 beserta 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 valid dalam kunci KEY_AUTHTOKEN dan Anda siap melanjutkan. Namun, segala sesuatunya tidak selalu berjalan lancar...

Meminta token autentikasi lagi

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. Dua kasus terakhir sedikit lebih rumit, karena aplikasi yang berperilaku baik diharapkan dapat menangani kegagalan ini secara otomatis.

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

Ada banyak alasan mengapa pengautentikasi menampilkan Intent. Mungkin itu 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 harus mengaktifkan kamera untuk melakukan pemindaian retina. Alasannya memang tidak terlalu penting. 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 memperoleh hasil Intent dengan mengimplementasikan onActivityResult() dalam aktivitas Anda sendiri. Ini penting! Jika tidak memperoleh hasil dari Intent respons pengautentikasi, Anda tidak akan bisa mengetahui apakah pengguna telah berhasil diautentikasi atau tidak. Jika hasilnya adalah RESULT_OK, maka pengautentikasi telah mengupdate kredensial tersimpan sehingga memadai untuk level akses yang Anda minta, dan Anda harus memanggil AccountManager.getAuthToken() sekali lagi untuk meminta token autentikasi baru.

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

Membuat sambungan 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 berlaku secara luas. Namun, perhatikan bahwa setiap server berbeda. Anda mungkin perlu melakukan sedikit penyesuaian pada petunjuk ini untuk memperhitungkan situasi khusus Anda.

Google API mengharuskan Anda memberikan empat nilai bersama setiap permintaan: kunci API, ID klien, 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(). Semua nilai ini harus diteruskan 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 habis. Cara memperbaikinya mudah: panggil AccountManager.invalidateAuthToken() dan ulangi proses akuisisi token sekali lagi.

Karena token yang habis masa berlakunya merupakan kejadian wajar, dan perbaikannya sangat mudah, banyak aplikasi akan berasumsi bahwa token telah habis masa berlakunya bahkan sebelum menanyakannya. Jika memperpanjang masa berlaku token merupakan operasi ringan bagi server Anda, sebaiknya panggil AccountManager.invalidateAuthToken() terlebih dahulu sebelum panggilan pertama ke AccountManager.getAuthToken(), lalu siapkan token autentikasi dua kali.