LINE Botが超便利になるLINE Loginバージョン2の解説と実装方法(Heroku+PHP)

<img src="https://cl.ly/2z0J3r1X003U/IMG_7114.PNG" width=50%>

LINE Login v2がものすごく便利なのに解説した記事が見当たらないので、LINE Messaging APIとの連携を例として書いておきます。(すいません、Salesforce版はありました > http://qiita.com/hhayai/items/c7d1d8e30751871aafc7)
これが使えるとLINE Botで出来ることが格段に増えるのでBot Awardとか狙っている人にはものすごく便利かと。

[LINE Bot Awards](https://botawards.line.me/ja/)

# LINE Login バージョン2とMessaging APIを組み合わせて出来ること

+ LINEのIn AppブラウザでユーザーはLINEのアカウントを利用してログインできる
+ 2度目以降はIDパスワード入力無しの自動ログイン
+ ログイン後はブラウザ上でもLINEの表示名、サムネイル、Messaging APIと共通のuserIdが取得できる(それを使ってメッセージのPushができる)

# 準備
+ [LINE Business Center](https://business.line.me/ja/)でLINE Messaging APIとLINE Loginのアカウントを開設
+ Messaging APIのChannel SecretとChannel Access Token、LINE LoginのChannel IDとChannel Secretを控えとく

<img src="https://cl.ly/0E1x2c2v3t3y/login_bc.png" width=50%>

+ LINE Messaging APIの設定を終わらせておきます。わからない方はググって。
+ LINE LoginのTechnical Configurationにログイン後のコールバック先を入力 **← 忘れがち**

<img src="https://cl.ly/421x3M2g0J1y/login_technical.png" width=50%>

+ CSRF対策用にaura/sessionを入れときます。

```shell-session
composer require composer require aura/session
```

# index.php(Botからのリクエストを受け取るスクリプト)

```php:index.php
<?php

require_once __DIR__ . '/vendor/autoload.php';

$httpClient = new \LINE\LINEBot\HTTPClient\CurlHTTPClient(getenv('CHANNEL_ACCESS_TOKEN'));
$bot = new \LINE\LINEBot($httpClient, ['channelSecret' => getenv('CHANNEL_SECRET')]);

$signature = $_SERVER['HTTP_' . \LINE\LINEBot\Constant\HTTPHeader::LINE_SIGNATURE];
try {
  $events = $bot->parseEventRequest(file_get_contents('php://input'), $signature);
} catch(\LINE\LINEBot\Exception\InvalidSignatureException $e) {
  error_log('parseEventRequest failed. InvalidSignatureException => '.var_export($e, true));
} catch(\LINE\LINEBot\Exception\UnknownEventTypeException $e) {
  error_log('parseEventRequest failed. UnknownEventTypeException => '.var_export($e, true));
} catch(\LINE\LINEBot\Exception\UnknownMessageTypeException $e) {
  error_log('parseEventRequest failed. UnknownMessageTypeException => '.var_export($e, true));
} catch(\LINE\LINEBot\Exception\InvalidEventRequestException $e) {
  error_log('parseEventRequest failed. InvalidEventRequestException => '.var_export($e, true));
}

foreach ($events as $event) {
  $bot->replyText($event->getReplyToken(), "https://" . $_SERVER["HTTP_HOST"] .  "/line_login.php");
}

?>
```

イベントに対してログイン用のWebサイトへのリンクを返信するだけ。

# line_login.php(ログインボタンを表示するWebページ)
```php:line_login.php
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml' lang='ja' xml:lang='ja'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
<title>LINE Login v2 Sample</title>
</head>
<body>
<?php

require_once __DIR__ . '/vendor/autoload.php';

$session_factory = new \Aura\Session\SessionFactory;
$session = $session_factory->newInstance($_COOKIE);
$segment = $session->getSegment('Vendor\Package\ClassName');

$csrf_value = $session->getCsrfToken()->getValue();

$callback = urlencode('https://' . $_SERVER['HTTP_HOST']  . '/line_callback.php');
$url = 'https://access.line.me/dialog/oauth/weblogin?response_type=code&client_id=' . getenv('LOGIN_CHANNEL_ID') . '&redirect_uri=' . $callback . '&state=' . $csrf_value;
echo '<a href=' . $url . '><button class="contact">LINE Login v2 Sample</button></a>';

?>
</body>
</html>
```

ログインボタンを表示する用のページ。このページ表示せずいきなりログインAPI叩くのも可能。

# line_callback.php(ログイン後に飛ばされるページ)
```php:line_login.php
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
<html xmlns='http://www.w3.org/1999/xhtml' lang='ja' xml:lang='ja'>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
<title>LINE Login v2 Sample Callback</title>
</head>
<body>
<?php

require_once __DIR__ . '/vendor/autoload.php';

$unsafe = $_SERVER['REQUEST_METHOD'] == 'POST'
       || $_SERVER['REQUEST_METHOD'] == 'PUT'
       || $_SERVER['REQUEST_METHOD'] == 'DELETE';

$session_factory = new \Aura\Session\SessionFactory;
$session = $session_factory->newInstance($_COOKIE);
$csrf_value = $_GET['state'];
$csrf_token = $session->getCsrfToken();
if ($unsafe || !$csrf_token->isValid($csrf_value)) {
  return;
}

$callback = 'https://' . $_SERVER['HTTP_HOST']  . '/line_callback.php';
if (isset($_GET['code'])) {
  $url = 'https://api.line.me/v2/oauth/accessToken';
  $data = array(
    'grant_type' => 'authorization_code',
    'client_id' => getenv('LOGIN_CHANNEL_ID'),
    'client_secret' => getenv('LOGIN_CHANNEL_SECRET'),
    'code' => $_GET['code'],
    'redirect_uri' => $callback
  );
  $data = http_build_query($data, '', '&');
  $header = array(
    'Content-Type: application/x-www-form-urlencoded'
  );
  $context = array(
    'http' => array(
      'method'  => 'POST',
      'header'  => implode('\r\n', $header),
      'content' => $data
    )
  );
  $resultString = file_get_contents($url, false, stream_context_create($context));
  $result = json_decode($resultString, true);

  if(isset($result['access_token'])) {
    $url = 'https://api.line.me/v2/profile';
    $context = array(
      'http' => array(
      'method'  => 'GET',
      'header'  => 'Authorization: Bearer '. $result['access_token']
      )
    );
    $profileString = file_get_contents($url, false, stream_context_create($context));
    $profile = json_decode($profileString, true);
　　　　　　　　echo '<img src="' . htmlspecialchars($profile["pictureUrl"], ENT_QUOTES) . '" />';
    echo '<p>displayName : ' . htmlspecialchars($profile["displayName"], ENT_QUOTES) . '</p>';
    echo '<p>userId : ' . htmlspecialchars($profile["userId"], ENT_QUOTES) . '</p>';

    $httpClient = new \LINE\LINEBot\HTTPClient\CurlHTTPClient(getenv('CHANNEL_ACCESS_TOKEN'));
    $bot = new \LINE\LINEBot($httpClient, ['channelSecret' => getenv('CHANNEL_SECRET')]);
    $bot->pushMessage($profile["userId"], new \LINE\LINEBot\MessageBuilder\TextMessageBuilder('Logged in via LINE Login v2.'));
  }
}
else {
  echo '<p>Login Failed.</p>';
}
?>
</body>
</html>
```

# 結果

<img src="https://cl.ly/0o3w1A2L3C0F/IMG_7112.PNG" width=50%>

友達追加するとリンクが送られます。

<img src="https://cl.ly/052i1s1M2B0F/IMG_7113.PNG" width=50%>

ログインページ。

<img src="https://cl.ly/2z0J3r1X003U/IMG_7114.PNG" width=50%>

ボタンをタップするとログインを試みます。

<img src="https://cl.ly/2g3w1K1o470p/IMG_7115.PNG" width=50%>

ログインしたことないLogin先の場合は認証画面が開きます。一度同意すると次回からは表示されず、自動でログイン。

<img src="https://cl.ly/1C2R0D383z21/IMG_7118.png" width=50%>

ログイン完了。Web上でLINEアカウントの表示名やサムネイル、ステータスメッセージ、ユーザーIDが取得できます。

このユーザーIDはLINE Messaging APIと共通なので、Push APIを利用してユーザーにメッセージ送信できます。

<img src="https://cl.ly/1v2f123B3t2b/IMG_7117.PNG" width=50%>

こんな感じ。Botとのトーク画面にメッセージを送信できました。

# まとめ

LINE Loginを上手く使うとBotのインターフェースを考える時、トーク画面上でのコミュニケーションに限定されず必要に応じてWebに遷移させればいいので開発者にもユーザーにもとても優しいですね。

設定画面とかはWebに飛ばすのが当たり前になりそうです。