Symfony OpenIdを取得してみた (自作編

背景

近頃Webシステムは、ログイン情報を持たない(お客様にアカウントを多重管理させない)ことが支流になりつつある。
その問題を解決するのが、Single Sigh on(SSO)と呼ばれている。
今回は、SymfonyでSSOについて調べてみました!!!
ぜひ、Symfony開発者は参考にしてみてください!!!!!

 

環境情報
  • symfony3.4以上
  • php7.1.3 以上

 

参考サイト

 

認証用コントローラーを作成

use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\SSOBundle\Client\Yahoo;

class YahooController extends Controller
{
/**
* Link to this controller to start the "connect" process
*/
// public function connectAction(ClientRegistry $clientRegistry)
public function connectAction()
{
 // on Symfony 3.3 or lower, $clientRegistry = $this->get('knpu.oauth2.registry');

 // will redirect to Google!
 $clientRegistry = $this->get('knpu.oauth2.registry');

 $provider = new Yahoo([
  'client_id' => 'クライアントID',
  'client_secret' =>'シークレット',
 ]);

 return $this->redirect($provider->getAuthorizationUrl([
  'redirect_uri' => 'rediret_uri',
  'scope' => ['openid', 'profile']
 ]));
}

/**
* After going to Facebook, you're redirected back here
* because this is the "redirect_route" you configured
*/
public function connectCheckAction(Request $request)
{
 // ** if you want to *authenticate* the user, then
 // leave this method blank and create a Guard authenticator
 // (read below)
 $provider = new Yahoo([
  'client_id' => 'クライアントID',
  'client_secret' =>'シークレット',
  'scope' => ['openid', 'profile']
 ]);

 $accessToken = $provider->getAccessToken('authorization_code', [
  'code' => $request->get('code'),
  'redirect_uri' => 'redirect_uri',
  'scope' => ['openid', 'profile']
 ]);

 $ownerDetails = $provider->getResourceOwner($accessToken);

 return new JsonResponse($ownerDetails); // subがopenIdになります。
}

認証用のクラスを作成

https://github.com/hayageek/oauth2-yahoo // Yahooの認証用

真似して作成しました。(なぜかYahoo Japan!は対応していませんでした。泣)

use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpFoundation\Request;

class Yahoo extends AbstractProvider
{
 use BearerAuthorizationTrait;

 const ACCESS_TOKEN_RESOURCE_OWNER_ID = 'kzl_xoauth_yahoo_guid';
 /**
 * Constructs an OAuth 2.0 service provider.
 *
 * @param array $options An array of options to set on this provider.
 * Options include `clientId`, `clientSecret`, `redirectUri`, and `state`.
 * Individual providers may introduce more options, as needed.
 * @param array $collaborators An array of collaborators that may be used to
 * override this provider's default behavior. Collaborators include
 * `grantFactory`, `requestFactory`, and `httpClient`.
 * Individual providers may introduce more collaborators, as needed.
 */
 public function __construct(array $options = [], array $collaborators = [])
 {
  parent::__construct();

  if (!empty($options['client_id'])) {
   $this->clientId = $options['client_id'];
  }

  if (!empty($options['client_secret'])) {
   $this->clientSecret = $options['client_secret'];
  }
 }

 /*
 https://developer.yahoo.com/oauth2/guide/flows_authcode/#step-2-get-an-authorization-url-and-authorize-access
 */
 protected $language = "ja";

 private $imageSize = '192x192';

 public function getBaseAuthorizationUrl()
 {
  return 'https://auth.login.yahoo.co.jp/yconnect/v2/authorization';
 }

 public function getBaseAccessTokenUrl(array $params)
  {
  return 'https://auth.login.yahoo.co.jp/yconnect/v2/token';
 }

 public function getResourceOwnerDetailsUrl(AccessToken $token)
 {
  // $guid = $token->getResourceOwnerId();

  return 'https://userinfo.yahooapis.jp/yconnect/v2/attribute?access_token='.$token->getToken().'&scope=openid%20profile';
 }

 /**
 * Requests and returns the resource owner of given access token.
 *
 * @param AccessToken $token
 * @return ResourceOwnerInterface
 */
 public function getResourceOwner(AccessToken $token)
 {
  $response = $this->fetchResourceOwnerDetails($token);

  return $this->createResourceOwner($response, $token);
 }

 /**
 * Get user image from provider
 *
 * @param array $response
 * @param AccessToken $token
 *
 * @return array
 */
 protected function getUserImage(array $response, AccessToken $token)
 {
  $guid = $token->getResourceOwnerId();

  $url = 'https://userinfo.yahooapis.jp/yconnect/v2/attribute' . $guid . '/profile/image/' . $this->imageSize . '?format=json';

  $request = $this->getAuthenticatedRequest('get', $url, $token);

  $response = $this->getResponse($request);

  return $response;
 }

 protected function getAuthorizationParameters(array $options)
 {
  $params = parent::getAuthorizationParameters($options);

  $params['language'] = isset($options['language']) ? $options['language'] : $this->language;

  return $params;
 }

 protected function getDefaultScopes()
 {
  /*
  No scope is required. scopes are part of APP Settings.
  */
  return [];
 }

 protected function getScopeSeparator()
 {
  return ' ';
 }

 protected function checkResponse(ResponseInterface $response, $data)
 {
  if (!empty($data['error'])) {
  $code = 0;
  $error = $data['error'];

  if (is_array($error)) {
   /*
   No code is returned in the error
   */
   $code = -1;
   $error = $error['description'];
  }
   throw new IdentityProviderException($error, $code, $data);
  }
 }

 /**
 * Requests resource owner details.
 *
 * @param AccessToken $token
 * @return mixed
 */
 protected function fetchResourceOwnerDetails(AccessToken $token)
 {
  $url = $this->getResourceOwnerDetailsUrl($token);

  $request = $this->getAuthenticatedRequest(self::METHOD_GET, $url, $token);

  $response = $this->getParsedResponse($request);

  if (false === is_array($response)) {
   throw new UnexpectedValueException(
    'Invalid response received from Authorization Server. Expected JSON.'
   );
  }

  return $response;
 }

 protected function createResourceOwner(array $response, AccessToken $token)
 {
  return $response;
  // $user = new YahooUser($response);

  // $imageUrl = $this->getUserImageUrl($response, $token);

  // return $user->setImageURL($imageUrl);
 }

 /**
 * Returns the authorization headers used by this provider.
 *
 * Typically this is "Bearer" or "MAC". For more information see:
 * http://tools.ietf.org/html/rfc6749#section-7.1
 *
 * No default is provided, providers must overload this method to activate
 * authorization headers.
 *
 * @param mixed|null $token Either a string or an access token instance
 * @return array
 */
 protected function getAuthorizationHeaders($token = null)
 {
  return [];
 }

}

まとめ

とりあえず、OpenIdを取得するまでの流れを実装してみました。

あとは取得したOpenIdでログインユーザ等を取得し、セッションに格納すれば外部認証型のログインが可能になりますね!(すべらすぃ!)

今回は、Slackの認証方法は書いていませんが、[adam-paterson/oauth2-slack]を使用すれば30分ほどでできました。

自作すると2時間ほど掛かりましたのでClient Libraryが無くて困ってる型は参考にしてみてください!

ソースコードキレイに記述する方法がわからないので読みずらいかもしれませんがお試しあれ!