Auth0 を使った認証を試してみたいと思います。
まずは、Auth0 の認証(Authentication API)でどのようなことをできるかを試します。
この記事でわかること
- CakePHP4 での Auth0 の認証(Authentication API)使い方。実践で使うことはできないけど、動きをみることはできる。
- この記事内のソースは以下で公開しています。
バージョン情報
バージョン | |
---|---|
CakePHP4 | 4.0.5 |
auth0/auth0-php | 7.1.0 |
事前準備
- CakePHP4 を docker-compose で動くようにする の記事内容のソースから発展させてます。
- docker-compose を実行し、コンテナを立ち上げます。
docker-compose up -d
Auth0 の設定
Auth0 のアカウント作成方法は、他サイトを参考にしてください。
アカウント作成後からの手順をメモがわりに残します。
Application の作成
- 左メニュー「Applications」をクリック
- 画面右「+ CREATE APPLICATION」ボタンをクリック
- 以下を入力&選んで「CREATE」ボタンをクリック
- Name:自身で決めたアプリケーション名を入力
- Choose an application type:「Regular Web Applications」を選択
これで Application が作成されます。
Application の設定
- 左メニュー「Applications」をクリック
- 一覧から該当するアプリケーション名リンクをクリック
- タブ「Settings」をクリック
- 以下を入力して「SAVE CHANGES」ボタンをクリック
- Allowed Callback URLs:「 http://localhost/auth/callback 」を入力
- Authorization Code Flow での処理で Code を受け取る URL を記載する。
- Allowed Logout URLs:「 http://localhost/ 」を入力
- ログアウト後にリダイレクトする(リダイレクト先として指定する) URL を記載する。
- Allowed Callback URLs:「 http://localhost/auth/callback 」を入力
CakePHP4 の実装
Auth0 のライブラリ追加
Composer を使って Auth0 のライブラリを追加します。
(下の例では、Docker経由で Composer を実行しています。)
docker exec -it app php composer.phar require auth0/auth0-php
envファイル
Auth0 に関する設定情報を ./config/.env ファイルへ追加します。
追加する設定情報は以下です。
AUTH0_DOMAIN、AUTH0_CLIENT_ID、AUTH0_CLIENT_SECRET は、 Auth0 管理画面( Auth0 左メニュー「Applications」 > 該当アプリケーション名リンク > タブ「Settings」 )の Domain、Client ID、Client Secret を記載します。
AUTH0_CALLBACK_URL、AUTH0_LOGOUT_URL は、Auth0 Application 設定でセットした内容です。
# Auth0
export AUTH0_DOMAIN="xxxx.auth0.com"
export AUTH0_CLIENT_ID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export AUTH0_CLIENT_SECRET="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export AUTH0_CALLBACK_URL="http://localhost/auth/callback"
export AUTH0_LOGOUT_URL="http://localhost/"
他の人にも展開することを考えて、 ./config/.env.example へも同様の記述を残していきます。
秘密的な情報はあまり記載したくないので、自分は以下の内容にしています。
# Auth0
export AUTH0_DOMAIN="example.auth0.com"
export AUTH0_CLIENT_ID=""
export AUTH0_CLIENT_SECRET=""
export AUTH0_CALLBACK_URL="http://localhost/auth/callback"
export AUTH0_LOGOUT_URL="http://localhost/"
AuthController の実装
最後に Controller の実装の全体は載せます。ここではメソッド単位で説明します。
Auth0 インスタンス作成
Auth0 のインスタンスを作るメソッドです。
RefreshTokenが必要な場合、 scope
へ offline_access
を追加する必要があります。
persist_*
のパラメータは、Auth0から取得した情報を Store(デフォルトだとセッション情報)へ保持するかです。
user はデフォルト保持なので、保持したくない場合 false
を指定します。
その他の token 系情報は、デフォルト保持しないなので、保持したい場合 true
を指定します。
Auth0 のインスタンスを作るときのパラメータはたくさんあります。詳しくはソースを見るのが手っ取り早いです。
/**
* @return \Auth0\SDK\Auth0
*/
private function auth0(): Auth0
{
return new Auth0([
'domain' => env('AUTH0_DOMAIN', ''),
'client_id' => env('AUTH0_CLIENT_ID', ''),
'client_secret' => env('AUTH0_CLIENT_SECRET', ''),
'redirect_uri' => env('AUTH0_CALLBACK_URL', ''),
// refresh token が必要な場合は、scope へ offline_access を追加する
// @see https://auth0.com/docs/tokens/guides/get-refresh-tokens
'scope' => 'openid profile email',
// 'scope' => 'openid profile email offline_access',
// exchange (code から access token を取得) 後、ユーザー情報を取得する場合は true とする
// access token と一緒に返却される id token(JWT) を decode したユーザー情報でよければ false (デフォルト) とする
// 'skip_userinfo' => false,
// access token を Store (Session情報) へ保持しない場合は false とする
// 'persist_user' => false,
// access token を Store (Session情報) へ保持する場合は true とする
'persist_access_token' => true,
// refresh token を Store (Session情報) へ保持する場合は true とする
// 'persist_refresh_token' => true,
// id token を Store (Session情報) へ保持する場合は true とする
// 'persist_id_token' => true,
]);
}
login の実装
Auth0 のログイン画面を表示するアクションです。
$auth0->login()
でも良いのですが、呼び出したメソッド先で勝手に exit されるのが気持ち悪く感じたので、あえて $auth0->getLoginUrl()
でリダイレクトURLを取得して、自身でリダイレクトを実行させています。
それと、 Code を受け取るときの Callback でエラーを発生させないために、ここで Session クリアを実施しています。
/**
* @return \Cake\Http\Response|null
*/
public function login(): ?Response
{
// Session を破棄して、Auth0のユーザー情報を空にする。
// そうしないと、Auth0->exchange() で
// Can't initialize a new session while there is one active session already
// が発生する。
$this->getRequest()->getSession()->destroy();
$auth0 = $this->auth0();
$loginUrl = $auth0->getLoginUrl();
return $this->redirect($loginUrl);
}
callback の実装
Authorization Code Flow での処理で Code を受け取るアクションです。
Code から AccessToken / IdToken を取得する $auth0->exchange()
を呼び出します。
queryパラメータからの Code の取得や queryパラメータの State のチェックは $auth0->exchange()
のメソッド内で適切に実施してくれます。
デフォルトの場合、State のチェックは Cookie を使っており、チェック時に Cookie から値を削除するため、2度 $auth0->exchange()
を呼び出すと Stateが不正である旨のエラーが発生します。気を付けてください。
/**
* @return \Cake\Http\Response|null
*/
public function callback(): ?Response
{
$auth0 = $this->auth0();
$auth0->exchange();
return $this->render();
}
cache の実装
Auth0 が Store(デフォルトセッション情報)に保持している内容を確認します。
今回の Auth0 のインスタンスを作るときの設定であれば、 $auth0->getUser()
$auth0->getAccessToken()
は Store に保持されており、残りは保持されていません。
/**
* @return \Cake\Http\Response|null
*/
public function cache(): ?Response
{
$auth0 = $this->auth0();
debug($auth0->getUser());
debug($auth0->getAccessToken());
debug($auth0->getIdToken());
debug($auth0->getRefreshToken());
return $this->render();
}
user の実装
Authentication API を使ってユーザー情報を取得します。
このときにログインユーザーの AccessToken を使います。
/**
* @return \Cake\Http\Response|null
*/
public function user(): ?Response
{
$auth0 = $this->auth0();
/** @var string $domain */
$domain = env('AUTH0_DOMAIN', '');
/** @var string $clientId */
$clientId = env('AUTH0_CLIENT_ID', '');
$authApi = new Authentication($domain, $clientId);
/** @var string $accessToken */
$accessToken = $auth0->getAccessToken();
$user = $authApi->userinfo($accessToken);
debug($user);
return $this->render();
}
logout の実装
Authentication API を使ってログアウトURLを取得します。
/**
* @return \Cake\Http\Response|null
*/
public function logout(): ?Response
{
$auth0 = $this->auth0();
$auth0->logout();
/** @var string $domain */
$domain = env('AUTH0_DOMAIN', '');
/** @var string $clientId */
$clientId = env('AUTH0_CLIENT_ID', '');
$authApi = new Authentication($domain, $clientId);
/** @var string $returnTo */
$returnTo = env('AUTH0_LOGOUT_URL', '');
$logoutUrl = $authApi->get_logout_link($returnTo, $clientId);
return $this->redirect($logoutUrl);
}
AuthController の実装は以上です。
routes の定義
AuthContoller のルートを定義します。
Code を受け取る callback URL は、Auth0 Application にセットした内容に合わせる必要があります。
// ... snip
$routes->scope('/', function (RouteBuilder $builder) {
// Register scoped middleware for in scopes.
$builder->registerMiddleware('csrf', new CsrfProtectionMiddleware([
'httpOnly' => true,
]));
$builder->applyMiddleware('csrf');
$builder->connect('/login', ['controller' => 'Auth', 'action' => 'login']);
$builder->connect('/logout', ['controller' => 'Auth', 'action' => 'logout']);
$builder->connect('/auth/callback', ['controller' => 'Auth', 'action' => 'callback']);
$builder->connect('/auth/user', ['controller' => 'Auth', 'action' => 'user']);
$builder->connect('/auth/cache', ['controller' => 'Auth', 'action' => 'cache']);
// ... snip
});
// ... snip
これで
- ログイン( http://localhost/login )
- ログインユーザー情報取得( http://localhost/auth/cache , http://localhost/auth/user )
- ログアウト( http://localhost/logout )
の動きを確認することができます。
補足
Auth0 でGoogleソーシャルログインをさせない
Auth0 の Applications から設定できます。
- 左メニュー「Applications」をクリック
- 一覧から該当する Applicationリンクをクリック
- タブ「Connections」をクリック
- Social > google-oauth2 を OFF にする。
Auth0 でサインアップをさせない
Auth0 の Connections から設定できます。
- 左メニュー「Connections」 > 「Database」をクリック
- 一覧から該当するDatabase Connections(「Username-Password-Authentication」)をクリック
- 「Disable Sign Ups」を ON にする。
Auth0 のログイン画面を日本語にする
Auth0 の Tenant Settings から設定できます。
- 右上ログインアカウントプルダウンメニュー「Settings」をクリック
- Default Language:「Japanese(ja)」を選択して「SAVE」をクリック
AuthController の全体ソース
<?php
declare(strict_types=1);
namespace App\Controller;
use Auth0\SDK\API\Authentication;
use Auth0\SDK\Auth0;
use Cake\Http\Response;
/**
* Auth Controller
*/
class AuthController extends AppController
{
/**
* @return \Auth0\SDK\Auth0
*/
private function auth0(): Auth0
{
return new Auth0([
'domain' => env('AUTH0_DOMAIN', ''),
'client_id' => env('AUTH0_CLIENT_ID', ''),
'client_secret' => env('AUTH0_CLIENT_SECRET', ''),
'redirect_uri' => env('AUTH0_CALLBACK_URL', ''),
// refresh token が必要な場合は、scope へ offline_access を追加する
// @see https://auth0.com/docs/tokens/guides/get-refresh-tokens
'scope' => 'openid profile email',
// 'scope' => 'openid profile email offline_access',
// exchange (code から access token を取得) 後、ユーザー情報を取得する場合は true とする
// access token と一緒に返却される id token(JWT) を decode したユーザー情報でよければ false (デフォルト) とする
// 'skip_userinfo' => false,
// access token を Store (Session情報) へ保持しない場合は false とする
// 'persist_user' => false,
// access token を Store (Session情報) へ保持する場合は true とする
'persist_access_token' => true,
// refresh token を Store (Session情報) へ保持する場合は true とする
// 'persist_refresh_token' => true,
// id token を Store (Session情報) へ保持する場合は true とする
// 'persist_id_token' => true,
]);
}
/**
* @return \Cake\Http\Response|null
*/
public function login(): ?Response
{
// Session を破棄して、Auth0のユーザー情報を空にする。
// そうしないと、Auth0->exchange() で
// Can't initialize a new session while there is one active session already
// が発生する。
$this->getRequest()->getSession()->destroy();
$auth0 = $this->auth0();
$loginUrl = $auth0->getLoginUrl();
return $this->redirect($loginUrl);
}
/**
* @return \Cake\Http\Response|null
*/
public function logout(): ?Response
{
$auth0 = $this->auth0();
$auth0->logout();
/** @var string $domain */
$domain = env('AUTH0_DOMAIN', '');
/** @var string $clientId */
$clientId = env('AUTH0_CLIENT_ID', '');
$authApi = new Authentication($domain, $clientId);
/** @var string $returnTo */
$returnTo = env('AUTH0_LOGOUT_URL', '');
$logoutUrl = $authApi->get_logout_link($returnTo, $clientId);
return $this->redirect($logoutUrl);
}
/**
* @return \Cake\Http\Response|null
*/
public function callback(): ?Response
{
$auth0 = $this->auth0();
$auth0->exchange();
return $this->render();
}
/**
* @return \Cake\Http\Response|null
*/
public function user(): ?Response
{
$auth0 = $this->auth0();
/** @var string $domain */
$domain = env('AUTH0_DOMAIN', '');
/** @var string $clientId */
$clientId = env('AUTH0_CLIENT_ID', '');
$authApi = new Authentication($domain, $clientId);
/** @var string $accessToken */
$accessToken = $auth0->getAccessToken();
$user = $authApi->userinfo($accessToken);
debug($user);
return $this->render();
}
/**
* @return \Cake\Http\Response|null
*/
public function cache(): ?Response
{
$auth0 = $this->auth0();
debug($auth0->getUser());
debug($auth0->getAccessToken());
debug($auth0->getIdToken());
debug($auth0->getRefreshToken());
return $this->render();
}
}