Bagaimana Mengintegrasikan OAuth 2 Ke Dalam Back-end Django/DRF Anda Tanpa Menjadi Gila
Diterbitkan: 2022-03-11Kita semua pernah ke sana. Anda sedang mengerjakan back-end API, dan Anda senang dengan hasilnya. Anda baru saja menyelesaikan produk minimal yang layak (MVP), semua tes telah berlalu, dan Anda berharap dapat menerapkan beberapa fitur baru.
Kemudian bos mengirimi Anda email: “Omong-omong, kita perlu membiarkan orang masuk melalui Facebook dan Google; mereka tidak harus membuat akun hanya untuk situs kecil seperti milik kami.”
Besar. Scope creep menyerang lagi.
Kabar baiknya adalah bahwa OAuth 2 telah muncul sebagai standar industri untuk autentikasi sosial dan pihak ketiga (digunakan oleh layanan seperti Facebook, Google, dll.) sehingga Anda dapat fokus untuk memahami dan menerapkan standar tersebut untuk mendukung berbagai penyedia otentikasi.
Sepertinya Anda tidak terbiasa dengan OAuth 2; Saya tidak, ketika ini terjadi pada saya.
Sebagai pengembang Python, insting Anda mungkin mengarahkan Anda ke pip, alat yang direkomendasikan Indeks Paket Python (PyPA) untuk menginstal paket Python. Kabar buruknya adalah bahwa pip mengetahui tentang 278 paket yang berhubungan dengan OAuth – 53 di antaranya secara spesifik menyebutkan Django. Ini adalah pekerjaan seminggu hanya untuk meneliti opsi, apalagi mulai menulis kode.
Dalam tutorial ini, Anda akan belajar bagaimana mengintegrasikan OAuth 2 ke dalam Django atau Django Rest Framework menggunakan Python Social Auth. Meskipun artikel ini berfokus pada Kerangka Django REST, Anda dapat menerapkan informasi yang disediakan di sini untuk mengimplementasikan hal yang sama dalam berbagai kerangka kerja back-end umum lainnya.
Ikhtisar singkat tentang alur OAuth 2
OAuth 2 dirancang sejak awal sebagai protokol autentikasi web. Ini tidak persis sama seperti jika telah dirancang sebagai protokol otentikasi bersih; itu mengasumsikan bahwa alat seperti rendering HTML dan pengalihan browser tersedia untuk Anda.
Ini jelas merupakan penghalang untuk API berbasis JSON, tetapi Anda dapat mengatasinya.
Anda akan menjalani prosesnya seolah-olah Anda sedang menulis situs web sisi server tradisional.
Alur OAuth 2 sisi Server
Langkah pertama terjadi di luar aliran aplikasi sepenuhnya. Pemilik proyek harus mendaftarkan aplikasi Anda ke setiap penyedia OAuth 2 yang Anda perlukan untuk masuk.
Selama pendaftaran ini, mereka memberikan URI panggilan balik kepada penyedia OAuth 2 , di mana aplikasi Anda akan tersedia untuk menerima permintaan. Sebagai gantinya, mereka menerima kunci klien dan rahasia klien . Token ini dipertukarkan selama proses otentikasi untuk memvalidasi permintaan login.
Token merujuk ke kode server Anda sebagai klien. Host adalah penyedia OAuth 2. Mereka tidak dimaksudkan untuk klien API Anda.
Alur dimulai saat aplikasi Anda membuat halaman yang menyertakan tombol, seperti "Masuk dengan Facebook" atau "Masuk dengan Google+". Pada dasarnya, ini tidak lain adalah tautan sederhana, yang masing-masing mengarah ke URL seperti berikut:
https://oauth2provider.com/auth? response_type=code& client_id=CLIENT_KEY& redirect_uri=CALLBACK_URI& scope=profile& scope=email
(Catatan: Pemisahan baris dimasukkan ke dalam URI di atas agar mudah dibaca.)
Anda telah memberikan kunci klien Anda dan mengarahkan ulang URI, tetapi tidak ada rahasia. Sebagai gantinya, Anda telah memberi tahu server bahwa Anda menginginkan kode otentikasi sebagai tanggapan dan akses ke cakupan 'profil' dan 'email'. Cakupan ini menentukan izin yang Anda minta dari pengguna, dan membatasi otorisasi token akses yang Anda terima.
Setelah diterima, browser pengguna diarahkan ke halaman dinamis yang dikontrol oleh penyedia OAuth 2. Penyedia OAuth 2 memverifikasi bahwa URI panggilan balik dan kunci klien cocok satu sama lain sebelum melanjutkan. Jika ya, alurnya secara singkat menyimpang tergantung pada token sesi pengguna.
Jika pengguna saat ini tidak masuk ke layanan itu, mereka akan diminta untuk melakukannya. Setelah mereka masuk, pengguna disajikan dengan dialog yang meminta izin untuk mengizinkan aplikasi Anda masuk.
Dengan asumsi pengguna menyetujui, server OAuth 2 kemudian mengarahkan mereka kembali ke URI panggilan balik yang Anda berikan, termasuk kode otorisasi dalam parameter kueri: GET https://api.yourapp.com/oauth2/callback/?code=AUTH_CODE
.
Kode otorisasi adalah token sekali pakai yang cepat kedaluwarsa; segera setelah diterima, server Anda harus berbalik, dan membuat permintaan lain ke penyedia OAuth 2, termasuk kode otorisasi dan rahasia klien Anda:
POST https://oauth2provider.com/token/? grant_type=authorization_code& code=AUTH_CODE& redirect_uri=CALLBACK_URI& client_id=CLIENT_KEY& client_secret=CLIENT_SECRET
Tujuan dari kode otorisasi ini adalah untuk mengautentikasi permintaan POST di atas, tetapi karena sifat alirannya, itu harus dirutekan melalui sistem pengguna. Dengan demikian, secara inheren tidak aman.
Pembatasan pada kode otorisasi (yaitu, kode itu kedaluwarsa dengan cepat dan hanya dapat digunakan sekali) ada untuk mengurangi risiko bawaan melewati kredensial otentikasi melalui sistem yang tidak tepercaya.
Panggilan ini, dibuat langsung dari server Anda ke server penyedia OAuth 2, adalah komponen kunci dari proses login sisi server OAuth 2. Mengontrol panggilan berarti Anda mengetahui bahwa panggilan tersebut aman dari TLS, sehingga membantu melindunginya dari serangan penyadapan.
Menyertakan kode otorisasi memastikan bahwa pengguna secara eksplisit memberikan persetujuan. Menyertakan rahasia klien, yang tidak pernah terlihat oleh pengguna Anda, memastikan bahwa permintaan ini tidak berasal dari beberapa virus atau malware di sistem pengguna, yang mencegat kode otorisasi.
Jika semuanya cocok, server mengembalikan token akses , yang dengannya Anda dapat melakukan panggilan ke penyedia itu saat diautentikasi sebagai pengguna.
Setelah Anda menerima token akses dari server, server Anda kemudian mengalihkan browser pengguna sekali lagi ke halaman arahan untuk pengguna yang baru saja masuk. Adalah umum untuk menyimpan token akses di cache sesi sisi server pengguna, jadi bahwa server dapat melakukan panggilan ke penyedia sosial yang diberikan kapan pun diperlukan.
Token akses tidak boleh tersedia untuk pengguna!
Ada lebih banyak detail yang bisa kita selami.
Misalnya, Google menyertakan token penyegaran yang memperpanjang masa pakai token akses Anda sementara Facebook menyediakan titik akhir di mana Anda dapat menukar token akses yang berumur pendek dengan sesuatu yang berumur lebih panjang. Namun, detail ini tidak penting bagi kami, karena kami tidak akan menggunakan alur ini.
Alur ini tidak praktis untuk REST API. Meskipun Anda dapat meminta klien front-end membuat halaman login awal dan meminta back-end memberikan URL panggilan balik, pada akhirnya Anda akan mengalami masalah. Anda ingin mengarahkan pengguna ke halaman arahan front-end setelah Anda menerima token akses, dan tidak ada cara yang jelas dan tenang untuk melakukannya.
Untungnya, ada aliran OAuth 2 lain yang tersedia, yang bekerja jauh lebih baik dalam kasus ini.
Alur OAuth 2 sisi Klien
Dalam alur ini, front-end bertanggung jawab untuk menangani seluruh proses OAuth 2. Ini umumnya menyerupai aliran sisi server, dengan pengecualian penting – ujung depan hidup di mesin yang dikendalikan pengguna, sehingga mereka tidak dapat dipercayakan dengan rahasia klien. Solusinya adalah dengan menghilangkan seluruh langkah proses tersebut.
Langkah pertama, seperti pada alur sisi server, adalah mendaftarkan aplikasi.
Dalam hal ini, pemilik proyek tetap mendaftarkan aplikasi, tetapi sebagai aplikasi web. Penyedia OAuth 2 akan tetap memberikan kunci klien , tetapi mungkin tidak memberikan rahasia klien apa pun.
Front-end menyediakan tombol login sosial kepada pengguna, yang mengarahkan ke halaman web yang dikontrol oleh penyedia OAuth 2, dan meminta izin aplikasi kami untuk mengakses aspek tertentu dari profil pengguna.
URL terlihat sedikit berbeda kali ini:
https://oauth2provider.com/auth? response_type=token& client_id=CLIENT_KEY& redirect_uri=CALLBACK_URI& scope=profile& scope=email
Perhatikan bahwa parameter response_type
kali ini di URL adalah token
.
Jadi bagaimana dengan redirect URI?
Ini hanyalah alamat apa pun di front-end yang disiapkan untuk menangani token akses dengan tepat.
Bergantung pada pustaka OAuth 2 yang digunakan, front-end sebenarnya dapat menjalankan server sementara yang mampu menerima permintaan HTTP di perangkat pengguna; dalam hal ini, URL pengalihan dalam bentuk http://localhost:7862/callback/?token=TOKEN
.
Karena server OAuth 2 mengembalikan pengalihan HTTP setelah pengguna menerima, dan pengalihan ini diproses oleh browser di perangkat pengguna, alamat ini ditafsirkan dengan benar, memberikan akses front-end ke token.
Atau, front-end dapat langsung mengimplementasikan halaman yang sesuai. Either way, front-end bertanggung jawab, pada titik ini, untuk mengurai parameter kueri dan memproses token akses.
Mulai saat ini, front-end dapat langsung memanggil API penyedia OAuth 2 menggunakan token. Tetapi pengguna tidak benar-benar menginginkan itu; mereka ingin akses terautentikasi ke API Anda. Yang perlu disediakan oleh back-end adalah titik akhir di mana front-end dapat menukar token akses penyedia sosial dengan token yang memberikan akses ke API Anda.
Mengapa mengizinkan ini sama sekali, mengingat bahwa memasok token akses ke front-end secara inheren kurang aman daripada aliran sisi server?
Alur sisi klien memungkinkan pemisahan yang lebih ketat antara REST API back-end dan front-end yang menghadap pengguna. Tidak ada yang benar-benar menghentikan Anda untuk menentukan server back-end Anda sebagai URI redirect; efek akhirnya akan menjadi semacam aliran hibrida.

Masalahnya adalah bahwa server kemudian harus menghasilkan halaman yang menghadap pengguna yang sesuai, dan kemudian mengembalikan kontrol ke front-end dengan cara tertentu.
Sudah umum dalam proyek modern untuk memisahkan secara ketat masalah antara UI front-end dan back-end yang menangani semua logika bisnis. Mereka biasanya berkomunikasi melalui JSON API yang terdefinisi dengan baik. Aliran hybrid yang dijelaskan di atas memperumit pemisahan masalah, memaksa back-end untuk melayani halaman yang menghadap pengguna, dan kemudian merancang beberapa aliran untuk entah bagaimana mengontrol kembali ke front-end.
Mengizinkan front-end untuk menangani token akses adalah teknik bijaksana yang mempertahankan pemisahan masalah. Ini agak meningkatkan risiko dari klien yang dikompromikan, tetapi bekerja dengan baik, secara umum.
Alur ini mungkin tampak rumit untuk front-end, dan memang demikian, jika Anda memerlukan tim front-end untuk mengembangkan semuanya sendiri. Namun, baik Facebook dan Google menyediakan perpustakaan yang memungkinkan front-end menyertakan tombol login yang menangani seluruh proses dengan konfigurasi minimal.
Berikut adalah resep untuk pertukaran token di bagian belakang.
Di bawah aliran klien, back-end cukup terisolasi dari proses OAuth 2. Jangan disesatkan: Ini bukan pekerjaan sederhana. Anda akan menginginkannya untuk mendukung setidaknya fungsi berikut.
- Kirim setidaknya satu permintaan ke penyedia OAuth 2, hanya untuk memastikan bahwa token yang disediakan front-end valid, bukan string acak sembarang.
- Saat token valid, kembalikan token yang valid untuk API Anda. Jika tidak, kembalikan kesalahan informatif.
- Jika ini adalah pengguna baru, buat model
User
untuk mereka, dan isi dengan tepat. - Jika ini adalah pengguna yang model
User
sudah ada, cocokkan dengan alamat email mereka, sehingga mereka mendapatkan akses ke akun yang ada yang benar alih-alih membuat yang baru untuk login sosial. - Perbarui detail profil pengguna berdasarkan apa yang mereka berikan di media sosial.
Kabar baiknya adalah bahwa menerapkan semua fungsi ini di back-end jauh lebih sederhana dari yang Anda harapkan.
Inilah keajaiban bagaimana membuat semua ini bekerja di back-end hanya dalam dua lusin baris kode. Ini tergantung pada pustaka Python Social Auth (“PSA” untuk selanjutnya), jadi Anda harus menyertakan baik social-auth-core
dan social-auth-app-django
di requirements.txt
Anda.
Anda juga harus mengonfigurasi pustaka seperti yang didokumentasikan di sini. Perhatikan bahwa ini mengecualikan beberapa penanganan pengecualian untuk kejelasan.
Kode lengkap untuk contoh ini dapat ditemukan di sini.
@api_view(http_method_names=['POST']) @permission_classes([AllowAny]) @psa() def exchange_token(request, backend): serializer = SocialSerializer(data=request.data) if serializer.is_valid(raise_exception=True): # This is the key line of code: with the @psa() decorator above, # it engages the PSA machinery to perform whatever social authentication # steps are configured in your SOCIAL_AUTH_PIPELINE. At the end, it either # hands you a populated User model of whatever type you've configured in # your project, or None. user = request.backend.do_auth(serializer.validated_data['access_token']) if user: # if using some other token back-end than DRF's built-in TokenAuthentication, # you'll need to customize this to get an appropriate token object token, _ = Token.objects.get_or_create(user=user) return Response({'token': token.key}) else: return Response( {'errors': {'token': 'Invalid token'}}, status=status.HTTP_400_BAD_REQUEST, )
Hanya ada sedikit lagi yang perlu masuk ke pengaturan Anda (kode lengkap), dan kemudian Anda siap:
AUTHENTICATION_BACKENDS = ( 'social_core.backends.google.GoogleOAuth2', 'social_core.backends.facebook.FacebookOAuth2', 'django.contrib.auth.backends.ModelBackend', ) for key in ['GOOGLE_OAUTH2_KEY', 'GOOGLE_OAUTH2_SECRET', 'FACEBOOK_KEY', 'FACEBOOK_SECRET']: # Use exec instead of eval here because we're not just trying to evaluate a dynamic value here; # we're setting a module attribute whose name varies. exec("SOCIAL_AUTH_{key} = os.environ.get('{key}')".format(key=key)) SOCIAL_AUTH_PIPELINE = ( 'social_core.pipeline.social_auth.social_details', 'social_core.pipeline.social_auth.social_uid', 'social_core.pipeline.social_auth.auth_allowed', 'social_core.pipeline.social_auth.social_user', 'social_core.pipeline.user.get_username', 'social_core.pipeline.social_auth.associate_by_email', 'social_core.pipeline.user.create_user', 'social_core.pipeline.social_auth.associate_user', 'social_core.pipeline.social_auth.load_extra_data', 'social_core.pipeline.user.user_details', )
Tambahkan pemetaan ke fungsi ini di urls.py
Anda, dan Anda sudah siap!
Bagaimana cara kerja sihir itu?
Python Social Auth adalah mesin yang sangat keren dan sangat kompleks. Sangat senang untuk menangani otentikasi dan akses ke salah satu dari beberapa lusin penyedia auth sosial, dan bekerja pada kerangka kerja web Python paling populer, termasuk Django, Flask, Pyramid, CherryPy, dan WebPy.
Untuk sebagian besar, kode di atas adalah tampilan berbasis fungsi Django REST framework (DRF) yang sangat standar: Ini mendengarkan permintaan POST di jalur mana pun yang Anda petakan di urls.py
Anda dan, dengan asumsi Anda mengirimkannya permintaan di format yang diharapkan, kemudian memberi Anda objek User
, atau None
.
Jika Anda mendapatkan objek User
, itu adalah tipe model yang telah Anda konfigurasikan di tempat lain dalam proyek Anda, yang mungkin sudah ada atau belum. PSA sudah menangani validasi token, mengidentifikasi apakah ada kecocokan pengguna atau tidak, membuat pengguna jika perlu, dan memperbarui detail pengguna dari penyedia sosial.
Detail yang tepat tentang bagaimana pengguna dipetakan dari pengguna penyedia sosial ke pengguna Anda, dan terkait dengan pengguna yang ada, ditentukan oleh SOCIAL_AUTH_PIPELINE
yang ditentukan di atas. Masih banyak yang harus dipelajari tentang cara kerja semua ini, tetapi itu di luar cakupan posting ini. Anda dapat membaca lebih lanjut tentang itu di sini.
Kunci keajaibannya adalah dekorator @psa()
pada tampilan, yang menambahkan beberapa anggota ke objek request
yang diteruskan ke tampilan Anda. Yang paling menarik bagi kami adalah request.backend
(untuk PSA, backend adalah penyedia otentikasi sosial).
Back-end yang sesuai dipilih untuk kami dan ditambahkan ke objek request
berdasarkan argumen backend
ke tampilan, yang diisi oleh URL itu sendiri.
Setelah Anda memiliki objek backend
, sangat senang untuk mengautentikasi Anda terhadap penyedia itu, dengan kode akses Anda; itulah metode do_auth
. Ini, pada gilirannya, melibatkan keseluruhan SOCIAL_AUTH_PIPELINE
dari file konfigurasi Anda.
Pipeline dapat melakukan beberapa hal yang cukup kuat jika Anda memperluasnya, meskipun ia sudah melakukan semua yang Anda perlukan hanya dengan fungsionalitas bawaan bawaannya.
Setelah itu, kembali ke kode DRF normal: jika Anda mendapatkan objek User
yang valid, Anda dapat dengan mudah mengembalikan token API yang sesuai. Jika Anda tidak mendapatkan kembali objek User
yang valid, mudah untuk menghasilkan kesalahan.
Salah satu kelemahan teknik ini adalah meskipun relatif mudah untuk mengembalikan kesalahan jika terjadi, sulit untuk mendapatkan banyak wawasan tentang apa yang salah secara spesifik. PSA menelan detail apa pun yang mungkin dikembalikan server tentang apa masalahnya.
Kemudian lagi, adalah sifat sistem otentikasi yang dirancang dengan baik untuk menjadi cukup buram tentang sumber kesalahan. Jika sebuah aplikasi pernah memberi tahu pengguna "Kata Sandi Tidak Valid" setelah upaya login, itu sama saja dengan mengatakan "Selamat! Anda telah menebak nama pengguna yang valid.”
Mengapa tidak menggulung sendiri?
Singkatnya: ekstensibilitas. Sangat sedikit penyedia OAuth 2 sosial yang memerlukan atau mengembalikan informasi yang sama persis dalam panggilan API mereka dengan cara yang persis sama. Ada semua jenis kasus khusus dan pengecualian.
Menambahkan penyedia sosial baru setelah Anda menyiapkan PSA adalah masalah beberapa baris konfigurasi di file pengaturan Anda. Anda tidak perlu menyesuaikan kode sama sekali. PSA mengabstraksikan semua itu, sehingga Anda dapat fokus pada aplikasi Anda sendiri.
Bagaimana cara saya menguji ini?
Pertanyaan bagus! unittest.mock
tidak cocok untuk mengejek panggilan API yang terkubur di bawah lapisan abstraksi jauh di dalam perpustakaan; hanya menemukan jalan yang tepat untuk mengejek akan membutuhkan banyak usaha.
Sebaliknya, karena PSA dibangun di atas perpustakaan Permintaan, Anda menggunakan perpustakaan Respons yang sangat baik untuk mengejek penyedia di tingkat HTTP.
Diskusi lengkap tentang pengujian berada di luar cakupan artikel ini, tetapi contoh pengujian kami disertakan di sini. Fungsi khusus yang perlu diperhatikan adalah manajer konteks yang mocked
dan kelas SocialAuthTests
.
Biarkan PSA melakukan angkat berat.
Proses OAuth2 terperinci dan rumit dengan banyak kerumitan yang melekat. Untungnya, sebagian besar kerumitan itu dapat dilewati dengan menghadirkan perpustakaan yang didedikasikan untuk menanganinya dengan cara yang semudah mungkin.
Python Social Auth melakukan pekerjaan yang bagus dalam hal itu. Kami telah mendemonstrasikan tampilan Django/DRF yang menggunakan aliran OAuth2 sisi klien, implisit, untuk mendapatkan pembuatan pengguna yang mulus dan pencocokan hanya dalam 25 baris kode. Itu tidak terlalu buruk.