Symfony3でHWIOAuthBundleを使ったFacebookログイン

Symfony3.4でHWIOAuthBundleを使ってFacebookログインするところまでの手順を記載します。
ログインに成功するとFOSUserBundleのエンティティにFacebookのIDやアクセストークンが保存される状態になります。
でもその後の処理を考えると [KnpUOAuth2ClientBundle](https://github.com/knpuniversity/oauth2-client-bundle) を使うほうが良さそうです。


## 主な環境

- LinuxまたはMacOS
- PHP 5.6以上
- Composer
- Symfony 3.4
- HWIOAuthBundle 0.6.1

## 前提
- Facebookアプリの登録が完了し、app IDなどが取得済みであること
- FOSUserBundleのインストール、設定、エンティティの準備が完了していること


## Userエンティティに項目追加
```src/AppBundle/Entity/User.php
    /**
     * @var string
     *
     * @ORM\Column(name="facebook_id", type="string", nullable=true)
     */
    private $facebookId;

    /**
     * @var string
     *
     * @ORM\Column(name="facebook_access_token", type="string", nullable=true)
     */
    private $facebookAccessToken;

    // setterとgetterも追加(bin/console d:g:entities AppBundle/Entity/User)

```

## HWIOAuthBundleのインストールと設定

[GitHubのドキュメント](https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/1-setting_up_the_bundle.md)の通り

```bash
# コンソールで実行
composer require hwi/oauth-bundle php-http/guzzle6-adapter php-http/httplug-bundle
```

```app/AppKernel.php

public function registerBundles()
{
    $bundles = array(
        // ...
        new Http\HttplugBundle\HttplugBundle(), // If you require the php-http/httplug-bundle package.
        new HWI\Bundle\OAuthBundle\HWIOAuthBundle(),
    );
}
```

```app/config/routing.yml
hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
    prefix:   /connect
    
hwi_oauth_connect:
    resource: "@HWIOAuthBundle/Resources/config/routing/connect.xml"
    prefix:   /connect

hwi_oauth_login:
    resource: "@HWIOAuthBundle/Resources/config/routing/login.xml"
    prefix:   /login
```


## 各種ymlの設定

### parameters.ymlの設定
```app/config/parameters.yml
parameters:
    # 以下を追加(*はFacebookアプリに応じて設定)
    facebook_id: *******************
    facebook_secret: *******************
```

### config.ymlの設定
```app/config/config.yml
# 以下を追加
httplug:
    plugins:
        logger: ~
    clients:
        default:
            factory: 'httplug.factory.curl'
        hwi_special:
            factory: 'httplug.factory.guzzle6'
            plugins: ['httplug.plugin.logger']
            config:
                timeout: 10
                verify_peer: false
                verify: false
                max_redirects: 1
                ignore_errors: false
                allow_redirects:
                    strict: false
                    protocols: ["http", "https"]

hwi_oauth:
    http:
        client: httplug.client.hwi_special
    firewall_names: [secured_area]
    fosub:
        username_iterations: 30
        properties:
            facebook: facebookId
    connect:
        confirmation: true
        account_connector: my.oauth_aware.user_provider.service
    resource_owners:
        facebook:
            type:            facebook
            client_id:       '%facebook_id%'
            client_secret:   '%facebook_secret%'
            scope:           "email, public_profile"
            infos_url:       "https://graph.facebook.com/me?fields=id,name,email,picture.type(large)"
            paths:
                email: email

# NelmioSecurityBundleを利用している場合は次の設定も追加
nelmio_security:
    external_redirects:
        # 追加
        whitelist:
            - facebook.com
```

### security.ymlの設定
```app/config/security.yml
firewalls:
　　　　　　　　# 以下を追加
    secured_area:
        anonymous: true
        oauth:
            resource_owners:
                facebook:      "/login/check-facebook"
            login_path:        /login
            provider: 　　　　　　　　　　　　　　　　　　fos_userbundle
            use_forward:       false
            failure_path:      /login

            oauth_user_provider:
                service: my.oauth_aware.user_provider.service
```

### service.ymlの設定
```app/config/service.yml
services:
　　　　　　　　# 以下を追加
    my.oauth_aware.user_provider.service:
        class: AppBundle\Security\Core\User\OAuthUserProvider
        arguments:
            - '@fos_user.user_manager'
            - {facebook: facebookId}
```

## OAuthUserProviderの作成
このファイルにFacebookログインした後の動作を記述します。
[GitHubのドキュメント](https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/4-integrating_fosub.md#2-extend-default-fosubuserprovider)も参考にしてください。

```src/AppBundle/Security/Core/User/OAuthUserProvider.php
<?php
namespace AppBundle\Security\Core\User;

use AppBundle\Entity\User;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserChecker;
use Symfony\Component\Security\Core\User\UserInterface;
/**
 * Class OAuthUserProvider
 */
class OAuthUserProvider extends BaseClass
{
    /**
     * {@inheritdoc}
     */
    public function loadUserByOAuthUserResponse(UserResponseInterface $response)
    {
        $socialID = $response->getUsername();
        $user = $this->userManager->findUserBy(array($this->getProperty($response)=>$socialID));
        $email = $response->getEmail();
        if (null === $user) {
            $user = $this->userManager->findUserByEmail($email);

            if (null === $user || !$user instanceof UserInterface) {
                $user = $this->userManager->createUser();
                $user->setUsername($email);
                $user->setEmail($email);
                $user->setEnabled(true);
            }
            $service = $response->getResourceOwner()->getName();
            switch ($service) {
                case 'facebook':
                    $user->setFacebookId($socialID);
                    $user->setFacebookAccessToken($response->getAccessToken());
                    break;
            }

            $this->userManager->updateUser($user);
        } else {
            $checker = new UserChecker();
            $checker->checkPreAuth($user);
        }

        die('登録完了');

        return $user;
    }
}
```

## ログイン画面HTMLの変更
```app/Resources/FOSUserBundle/views/Security/login.html.twig
{# 次のリンクを追加 #}
<a href="{{ path('hwi_oauth_service_redirect', { 'service' : 'facebook' }) }}">Login with Facebook</a>
```

## 確認

```bash
# コンソールで実行
php bin/console server:run 0.0.0.0:8000
```
1. ブラウザで http://localhost:8000/login にアクセス
2. Login with FacebookリンクをクリックしてFacebookの画面に遷移
3. Facebook内でログインやアプリの許可を行った後に登録完了画面が表示される