開発情報・ナレッジ

投稿者: SPIRERS ナレッジ向上チーム 2023年5月30日 (火)

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

Facebookのアカウント情報を利用して SPIRAL ver.2にAPIで登録を行い、認証エリアにログインさせるサンプルプログラムを作成いたしました。
SPIRAL ver.2とFacebookを連携して認証エリアにソーシャルログインさせたい場合は、ぜひ参考にしてみてください。

仕様概要

本記事ではFacebookの公式ドキュメント を参考に、JavaScript SDKを利用してFacebookアカウント情報を取得しソーシャルログインを実装しています。
処理詳細
1)ログインページで「Facebookでログイン」ボタンをクリックした時、アカウント情報を取得
2)取得したアカウント情報をGETパラメータとして付与し、ログイン処理ページへ遷移
3)ログイン処理ページでパラメータからFacebookアカウント情報を取得し、ver.2の認証DBへアカウントを登録
4)登録したアカウント情報で認証APIを実行し認証エリアへ遷移

上記のようにすることで、Facebookアカウントの情報を使ってver.2の認証エリアにログインすることができます。

マイアプリの設定

ソーシャルログインを実装するために、Metaのディベロッパーズサイトへのアカウント登録と「マイアプリ」の設定が必要になります。
設定方法に関しては、ウェブ - Facebookログイン を参考にしてください。

マイアプリを設定するとアプリIDが取得できますので控えておいてください。
アプリIDは以下でご紹介するログインプログラム内で必要になります。

認証DBの設定

ログインする認証エリアを作成するため、認証DBを作成する必要があります。
Facebookアカウントから取得したデータを登録するため下記の項目を追加するようにしてください。
必須項目
項目名 フィールドタイプ DB上で必須な属性 備考
認証ID テキスト 必須制約:あり
ユニーク成約:あり
認証IDとして利用するフィールドです。
ソーシャルログインする際に、FacebookアカウントIDを登録します。
パスワード パスワード 必須制約:あり
APIでログインさせる際に必要なフィールドです。
ソーシャルログインする際は、パスワードを自動生成します。
任意項目
項目名 フィールドタイプ DB上で必須な属性 備考
アカウント名 テキスト or
テキストエリア
なし Facebookアカウント名を登録するフィールドです。
姓、名を分けて登録することも可能で、その場合は2つフィールドを用意してください。

ログインページの設定

Javascript SDKの読み込みと、Facebookアカウントの情報取得、ログイン処理ページへのリダイレクト処理プログラムを追加します。
公式のドキュメントにあるように、SDKの読み込みなどJavascriptに関してはbodyタブの一番上に記載してください。
bodyタブ
<script>
const facebookAppId = '';
const apiVersion = '';
const loginProcessPage = '';

window.fbAsyncInit = function() {
  FB.init({
    appId            : facebookAppId,
    autoLogAppEvents : true,
    xfbml            : true,
    version          : apiVersion
  });
};

function checkLoginState() {
  FB.getLoginStatus(function(response) {
    statusChangeCallback(response);
  });
}

function statusChangeCallback(response) {
  if (response.status === 'connected') {
    facebookApi();
  }
}

function facebookApi() {
  FB.api(
    '/me',
    'GET',
    {"fields":"id,name"},
    function(response) {
      const session = document.getElementById("loginPageSession");
      const sessionValue = session.dataset.value;

      window.location.href = `${loginProcessPage}?number=${response.id}&name=${response.name}&session=${sessionValue}`;
    }
  );
}

// Javascript SDK 読み込み
(function(d){
  var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
  js = d.createElement('script'); js.id = id; js.async = true;
  js.src = "https://connect.facebook.net/ja_JP/sdk.js";
  d.getElementsByTagName('head')[0].appendChild(js);
}(document));
</script>
また、コード内の設定値に以下のように値を設定してください。
設定値
facebookAppId 作成したアプリのIDを設定してださい。
apiVersion 使用するAPIのバージョンを設定してください。
例)v17.0
loginProcessPage 次の手順で作成するログイン処理ページのURLを設定してください。
URLは相対パスで記載するようにしてください。
例)/FacebookLoginProcess

また、標準で用意されているボタンのHTMLをページ内の任意の場所に追加してください。
bodyタブ
<div class="fb-login-button" data-size="large" data-button-type="continue_with" data-auto-logout-link="false" onlogin="checkLoginState();"></div>
ボタンデザインは属性によって変更することが可能です。
詳細はログインボタン - Facebookログインを参照してください。

同じくbodyタブにsessionの値をJavascriptに受け渡すためのThymeleafを記載してください。
非表示の項目ですので、同じくbodyタブに追加したJavascriptよりも下であればどこに記載しても大丈夫です。
bodyタブ
<div id="loginPageSession" th:data-value="${cp.result.value['session']}"></div>
最後にログインページにアクセスした際にsessionを発行するプログラムをPHPタブに記載します。
sessionはログイン処理ページへの不正経路からのアクセスを防止するために利用しています。
PHPタブ
<?php
session_start();
$_SESSION['randStr'] = makeRandStr(10);
$SPIRAL->setTHValue("session", $_SESSION['randStr']);

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;
}
?>
以上で、ログインページの設定は完了です。

ログイン処理ページの設定

ログイン処理のためのページを作成してください。
作成したページにver.2認証エリアのアカウント作成とAPIでのログイン処理を行うプログラムを記載します。
PHPタブ
<?php
define("API_URL", "https://api.spiral-platform.com/v1");
define("API_KEY", "");
define("APP_ROLE", "");
define("DB_ID", "");
define("APP_ID", "");
define("SITE_ID", "");
define("AREA_ID", "");
define("LOGIN_URL", "");
define("LOGIN_TOP_PASS", "");
define("ENCRYPT_KEY", "");

session_start();
$session = isset($_SESSION['randStr']) ? $_SESSION['randStr'] : "error";
// パラメータ情報取得
$url = (empty($_SERVER['HTTPS']) ? 'http://' : 'https://'). $_SERVER['HTTP_HOST']. $_SERVER['REQUEST_URI'];
$components = parse_url($url);
parse_str($components['query'], $urlParams);
$id = isset($urlParams["number"]) ? $urlParams["number"] : "";
$name = isset($urlParams["name"]) ? $urlParams["name"] : "";
$getSession = isset($urlParams["session"]) ? $urlParams["session"] : "";

if ($session == $getSession) {
    $result = googleLoginProcess($id, $name);
    if ($result[0] == "error") {
        $SPIRAL->setTHValue("status", $result[0]);
        $SPIRAL->setTHValue("redirectURL", LOGIN_URL);
    } else if ($result[0] == "pageRedirect") {
        $SPIRAL->setTHValue("status", $result[0]);
        $SPIRAL->setTHValue("redirectURL", $result[1]["url"]);
    }
} else {
    $SPIRAL->setTHValue("error", "データが不正です。");
}

function googleLoginProcess($id, $name) {
    $commonBase = CommonBase::getInstance();

    $insertData = array(
        "認証IDフィールドの識別名"  => $id,
        "アカウント名フィールドの識別名" => $name,
        "パスワードフィールドの識別名" => encrypt($id),
    );
    $resultRecordInsert = $commonBase->apiCurlAction("POST", "/apps/". APP_ID. "/dbs/". DB_ID. "/records", $insertData);

    $body = array(
        "id" => $id,
        "password" => encrypt($id),
    );
    $resultLogin = $commonBase->apiCurlAction("POST", "/sites/". SITE_ID. "/authentications/". AREA_ID. "/login", $body);

    if (isset($resultLogin["token"])) {
        $body = array(
            "token" => $resultLogin["token"],
            "path" => LOGIN_TOP_PASS,
        );
        $resultOneTimeLogin = $commonBase->apiCurlAction("POST", "/sites/". SITE_ID. "/authentications/". AREA_ID. "/oneTimeLogin", $body);
        if (isset($resultOneTimeLogin["url"])) {
            $result[] = "pageRedirect";
            $result[] = $resultOneTimeLogin;
            return $result;
        } else {
            $result[] = "error";
            $result[] = $resultLogin;
            return $result;
        }
    } else {
        $result[] = "error";
        $result[] = $resultLogin;
        return $result;
    }
}
function encrypt($data){
    return openssl_encrypt(substr($data,5), 'AES-128-ECB', ENCRYPT_KEY);
}

//------------------------------
// 共通モジュール
//------------------------------
class CommonBase {
    /**
     * シングルトンインスタンス
     * @var UserManager
     */
    protected static $singleton;

    public function __construct() {
        if (self::$singleton) {
            throw new Exception('must be singleton');
        }
        self::$singleton = $this;
    }
    /**
     * シングルトンインスタンスを返す
     * @return UserManager
     */
    public static function getInstance() {
        if (!self::$singleton) {
            return new CommonBase();
        } else {
            return self::$singleton;
        }
    }
    /**
     * V2用 API送信ロジック
     * @return Result
     */
    function apiCurlAction($method, $addUrlPass, $data = null, $multiPart = null, $jsonDecode = null) {
        $header = array(
            "Authorization:Bearer ". API_KEY,
            "X-Spiral-Api-Version: 1.1",
        );
        if($multiPart) {
            $header = array_merge($header, array($multiPart));
        } else {
            $header = array_merge($header, array("Content-Type:application/json"));
        }
        if(APP_ROLE){
            $header = array_merge($header, array("X-Spiral-App-Role: ".APP_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") {
            if ($multiPart) {
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
            } else {
                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_POSTFIELDS, json_encode($data));
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        $response = curl_exec($curl);
        if (curl_errno($curl)) {
            return curl_error($curl);
        }
        curl_close($curl);
        if($jsonDecode){
            return $response;
        }else{
            return json_decode($response, true);
        }
    }
}
?>
PHP内の設定値に以下のようにそれぞれ値を設定してください。
設定値
API_KEY 発行したAPIキーを設定してださい。別途権限の付与が必要になります。
API_ROLE 設定したアプリロールの識別名を入れてください。全権限の場合は値は空で大丈夫です。
DB_ID 認証DBのIDを設定してください。
APP_ID 認証DBがあるアプリのIDを設定してください。
SITE_ID ソーシャルログインを行う認証エリアがあるサイトのIDを設定してください。
AREA_ID ソーシャルログインを行う認証エリアのIDを設定してください。
LOGIN_URL ログインページのURLを設定してください。
URLは絶対パスで記載するようにしてください。
例)https://www.example.com/login
LOGIN_TOP_PASS 認証後トップに設定しているページのURLを設定してください。
URLは相対パスで記載するようにしてください。
例)/topPage
ENCRYPT_KEY セキュリティを向上させるため、任意のランダム値を半角英数字で設定してください。
補足
define("API_KEY", $SPIRAL->getEnvValue("apikey"));
上記はAPIキーの記述で「PHP環境変数設定」を利用した場合のコードとなります。
このように定数を設定する時に、ver.2 機能の「PHP環境変数設定」を利用することで、
本番環境とテスト環境でキーが異なっていても、コードを書き換える必要がなく、保守性が高まります。
APIキーなど複数ページで使用するものは、環境変数設定をしておきましょう。
その他詳しくはサポートサイト「PHP環境変数」をご参照ください。

また、上記の設定値以外に以下の認証DBに登録するアカウント情報のフィールド識別名に関しては、ご自身の環境に合わせて値を変更するようにしてください。
$insertData = array(
    "認証IDフィールドの識別名"  => $id,
    "アカウント名フィールドの識別名" => $name,
    "パスワードフィールドの識別名" => encrypt($id),
);
APIでのログイン処理が成功した際に、認証エリア内ページにリダイレクトするためにheadタブにThymeleafを記載してください。
headタブ
<th:block th:if="${cp.result.value['status']} == 'pageRedirect'">
    <meta http-equiv="refresh" th:content="|0;URL=${cp.result.value['redirectURL']}|">
</th:block>
最後にログイン処理が失敗した際にエラー文言を表示するために、bodyタブにThymeleafを記載してください。
bodyタブ
<div>
    <div th:if="${cp.result.isSuccess}">
        <div th:if="${cp.result.value['status']} == 'error'">
            <h4>ログインエラーが発生しました。</h4>
            <a href="#" th:href="${cp.result.value['redirectURL']}">ログインページへ戻る</a>
        </div>
        <h4 th:text="${cp.result.value['error']}"></h4>
    </div>
    <div th:if="${!cp.result.isSuccess}">
        <h4>エラーが発生しました。</h4>
    </div>
</div>
以上でFacebookソーシャルログインのための設定は完了です。
デザインや、文言などは要件に合わせて適宜変更をお願いします。

最後に

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