開発情報・ナレッジ

投稿者:SPIRERS ナレッジ向上チーム 2022年8月12日 (金)

LINEアカウントで認証エリアにログインさせるサンプルプログラム

LINEのアカウント情報を API を利用して SPIRAL ver.2 に登録を行い、認証エリアにログインさせるサンプルプログラムを作成いたしました。
SPIRAL ver.2とLINEを連携して認証エリアにログインさせるような会員サイトを作成しようと思っている方は、ぜひ参考にしてみてください。

関連記事はこちら

変更・改定履歴

  • 改定

    ログイン後のリダイレクト処理に関する不具合の修正

  • 改定

    LINE側からメールアドレスも取得できる形にプログラム変更

  • 改定

    ログインAPIで発行されるtokenをSESSIONに格納する処理を追加

  • 改定

    パラメータ付きURLでもログインできるように改修

仕様

挙動イメージ図
 グレーの線・数字が通信の流れと順番になります。
 の線、オレンジの数字は、プログラムによる処理の流れになります。
挙動イメージ(処理の流れ詳細)
1. グレー線 ①
ログイン画面にアクセス
2. オレンジ線 ①
POST による LINE 一時コードの存在チェックを実施
一時コードが存在しない場合、下記を実施の上、グレー線 ②へ
 ・SESSION変数にランダム値を書き込み
 ・LINE側のキー や ランダム値などの LINE へリダイレクトさせるための情報を設定し、Head へデータを渡す
3. グレー線 ②
必要な情報をURLパラメータにつけて、リダイレクト
4. グレー線 ③
LINE 側でログイン許可処理が完了すると、ログイン画面に一時コードを POST
5. オレンジ線 ①
POST による LINE 一時コードのチェックを実施
一時コードが存在する場合、下記を実施の上、オレンジ線 ②へ
 ・SESSION変数に設定したランダム値とLINEからPOSTされた値が一致しているかのチェック
  → 一致していない場合、不正エラーを表示し、処理をストップ
 ・一致している場合、LINE API にて、ユーザ情報取得用の一時トークンを発行
6. オレンジ線 ③
LINE API にて、一時トークンをもとに、LINE IDを含めたプロフィール情報を取得
7. オレンジ線 ④
ver.2 の API にて、登録処理を実施
すでに値がある場合もエラーとせず、後続処理へ
LINEログインを実施したタイミングでLINEの情報が登録される仕組みです
8. オレンジ線 ⑤
ver.2 の API にて、認証エリアへのログイン処理を実施
9. オレンジ線 ⑥
ログイン処理で取得したトークンを元に認証エリアログインのワンタイムURL発行
ワンタイムURLへリダイレクトさせるための情報を設定し、Head へデータを渡す
10. グレー線 ④
認証エリア内のページにアクセス

LINEアカウント

LINE ログインを実装するために、LINEログインのチャネルを作成が必要となります。
作成方法については、Step 1: LINEログインのチャネルを作成する を参考にしてください。
プログラムでは、Step 1で作成したチャネルのチャネルIDとチャネルシークレットを使用します。

DB項目

認証エリアを作成するため、DBを作成する必要があります。
LINE よりデータを取得するために下記の項目を追加お願いします。
テキスト LINEのユーザID として使用するため、必須 かつ 重複不可で作成します。
パスワード APIでログインさせる際に必要な項目です。
自動生成を行っているため、入力不要です。
テキスト LINEの名前を取得するための項目です。
取得しない場合は、不要となります。
テキストエリア プロフィールの画像URLを取得するための項目です。
取得しない場合は、不要となります。
メールアドレス メールアドレスを取得するための項目です。
取得しない場合は、不要となります。
メールアドレスは、必ず登録されているものではないので、必須にしないでください。
また、メールアドレスを取得する場合、あらかじめメールアドレス取得権限を申請してください。

ver.2 側のログインページ設定

ログインページ PHP
<?php
// SPIRAL ver.2 の情報
define("LOGIN_URL","");
define("LOGIN_TOP_PASS","");
define("ENCRYPT_KEY",""); // 文字列
define("API_URL", "https://api.spiral-platform.com/v1/");
define("API_KEY", $SPIRAL->getEnvValue(""));
define("API_ROLE", $SPIRAL->getEnvValue(""));
define("SITE_ID", "");
define("AUTHENTICATION_ID", "");
define("APP_ID", $SPIRAL->getEnvValue(""));
define("USERDB_ID", $SPIRAL->getEnvValue(""));
// Line の情報
define("CLIENT_ID", "");
define("CLIENT_SECRET", "");

// セッションスタート
session_start();
// ステータスをセット
// Redirect用URL
if(!$SPIRAL->getParam("back-url")){
    $redirect_uri = LOGIN_URL;
}else{
    $redirect_uri = LOGIN_URL.'?back-url='.$SPIRAL->getParam("back-url");
}
$SPIRAL->setTHValue("status","");
if(!$SPIRAL->getParam("code")){
    $_SESSION['randStr'] = makeRandStr(8);
    // Lineに未ログイン状態
    $SPIRAL->setTHValue("status","pageRedirect");
    $SPIRAL->setTHValue('redirectURL','https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id='.CLIENT_ID.'&redirect_uri='. $redirect_uri .'&state='. $_SESSION['randStr'] . '&bot_prompt=aggressive&scope=profile%20openid%20email');
}else{    
    if($SPIRAL->getParam("state") == $_SESSION['randStr']){
        $_SESSION['randStr'] = null;

        $lineData = lineLogin($SPIRAL->getParam("code"),$redirect_uri);
        if(!isset($lineData['sub'])){
            $SPIRAL->setTHValue("status","error");
            $SPIRAL->setTHValue("errorMessage","Line の認証に失敗しました。");
            // debug
            // $SPIRAL->setTHValue("errorMessage","Line認証".print_r($lineData,true));
        }else{
            $data = array(
                "lineID" => $lineData['sub'],
                "password" => encrypt($lineData['sub']),
                // 名前を使用する場合
                "name" => $lineData['name'],
            );            
            if(isset($lineData['picture'])){
                // プロフィールアイコンを入れる場合
                // $data = array_merge($data,array("" => $lineData['picture']));
            }
            if(isset($lineData['email'])){
                // メールアドレスを入れる場合
                // $data = array_merge($data,array("" => $lineData['email']));
            }         
            $regData = apiCurlAction("POST","apps/".APP_ID."/dbs/".USERDB_ID."/records",$data);
            $loginUser = array(
                "id" => $lineData['sub'],
                "password" => encrypt($lineData['sub']),
            );
            $loginCheckAPI = apiCurlAction("POST","sites/".SITE_ID."/authentications/".AUTHENTICATION_ID."/login",$loginUser);
            if(isset($loginCheckAPI["status"]) && $loginCheckAPI["status"] != 200){
                // DB情報取得に失敗
                $SPIRAL->setTHValue("status","error");
                $SPIRAL->setTHValue("errorMessage","ログイン処理のAPIの処理に失敗しました。時間をおいて再度お試しください。");
                // debug
                // $SPIRAL->setTHValue("errorMessage","ログイン処理".print_r($loginCheckAPI,true));
            }else{
                if($SPIRAL->getParam("back-url")){
                    if(strpos($SPIRAL->getParam("back-url"),'?') !== false){
                        $parameter = str_replace('?', '', strstr($SPIRAL->getParam("back-url"), "?"));
                        $path = strstr($SPIRAL->getParam("back-url"), "?", true);
                    }else{
                        $path = $SPIRAL->getParam("back-url");
                    }
                }else{
                    $path = LOGIN_TOP_PASS;
                }
                $loginData = array(
                    "token" => $loginCheckAPI['token'],
                    "path" => $path,
                );
                $_SESSION['token'] = $loginCheckAPI['token'];
                $loginAPI = apiCurlAction("POST","sites/".SITE_ID."/authentications/".AUTHENTICATION_ID."/oneTimeLogin",$loginData);
                if(isset($loginAPI['status']) && $loginAPI["status"] != 200){
                    // DB情報取得に失敗
                    $SPIRAL->setTHValue("status","error");
                    $SPIRAL->setTHValue("errorMessage","APIの処理に失敗しました。時間をおいて再度お試しください。");
                    // debug
                    //$SPIRAL->setTHValue("errorMessage","リダイレクト処理".print_r($loginAPI,true));
                }else{
                    $SPIRAL->setTHValue("status","pageRedirect");
                    if(isset($parameter)){
                        $SPIRAL->setTHValue('redirectURL',$loginAPI['url'].'&'.$parameter);
                    }else{
                        $SPIRAL->setTHValue('redirectURL',$loginAPI['url']);
                    }
                }
            }   
        }
    }else{
        $SPIRAL->setTHValue("status","error");
        $SPIRAL->setTHValue("errorMessage","不正なアクセスです。");
    }   
}



function lineLogin($code,$redirect_uri){
    $postData = array(
        'grant_type'    => 'authorization_code',
        'code'          => $code,
        'redirect_uri'  => $redirect_uri,
        'client_id'     => CLIENT_ID,
        'client_secret' => CLIENT_SECRET,
    );
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
    curl_setopt($ch, CURLOPT_URL, 'https://api.line.me/oauth2/v2.1/token');
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postData));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close($ch);
    
    $json = json_decode($response);
    $postData2 = array(
        'id_token'    => $json->id_token,
        'client_id'     => CLIENT_ID,
    );
    $ch2 = curl_init();
    curl_setopt($ch2, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
    curl_setopt($ch2, CURLOPT_URL, 'https://api.line.me/oauth2/v2.1/verify');
    curl_setopt($ch2, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch2, CURLOPT_POSTFIELDS, http_build_query($postData2));
    curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
    $LineProfile = curl_exec($ch2);
    curl_close($ch2);
    
    $json2 = json_decode($LineProfile);
    $userInfo= json_decode(json_encode($json2), true);
    return $userInfo;
}

function makeRandStr($length) {
    $str = array_merge(range('a', 'z'), range('0', '9'), range('A', 'Z'));
    $r_str = null;
    for ($i = 0; $i < $length; $i++) {
        $r_str .= $str[rand(0, count($str) - 1)];
    }
    return $r_str;
}

/**
 * V2用 API送信ロジック
 * @return Result
 */
function apiCurlAction($method, $addUrlPass, $data = null){    
    $header = array(
        "Authorization:Bearer " . API_KEY,
        "Content-Type:application/json",
        "X-Spiral-Api-Version: 1.1",
    );
    if(API_ROLE){
        $header = array_merge($header,array("X-Spiral-App-Role: ".API_ROLE));
    }

    // curl
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_URL, API_URL . $addUrlPass);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
    if ($method == "POST") {
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
    }
    if ($method == "PATCH") {
        curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
    }
    if ($method == "DELETE") {
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
    }
    if ($method == "GET") {
        curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
    }
    $response = curl_exec($curl);
    if (curl_errno($curl)) echo curl_error($curl);
    curl_close($curl);
    return json_decode($response, true);
}

/**
 * 暗号化
 * @return text
 */

function encrypt($data){
    return openssl_encrypt(substr($data,5), 'AES-128-ECB', ENCRYPT_KEY);
}

SPIRAL ver.2 についての基本の設定は下記になります。

LOGIN_URL ログインページのURLを設定
絶対パスで記載してください。
例)https://www.example.com/login
LOGIN_TOP_PASS 認証後トップに設定しているページのURLを設定
相対パスで記載してください。
例)/topPage
認証ページに直接アクセスされた場合は、ログイン処理が実施され、アクセスした認証ページへ遷移します。
ENCRYPT_KEY セキュリティを向上させるため、任意のランダム値を半角英数字で設定
API_KEY APIキーを設定
API_ROLE アプリロールの識別名をセットしたPHP環境変数の変数名を設定
アプリロールによる権限設定をしない場合は未入力
SITE_ID サイトIDをセットしたPHP環境変数の変数名を設定
AUTHENTICATION_ID LINEログインを行う、認証エリアIDを設定
APP_ID アプリのIDをセットしたPHP環境変数の変数名を設定
USERDB_ID 認証を行うDBのIDをセットしたPHP環境変数の変数名を設定

下記は、PHP環境変数を使った書き方になります。
PHP環境変数については、サポートサイトを参照ください。
PHP環境変数を使用しない場合は、下記を使用せずに直接識別名もしくは IDを設定してください。
$SPIRAL->getEnvValue("")
次に、LINE側の設定値を設定します。
使用する チャネルID と チャネルシークレット に関しましては、2で設定したLINEアカウント内で確認してください。
詳細は、 Step 2: スターターアプリをデプロイする を参考にしてください。

CLIENT_ID LINE のチャネルIDを設定
CLIENT_SECRET LINE のチャネルシークレットを設定

最後にDBへ登録するフィールドの設定を行います。
PHPの 43行目 - 56行目の下記の箇所が設定箇所となります。
$data = array(
    "lineID" => $lineData['sub'],
    "password" => encrypt($lineData['sub']),
    // 名前を使用する場合
    "name" => $lineData['name'],
);            
if(isset($lineData['picture'])){
    // プロフィールアイコンを入れる場合
    // $data = array_merge($data,array("" => $lineData['picture']));
}
if(isset($lineData['email'])){
    // メールアドレスを入れる場合
    // $data = array_merge($data,array("" => $lineData['email']));
}     
DBへ登録するフィールドの設定に関しては、"lineID","password", "name" の箇所を引き込むフィールドの識別名に変更してください。
また、アイコン画像のURLを取得・メールアドレスの取得を行う場合は、51行目・55行目のコメントアウト(//)を削除し、引き込むフィールドの識別名を""の間に設定してください。
メールアドレスを取得する場合、あらかじめメールアドレス取得権限を申請が必要です。

ログインページの HEAD および BODY は、以下を設定します。
ログインページ HEAD
<title th:text="${page.title}"></title>
<th:block th:if="${cp.result.value['status']} == 'pageRedirect'">
    <meta http-equiv="refresh" th:content="|0;URL=${cp.result.value['redirectURL']}|">
</th:block>
ログインページ BODY
<div th:if="${cp.result.isSuccess}">
    <th:block th:if="${cp.result.value['status']} == 'error'">
        <p th:text="${cp.result.value['errorMessage']}"></p>
    </th:block>
</div>
<div th:if="${!cp.result.isSuccess}">
    <p th:text="${cp.result.errorMessage}">error message</p>
</div>

BODY に関しては、エラー時にログイン画面でエラー文言を表示するつくりになっています。
エラー文言を修正する場合は、PHPの下記の箇所を修正してください。
 $SPIRAL->setTHValue("errorMessage","xxx");
上記の記述は、複数箇所設定されておりますので、ご注意ください。

また、APIの処理確認用にエラー内容を表示できるよう設定しております。
エラー内容を確認する場合は、PHPの「// debug」の下に記載されている処理のコメントアウトを外してしてください。

LINE 側の設定

LINEの側の設定としましては、コールバックURLの設定のみとなります。
2で設定したLINEアカウントで設定したアカウントのコールバックURLに、ログインページのURLを絶対パスで登録してください。
設定方法については、Step 3: チャネルの設定確認し、コールバックURLを入力する を参考にしてください。

最後に

設定後は動作確認を必ず行い、動作に問題がないか確認をしてください。
不具合やほかのやり方が知りたい等あれば、下記の「コンテンツに関しての要望はこちら」からご連絡ください。

解決しない場合はこちら コンテンツに関しての
要望はこちら