concrete5でAuth0を使って認証する方法を考えてみる

2019.08.05
この記事ではconcrete5でAuth0を使って認証できるようにするには、どのような方法があるのか?について考え、試した結果を記載しております。ただ、もっと適切な方法や考慮が足りていない点などが存在している可能性があるかもしれません。その点はご了承ください。

はじめに

タイトルの通りですが、Auth0をconcrete5で使うには、どのような方法があるのか気になっておりまして、試してみました。

concrete5の認証機能について

下記のようなログイン画面(デザインのカスタマイズが可能)があり、ユーザーIDとパスワードによる認証が可能です。 concrete5のユーザーとして、concrete5の管理者やユーザーの他に、concrete5で構築したサイト訪問者(サイトのユーザー)も登録することで、会員サイトを構築することが可能になります。

その他にfacebook, twitter, googleといったSNSアカウントでの認証も可能です。 各SNSでの認証を有効にする設定を行うと、このようにログイン画面にメニューが表示されるようになります。

以前にtwitterでの認証機能を試したことがあるため、参考までに記事のリンクを貼っておきます。

concrete5のSNS認証機能を試してみた

Auth0を使って認証する対象について考える

concrete5の標準の認証機能を使用する場合、concrete5のデータベース上に認証情報(ユーザーID、パスワードなど)を保持し、そこにユーザーとしてconcrete5の管理者やサイト制作者、サイト訪問者(サイトのユーザー)などを登録していきます。

Auth0を使って認証を行うにあたって、下記のような検討ができるのではないかと思いました。

  1. 全てのユーザー(concrete5の管理者やサイト制作者、サイト訪問者など)を対象とするのか?
  2. サイト訪問者だけを対象とするのか?

全てのユーザー(concrete5の管理者やサイト制作者、サイト訪問者など)を対象とする場合

簡単に言うと標準のログイン画面、機能を一切使わず、Auth0に置き換えることになります。

イメージとしては、concrete\single_pages\login.phpconcrete\controlles\single_page\login.phpといった直接関係する部分や、関連する部分をオーバーライドしていくことになると思います。

サイト訪問者だけを対象とする場合

サイトは複数存在しており、メンテナンスをそれぞれ別の担当者(業者)が行なっているけど、各サイトへの認証処理は共通のIDで行いたい。そのためにAuth0のような統合認証基盤を導入するといった場合です。

この場合は、concrete5の管理者やサイト制作者の認証はconcrete5の標準機能を使い、サイト訪問者の認証はAuth0を使うことになるため、機能を追加するイメージです。

今回はこちらの実現方法について試してみました。

Auth0で認証する場合、concrete5のユーザーアカウントは不要か?

認証することだけを考えると不要なのですが、ログインセッションの管理や、ユーザーアカウントが存在していることを前提とした機能を使用することを考えると、内部的にはユーザーアカウントを作成して保持しておくのが良さそうであると判断しました。

そのため、下記の対応を実施しております。

  • concrete5上に、個々のAuth0ユーザーとひもづくアカウントを作成する
  • Auth0の認証が成功したら、concrete5上のアカウントも認証状態にさせておく

Auth0ユーザーとconcrete5のユーザーをどうやって紐づけるのか?

今回の検証では事前にconcrete5上にユーザーを作っておきましたが、実際に運用する場合は下記が対応案になると思っております。

  • 方法1)Auth0でのサインアップ処理をトリガーにして、concrete5上のユーザーを作成する。concrete5側にユーザー作成のためのAPIが必要になるためAPIをカスタマイズ開発する必要がある。ユーザー名(uName)はAuth0のclient_id(subクレームの値)と同じにしておきたいが、|がconcrete5のバリデーションチェックに引っかかってしまう。

カスタマイズ可能ならば、|を許容するようにして、無理な場合はAuth0のclient_idの頭からauth0|を除去した値で登録する。パスワードはダミーのものを設定しておく。

Auth0でのサインアップ処理をトリガーにする方法はこちらのブログ記事が参考になります。

Auth0 Rule でサインアップ処理をフックする

  • 方法2)Auth0で認証されたユーザーのclient_id(subクレームの値)の値でconcrete5のユーザー検索を行い、存在しない場合はユーザーを作成する

concrete5のログインセッションの管理をどうするか?

今回はデフォルトのままですが、Auth0のセッション有効期間 < concrete5のセッション有効期間にしておく必要がございます。

試した内容

Auth0の認証画面への遷移方法をどうするか?

標準機能の拡張として、カスタム認証タイプ(Custom Authentication Type)にAuth0を追加する方法があると思います。

Advanced: Creating a Custom Authentication Type

下記はgoogleですが、Auth0でログインといったボタンを増やすということです。

各SNSアカウントでの認証だと、googleでログインとかtwitterでログインとか、各ボタンを並べて選択してもらうのだと思いますが、Auth0の場合はユーザーはそれを特に意識しないため、ログイン画面をそのまま表示させるのが良いだろうと思いました。

そのため、標準の認証URL(https://{domain}/concrete5/(index.php)/login)とは別に、https://{domain}/concrete5/(index.php)/customer/auth0_loginといったURLをサイト訪問者用に別で用意することにしました。

※今回は私のローカル環境で試したため、http通信にしているのと、URLからindex.phpを消すrewrite処理は入れておりません

Auth0アプリケーションの作成

Auth0の管理画面のApplicationsメニューを選択して、画面上のCREATE APPLICATIONボタンを押して作成します。 Allowed Callback URLsAllowed Logout URLsの設定も忘れないようにしてください。

Auth0 PHP SDKのインストール

concrete5アプリケーションのルートディレクトリへ移動し、下記を実行。

composer require auth0/auth0-php:"~5.0"

下記の警告が出ましたが、とりあえずはそのままにして今回の動作確認を行いました。

Warning: Ambiguous class resolution, "Normalizer" was found in both "/{ローカル環境のインストールパス}/concrete5-8.5.1/concrete/vendor/patchwork/utf8/src/Normalizer.php" and "/{ローカル環境のインストールパス}/concrete5-8.5.1/concrete/vendor/voku/portable-utf8/src/Normalizer.php", the first will be used.
    Skipped installation of bin bin/doctrine-dbal for package doctrine/dbal: file not found in package
    Skipped installation of bin bin/doctrine-migrations for package doctrine/migrations: file not found in package
    Skipped installation of bin bin/doctrine for package doctrine/orm: file not found in package
    Skipped installation of bin bin/doctrine.php for package doctrine/orm: file not found in package
    Skipped installation of bin bin/export-plural-rules for package gettext/languages: file not found in package
    Skipped installation of bin bin/export-plural-rules.php for package gettext/languages: file not found in package

シングルページの作成

下記を追加しました。

  • application\single_pages\customer\auth0_login.php
  • application\controllers\single_page\customer\auth0_login.php

application\single_pages\customer\auth0_login.phpの内容

動作確認用に表示したい内容を記載しました。

<h2>Auth0での認証結果</h2>
<?php
echo "name=" . $userInfo['name'] . "<br />"; 
echo "sub=" . $userInfo['sub'] . "<br />";
echo "nickname=" . $userInfo['nickname'] . "<br />";
echo "picture=" . $userInfo['picture'] . "<br />";
echo "updated_at=" . $userInfo['updated_at'] . "<br />";
echo "email=" . $userInfo['email'] . "<br />";
echo "email_verified=" . $userInfo['email_verified'] . "<br />";
?>
<hr />
<h2>concrete5のユーザー情報</h2>
<?php
echo "uID=" . $c5userInfo->getUserID() . "<br />";
echo "uName=" . $c5userInfo->getUserName() . "<br />";
echo "uEmail=" . $c5userInfo->getUserEmail() . "<br />";
?>
<hr />
<?php if(!$userInfo): ?>
未ログイン状態
<?php else: ?>
    <a href="http://localhost:8888/concrete5/index.php/customer/auth0_login/logout">Auth0_Logout</a>
<?php endif ?>

`application\controllers\single_page\customer\auth0_login.phpの内容

Auth0のサイトにあるPHPでのクイックスタートを参考にしました。

<?php
namespace Application\Controller\SinglePage\Customer;
use Concrete\Core\Page\Controller\PageController;
use Concrete\Core\User\User;
use Core;
use Auth0\SDK\Auth0;

class Auth0Login extends PageController
{
    private $auth0;

    // Concrete5のPage Controllerの初期化処理でAuth0の設定を入れておく
    public function on_start(){
        // Auth0の設定
        $this->auth0 = new Auth0([
            'domain' => '{Auth0のドメイン}',
            'client_id' => '{Auth0のクライアントID}',
            'client_secret' => '{Auth0のクライアントシークレット}',
            'redirect_uri' => 'http://localhost:8888/concrete5/index.php/customer/auth0_login/callback',
            'scope' => 'openid profile email',
            'persist_id_token' => true,
            'persist_access_token' => true,
            'persist_refresh_token' => true,            
        ]);        
    }

    // ログイン時に呼ばれるメソッド
    public function view(){
        $this->auth0->login();
    }

    // Auth0からのコールバックを受けるメソッド
    public function callback(){
        $userInfo = $this->auth0->getUser();

        if (!$userInfo) {
            // 認証できていない場合の処理は、今回はスキップ
        } else {
            /*** concrete5ユーザーをログイン状態にさせる ***/
            
            // concrete5上のユーザーは、ユーザー名をAuth0のclient_idの頭の「auth0|」を取った値で登録しているため、subクレームの値を分割
            // (concrete5のバリデーションチェックにより、'|'をユーザー名に含めることができなかったため)
            $auth0sub[] = explode('auth0|', $userInfo['sub']);
            
            // ユーザー名からconcrete5のUserInfoオブジェクトを取得する
            $c5userInfo = Core::make('Concrete\Core\User\UserInfoRepository')->getByName($auth0sub[0]);
            
            // UserInfoオブジェクトからユーザーIDを取得して、concrete5上のユーザーをログイン状態にする
            User::loginByUserID($c5userInfo->getUserID());

            // Viewで表示できるようにUserInfoの値をセットしておく
            $this->set('userInfo', $userInfo);
            $this->set('c5userInfo', $c5userInfo);
        }        
    }

    // Auth0のログアウト処理を行うメソッド
    public function logout(){
        /*** concrete5上のユーザーをログアウトさせる ***/
        
        // ログイン中のconcrete5ユーザーを取得
        $u = new User();
        
        // ログイン中のconcrete5ユーザーをログアウト
        $u->logout();

        /*** Auth0からのログアウト ***/
        $this->auth0->logout();
        $return_to = 'http://' . $_SERVER['HTTP_HOST'] . '/concrete5/index.php/';
        $logout_url = sprintf('http://%s/v2/logout?client_id=%s&returnTo=%s', '{Auth0のドメイン}', '{Auth0のクライアントID}', $return_to);
        header('Location: ' . $logout_url);
        die();
    }

}

concrete5の管理画面上でシングルページを追加、設定

ページとテーマ > シングルページを開き、作成したシングルページを追加します。

/customer/auth0_login名前Auth0 Loginに変更しているのは、サイトマップメニュー画面上で変更しております。

concrete5の管理画面上で外部リンクの追加

サイトマップを開き、作成したシングルページのメニュー(Login)にログアウト処理を実行するためのリンクを追加します。

ログインユーザーのみに表示されるように設定します。

動作確認

まずはAuth0に未ログイン状態です。LoginメニューにAuth0_Loginのみが表示されておりますので、concrete5上にもログインしていないことがわかります。Auth0にログインするためにAuth0_Loginをクリックします。

Auth0の認証画面に遷移しました。ユーザーIDとパスワードを入力してログインします。 (ユーザーアカウントを作成していない場合は、サインアップをします)

Auth0での認証が完了し、コールバックを受けました。 Auth0で認証したユーザーのUserInfo情報を取得できております。画面上に`Auth0_Logout'リンクが表示されているため、Auth0へログイン状態であることが確認できます。

LoginメニューにAuth0_Logoutも表示されましたので、concrete5へのログインもできております。

ログアウトをすると、Auth0とconcrete5からログアウトされました。

おわりに

今回は、とりあえず試してみたという結果でした。

Laravel上でAuth0を使用する方法を参考にしたりして、concrete5で最適な実現方法を探していきたいと思います。 あとは他のCMSでも試してみたいと思います。