Implementasi Custom Driver Untuk Laravel Socialite
Laravel Socialite adalah paket Laravel resmi untuk otentikasi dengan penyedia OAuth. Kami mendukung otentikasi dengan Facebook, Twitter, LinkedIn, Google, GitHub, dan Bitbucket. Tetapi bagaimana jika Anda ingin menggunakan driver yang berbeda?
Contoh ini menggunakan AWS Cognito sebagai penyedia otentikasi. Dengan AWS Cognito, Anda dapat mengautentikasi dengan penyedia yang berbeda dan menyimpan data pengguna terpusat yang dapat Anda gunakan untuk aplikasi yang berbeda. Oleh karena itu, hal pertama yang harus dilakukan adalah menginstal Laravel Socialite sebagai berikut:
composer require laravel/socialite
Sekarang buat kelas `CognitoProvider` yang memanjang dari `\Socialite\Two\AbstractProvider`. Driver berfungsi seperti yang diharapkan karena kita perlu menerapkan metode berikut.
// ...
use Laravel\Socialite\Two\AbstractProvider;
class SocialiteCognitoProvider extends AbstractProvider
{
protected function getAuthUrl($state)
{
// TODO: Implement getAuthUrl() method.
}
protected function getTokenUrl()
{
// TODO: Implement getTokenUrl() method.
}
protected function getUserByToken($token)
{
// TODO: Implement getUserByToken() method.
}
protected function mapUserToObject(array $user)
{
// TODO: Implement mapUserToObject() method.
}
}
Anda perlu membuat redirect routes, berikut dokumentasi Laravel Socialite. Ini pada dasarnya memanggil metode redirect() dari driver yang dipilih, seperti ini:
use Laravel\Socialite\Facades\Socialite;
Route::get('/auth/redirect', function () {
return Socialite::driver('cognito')->redirect();
});
Metode redirect() ini secara internal memanggil metode getAuthUrl(), yang mengarahkan pengguna ke halaman otentikasi penyedia pihak ketiga. Oleh karena itu, Anda perlu menentukan URL ini dalam metode ini. Itu juga mengekstrak cara mendapatkan URL dasar dengan cara yang berbeda untuk digunakan di tempat yang berbeda.
/**
* @return string
*/
public function getCognitoUrl()
{
return config('services.cognito.base_uri') . '/oauth2';
}
/**
* @param string $state
*
* @return string
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase($this->getCognitoUrl() . '/authorize', $state);
}
Metode internal buildAuthUrlFromBase() membuat URL autentikasi dengan semua parameter yang diperlukan. Saat pengguna mengautentikasi dengan penyedia pihak ketiga, mereka dialihkan ke URL `callback` yang ditentukan dalam aplikasi. Tergantung pada apa yang ingin Anda lakukan dengan metode controller ini, Anda mungkin memanggil metode sosialite user() sebagai berikut:
Route::get('/auth/callback', function () {
$user = Socialite::driver('cognito')->user();
// $user->token
});
Saat Anda memanggil metode ini, metode getTokenUrl() dipanggil untuk mendapatkan token akses dengan kode yang ditentukan oleh parameter URL callback. Oleh karena itu, Anda perlu memberikan URL ini.
/**
* @return string
*/
protected function getTokenUrl()
{
return $this->getCognitoUrl() . '/token';
}
Sekarang kita memiliki token akses, kita bisa mendapatkan pengguna yang diautentikasi. Ini dilakukan dengan metode getUserByToken(). Dalam hal ini, Anda perlu membuat permintaan POST seperti berikut:
/**
* @param string $token
*
* @throws GuzzleException
*
* @return array|mixed
*/
protected function getUserByToken($token)
{
$response = $this->getHttpClient()->post($this->getCognitoUrl() . '/userInfo', [
'headers' => [
'cache-control' => 'no-cache',
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/x-www-form-urlencoded',
],
]);
return json_decode($response->getBody()->getContents(), true);
}
Terakhir, Anda perlu mendapatkan objek pengguna dari metode sebelumnya dan memetakan objek ini ke kelas Pengguna baru. Contoh ini menggunakan Laravel\Socialite\Two\User dan memetakan ke pengguna menggunakan mapUserToObject() sebagai berikut:
/**
* @return User
*/
protected function mapUserToObject(array $user)
{
return (new User())->setRaw($user)->map([
'id' => $user['sub'],
'email' => $user['email'],
'username' => $user['username'],
'email_verified' => $user['email_verified'],
'family_name' => $user['family_name'],
]);
}
Sekarang Anda dapat melakukan hal berikut dengan metode callback():
Route::get('/auth/callback', function () {
try {
$cognitoUser = Socialite::driver('cognito')->user();
$user = User::query()->whereEmail($cognitoUser->email)->first();
if (!$user) {
return redirect('login');
}
Auth::guard('web')->login($user);
return redirect(route('home'));
} catch (Exception $exception) {
return redirect('login');
}
});
Tergantung pada penyedianya, Anda mungkin perlu menambahkan beberapa cakupan ke permintaan autentikasi Anda. Lingkup adalah mekanisme yang membatasi akses pengguna ke suatu aplikasi.
AWS Cognito memiliki cakupan reservasi sistem. Cakupan ini adalah openid, email, telepon, profil, dan aws.cognito.signin.user.admin. Periksa di sini untuk informasi lebih lanjut tentang cakupan ini. Anda juga dapat membuat cakupan khusus di Cognito. Cek di sini untuk informasi lebih lanjut.
Di kelas SocialiteCognitoProvider, Anda dapat menentukan cakupan khusus dengan mengganti variabel internal $scopes dan $scopeSeparator sebagai berikut:
class SocialiteCognitoProvider extends AbstractProvider
{
/**
* @var string[]
*/
protected $scopes = [
'openid',
'profile',
'aws.cognito.signin.user.admin',
];
/**
* @var string
*/
protected $scopeSeparator = ' ';
// ...
}
Untuk informasi selengkapnya tentang AWS Cognito Scope, lihat dokumentasi resmi di sini. Kelas terakhir terlihat seperti ini:
// ...
use Laravel\Socialite\Two\User;
use GuzzleHttp\Exception\GuzzleException;
use Laravel\Socialite\Two\AbstractProvider;
class SocialiteCognitoProvider extends AbstractProvider
{
/**
* @var string[]
*/
protected $scopes = [
'openid',
'profile',
'aws.cognito.signin.user.admin',
];
/**
* @var string
*/
protected $scopeSeparator = ' ';
/**
* @return string
*/
public function getCognitoUrl()
{
return config('services.cognito.base_uri') . '/oauth2';
}
/**
* @param string $state
*
* @return string
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase($this->getCognitoUrl() . '/authorize', $state);
}
/**
* @return string
*/
protected function getTokenUrl()
{
return $this->getCognitoUrl() . '/token';
}
/**
* @param string $token
*
* @throws GuzzleException
*
* @return array|mixed
*/
protected function getUserByToken($token)
{
$response = $this->getHttpClient()->post($this->getCognitoUrl() . '/userInfo', [
'headers' => [
'cache-control' => 'no-cache',
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/x-www-form-urlencoded',
],
]);
return json_decode($response->getBody()->getContents(), true);
}
/**
* @return User
*/
protected function mapUserToObject(array $user)
{
return (new User())->setRaw($user)->map([
'id' => $user['sub'],
'email' => $user['email'],
'username' => $user['username'],
'email_verified' => $user['email_verified'],
'family_name' => $user['family_name'],
]);
}
}
Tapi bagaimana Socialite mengenali driver? Anda perlu menambahkan kode ke `AppServiceProvider`.
// ...
use Laravel\Socialite\Contracts\Factory;
/**
* @throws BindingResolutionException
*/
public function boot()
{
$socialite = $this->app->make(Factory::class);
$socialite->extend('cognito', function () use ($socialite) {
$config = config('services.cognito');
return $socialite->buildProvider(SocialiteCognitoProvider::class, $config);
});
}
Metode boot mendaftarkan driver dengan manajer Socialite, jadi memanggil Socialite::driver('cognito') membuat instance kelas SocialiteCognitoProvider.
itu saja! Ini adalah cara mengimplementasikan driver kustom baru untuk Laravel Socialite. Untuk membuat hidup Anda lebih mudah, kami telah membuat paket kecil untuk driver Cognito kustom Anda. Anda dapat melihat ini di sini.