目標
Laravelで「Googleでログイン」を実装する際に導入する"Socialite"がどんなことをやっているのか調査してみた。
①リダイレクト編の目標は、Controllerに記述することになる下記コードが最終的なアウトプットは何か、どのように実装されているのかを明確にする。
以下Controllerに記述することになるコード
/**
* Googleアカウントの認証ページへユーザーをリダイレクト
* ①リダイレクト編で解説するコード
*/
public function redirectToGoogle()
{
return Socialite::driver('google')->redirect();
}
/**
* Googleアカウントからユーザー情報を取得
* ②コールバック編で解説予定
*/
public function handleGoogleCallback()
{
$user = Socialite::driver('google')->stateless()->user();
}
先に結論(最終的なアウトプット)
認可URLにリダイレクトするRedirectResponseインスタンスを生成します。
リダイレクト先のURLは下記の通りです。
RequestURL = https://accounts.google.com/o/oauth2/auth?
client_id=$client_id
&redirect_url=$redirect_url
&scope=$scope1+$scope2+$scope3
&response_type=code
&state=$state
<環境>
Laravel Framework 8.17.2
laravel/socialite v5.1.2
<解説>
Socialite
SocialiteのファサードはSocialiteServiceProviderで下記のように定義されています。
namespace Laravel\Socialite;
class SocialiteServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton(Factory::class, function ($app) {
return new SocialiteManager($app);
});
}
}
Socialite::driver('google')
driverメソッドに文字列でドライバを指定しています。
Socialiteクラスの基底クラスであるManagerクラスにdriverメソッドで引数に渡した文字列に該当するドライバが可変関数として呼び出されます。
namespace Illuminate\Support;
abstract class Manager
{
/**
* Get a driver instance.
*
* @param string|null $driver
* @return mixed
*
* @throws \InvalidArgumentException
*/
public function driver($driver = null)
{
$driver = $driver ?: $this->getDefaultDriver();
if (is_null($driver)) {
throw new InvalidArgumentException(sprintf(
'Unable to resolve NULL driver for [%s].', static::class
));
}
// ドライバのインスタンスはキャッシュして再利用される。
if (! isset($this->drivers[$driver])) {
$this->drivers[$driver] = $this->createDriver($driver);
}
return $this->drivers[$driver];
}
/**
* Create a new driver instance.
*
* @param string $driver
* @return mixed
*
* @throws \InvalidArgumentException
*/
protected function createDriver($driver)
{
if (isset($this->customCreators[$driver])) {
return $this->callCustomCreator($driver);
} else {
$method = 'create'.Str::studly($driver).'Driver';
if (method_exists($this, $method)) {
// ここでcreateGoogleDriver()が呼び出される
return $this->$method();
}
}
throw new InvalidArgumentException("Driver [$driver] not supported.");
}
}
createGoogleDriver()
createGoogleDriverメソッドではconfigファイル(services.google)に設定した情報を取得し、GoogleProviderクラスのインスタンスを生成しています。
namespace Laravel\Socialite;
class SocialiteManager extends Manager implements Contracts\Factory
{
/**
* Create an instance of the specified driver.
*
* @return \Laravel\Socialite\Two\AbstractProvider
*/
protected function createGoogleDriver()
{
$config = $this->config->get('services.google');
return $this->buildProvider(
GoogleProvider::class, $config
);
}
}
AbstractProviderを継承するGoogleProviderインスタンスを生成するまでがSocialite::driver('google')の動きです。
Socialite::driver('google')->redirect()
redirectメソッドでは生成されたリダイレクトURLにリダイレクトするようにRedirectResponseクラスのインスタンスを生成している。
namespace Laravel\Socialite\Two;
abstract class AbstractProvider implements ProviderContract
{
/**
* Redirect the user of the application to the provider's authentication screen.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function redirect()
{
$state = null;
if ($this->usesState()) {
$this->request->session()->put('state', $state = $this->getState());
}
// ここでRedirectResponseクラスのインスタンスを生成
return new RedirectResponse($this->getAuthUrl($state));
}
}
getAuthUrl()
getAuthUrlメソッドはGoogleProviderで定義されている
namespace Laravel\Socialite\Two;
use Illuminate\Support\Arr;
class GoogleProvider extends AbstractProvider implements ProviderInterface
{
/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://accounts.google.com/o/oauth2/auth', $state);
}
}
AbstractProviderに戻ってURLがどのように生成されるか見ていく。
GoogleのベースURLにクエリパラメータとして認可に必要な各項目をセットしている。
/**
* Build the authentication URL for the provider from the given base URL.
*
* @param string $url
* @param string $state
* @return string
*/
protected function buildAuthUrlFromBase($url, $state)
{
// $this->encodingTypeはPHP_QUERY_RFC1738が定義されている
return $url.'?'.http_build_query($this->getCodeFields($state), '', '&', $this->encodingType);
}
/**
* Get the GET parameters for the code request.
*
* @param string|null $state
* @return array
*/
protected function getCodeFields($state = null)
{
$fields = [
'client_id' => $this->clientId,
'redirect_uri' => $this->redirectUrl,
'scope' => $this->formatScopes($this->getScopes(), $this->scopeSeparator),
'response_type' => 'code',
];
if ($this->usesState()) {
$fields['state'] = $state;
}
return array_merge($fields, $this->parameters);
}
/**
* Format the given scopes.
*
* @param array $scopes
* @param string $scopeSeparator
* @return string
*/
protected function formatScopes(array $scopes, $scopeSeparator)
{
return implode($scopeSeparator, $scopes);
}
}
<おまけ>認可リクエストのカスタマイズ
Laravel公式にもあるようにパラメータや、スコープの追加をすることができる。
パラメータの追加
パラメータを追加するにはwithメソッドを使用します。
$parametersの規定値は空の配列[]です。
/**
* Set the custom parameters of the request.
*
* @param array $parameters
* @return $this
*/
public function with(array $parameters)
{
$this->parameters = $parameters;
return $this;
}
scopeの追加
scopeに規定値は['openid','profile','email']が定義されている。
追加するには下記のようにscopesメソッドを呼び出すか、setScopeメソッドで上書きするとよい。
/**
* Googleアカウントの認証ページへユーザーをリダイレクト
*/
public function redirectToGoogle()
{
return Socialite::driver('google')->scopes(['read:user', 'public_repo'])->redirect();
}
メソッドの中身は下記のように配列がマージされる。
array_uniqueメソッドでは、重複した要素が存在する場合、はじめの要素のキーと値が保持されます。
https://www.php.net/manual/ja/function.array-unique.php
/**
* Merge the scopes of the requested access.
*
* @param array|string $scopes
* @return $this
*/
public function scopes($scopes)
{
//
$this->scopes = array_unique(array_merge($this->scopes, (array) $scopes));
return $this;
}
/**
* Set the scopes of the requested access.
*
* @param array|string $scopes
* @return $this
*/
public function setScopes($scopes)
{
$this->scopes = array_unique((array) $scopes);
return $this;
}
リダイレクトURLの変更
リダイレクトURLが複数存在し、configファイルではなく動的に値を変更したい場合はredirectURLメソッドを使用します。
/**
* Googleアカウントの認証ページへユーザーをリダイレクト
*/
public function redirectToGoogle()
{
return Socialite::driver('google')->redirectURL($redirectUrl)->redirect();
}
/**
* Set the redirect URL.
*
* @param string $url
* @return $this
*/
public function redirectUrl($url)
{
$this->redirectUrl = $url;
return $this;
}