SaaSus SDK をWebアプリケーションに組み込みマルチテナント化する
Web アプリケーションに SaaSus SDK を組み込む
※ここからはプログラミングの知識が必要になります
SaaS ID と API キー、クライアントシークレットの再確認
まず、SaaSus 開発コンソールにて API キーを表示しておきましょう。これをアプリケーションの設定に使います
(API キーは決して外部に漏らさないようにお気をつけください。このチュートリアルに表示されている API キーは既に無効化してあります)
SaaSus SDK 利用の準備
では、さきほど準備したサンプルアプリケーションを開発環境で開きましょう。
冒頭で行ったとおり、 init.sh を利用して docker コンテナが立ち上がっており、http://localhost/board でサンプルアプリケーションが動いていることを確認しておいてください。
まず、 SaaSus Platform を簡単に使うためには、 SaaSus SDK をアプリケーションに組み込 みます。
今回は PHP なので、 composer を使います。
ターミナルを開き php のコンテナに入り、composer を使ってセットアップをします。
- PHP
- Node.js
repo$ docker compose exec php bash
root@xxx:/var/www# cd api
root@xxx:/var/www/api# composer config repositories.saasus-platform/saasus-sdk-php vcs https://github.com/saasus-platform/saasus-sdk-php
repo$ docker compose exec node bash
api ディレクトリで、 composer コマンドを使って追加します
root@xxx:/var/www/api# composer require saasus-platform/saasus-sdk-php
SaaSus SDK がインストールできたら、まずは SaaSus SDK 利用にあたって必要な環境変数を定義しましょう
api ディレクトリの .env.example ファイルをコピーして.env ファイルを作成し、その.env ファイルの一番下の以下の部分を編集します
### for SaaSus Platform
SAASUS_SAAS_ID="98tjo3wifaoua (画面のSaaS ID)"
SAASUS_API_KEY="kjntowjfoasnkjntwonsflajsno4as (画面のAPI KEY)"
SAASUS_SECRET_KEY=" (画面のクライアントシークレット)"
SAASUS_LOGIN_URL="https://auth.sample.saasus.jp/ (ログイン画面のURL)"
SAASUS_SAAS_ID, SAASUS_API_KEY, SAASUS_SECRET_KEY は先ほど画面で表示した SaaS ID と API Key、クライアントシークレットを、
SAASUS_LOGIN_URL は、SaaSus 開発コンソールで作成したログイン画面の URL を設定します。
認証モジュールの組み込み
次に、 SaaSus Platform の認証モジュールを組み込みます。
今回のアプリケーションでは、すべての URI のルートに認証を必要とします。認証されていない場合には、アプリケーションが利用できない仕様にします。そのため、 Laravel の Middleware として SaaSus 認証機能を組み込みます。
現状は、
api/routes/web.php
にて、Laravel 標準の認証機能が利用されているので、SaaSus の認証機能に置き換えます。
この部分
- PHP
- Node.js
Route::middleware('auth')->group(function () {
Route::get('/', function () {
return view('welcome');
});
Route::get('/dispatch', 'App\Http\Controllers\DispatchController@index')->name('dispatch');
Route::get('/board', 'App\Http\Controllers\MessageController@index')->name('board');
Route::post('/post', 'App\Http\Controllers\MessageController@post')->name('post');
});
require __DIR__ . '/auth.php';
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());
を
- PHP
- Node.js
// Route::middleware('auth')->group(function () {
// Route::get('/', function () {
// return view('welcome');
// });
// SaaSus SDK標準のAuth Middlewareを利用する
Route::middleware(\AntiPatternInc\Saasus\Laravel\Middleware\Auth::class)->group(function () {
Route::get('/dispatch', 'App\Http\Controllers\DispatchController@index')->name('dispatch');
Route::get('/board', 'App\Http\Controllers\MessageController@index')->name('board');
Route::post('/post', 'App\Http\Controllers\MessageController@post')->name('post');
Route::redirect('/', '/board');
});
// require __DIR__ . '/auth.php';
import { AuthMiddleware } from "saasus-sdk";
...
// app.use(
// session({
// secret: "secret",
// resave: false,
// saveUninitialized: false,
// })
// );
app.use(
["/chat", "/api/board", "/api/post", "/api/plan", "/api/tenant"],
AuthMiddleware
);
// app.use(passport.initialize());
// app.use(passport.session());
このようにします。
そして、認証画面からのコールバックの受口を用意します。先ほど SaaSus 開発コンソールでコールバック先を http://localhost/callback と定義したので、 /callback で受け取れるようにします。
おなじく、 api/routes/web.php の最後の行に以下を追加します。
- PHP
- Node.js
// SaaSus SDK標準のCallback Controllerを利用して、JWTをCookieやLocal Storageに入れる
Route::get('/callback', 'AntiPatternInc\Saasus\Laravel\Controllers\CallbackController@index');
import { CallbackRouter } from "saasus-sdk";
...
app.use("/callback", callbackRouter);
さらに、SaaSus SDK が提供する View を使えるように api/config/view.php にパスを追加します
- PHP
- Node.js
'paths' => [
resource_path('views'),
# ↓この行を追加:SaaSus SDKが提供するViewのディレクトリ
resource_path('../vendor/saasus-platform/saasus-sdk-php/src/Laravel/Views'),
],
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Auth Callback</title>
</head>
<body>
<script>
location.href = "/chat";
</script>
</body>
</html>
ここまで設定すると、 アプリケーションの Controller にたどり着いた時点で、SaaSus Platform で設定した認証情報が request の一部として渡ってくるようになります。
api/app/Http/Controllers/MessageController.php の index に Request の引数を追加し、 dd を使って $request に userinfo が入っているか確認してみましょう
以下の4行を書き換えてください。
- PHP
- Node.js
public function index(Request $request)
{
// SaaSus Platformからユーザ情報が渡ってきているかを確認する
dd($request->userinfo);
const getChats = async (req: Request, res: Response) => {
// SaaSus Platformからユーザ情報が渡ってきているかを確認する
console.log(req.userinfo);
ここまでで連携の基本ができました。
では、実際に SaaSus Platform からログインして動作確認してみましょう。
SaaSus SDK 組み込みの確認
SaaSus Platform で作成したログイン画面を表示します。
https://auth.sample.saasus.jp など、ご自身で設定したドメインのログイン画面を表示してください。
先ほど作成したユーザのメールアドレスとパスワードでログインすると、Callback URL で設定した URL に、認証情報とともにリダイレ クトされます。
たとえば、 user1-1@example.com でログインしてみましょう。
実際に先ほどのコードが動作すると、ログイン後にこのような表示になるはずです。
array:3 [▼
"email" => "user1-1@example.com"
"id" => "f6a02019-1306-431f-b93d-3a756b312481"
"tenants" => array:1 [▼
0 => array:7 [▼
"back_office_staff_email" => "saasus-sample-tenant1@example.com"
"completed_sign_up" => true
"envs" => array:1 [▼
0 => array:3 [▼
"id" => 1
"name" => "dev"
"roles" => array:1 [▼
0 => array:2 [▼
"display_name" => "一般利用者"
"role_name" => "user"
]
]
]
]
"id" => "7b639774-6fba-4b26-b580-f3d755876a4b"
"name" => "テナントのサンプルその1"
"plan_id" => "bc011444-a9f1-41c0-8251-bc8928b09ee7"
"user_attribute" => array:1 [▼
"username" => "user1-1"
]
]
]
]
アプリケーション側で、先ほど SaaSus Platform で設定したユーザ情報、テナント情報が取得できているのがわかります。
リダイレクト先の URL は、今回 SaaSus SDK 標準の Callback 処理で受けるようになっており(http://localhost/callback)、その処理の中で ブラウザの Local Storage や Cookie の中に認証情報を記憶します。
そして、 SaaSus SDK の Auth Middleware で、SaaSus Platform を利用し認証情報を検証しユーザ情報を取得して Request オブジェクトに詰めます。
そのあと、アプリケーションのコントローラに処理が移るので、この時点ですでにアプリケーションはログインした人の情報を持っていることになります。
では、この情報を使って、掲示板アプリケーションをマルチテナント対応にしてみましょう!
サンプルアプリケーションのマルチテナント化
api/app/Http/Controllers/MessageController.php がメインの処理なので、ここにマルチテナント対応にするための処理を入れてみましょう。
まず、表示の部分を変更します。
public function index の部分をまるごと書き換えてみましょう。
- PHP
- Node.js
public function index(Request $request)
{
// $request->userinfo に各種ユーザ情報、テナント情報が入ってくるので、それを使う
$messages = Message::where('tenant_id', $request->userinfo['tenants'][0]['id'])->get();
return view('messageBoard.index', ['messages' => $messages, 'plans' => $this::PLANS, 'tenant_name' => $request->userinfo['tenants'][0]['name']]);
}
const getChats = async (req: Request, res: Response) => {
try {
const messages = await db.Messages.findAll({
where: {
tenant_id: req.userInfo?.tenants[0].id,
},
});
res.render("chat", {
messages: messages,
plans: PLANS,
tenant_name: TENANT_NAME,
});
} catch (error) {
console.error(error);
res.redirect("/chat");
}
};
このようにして、渡ってきたテナント ID をもとに DB を検索するようにします。
次に投稿の部分です。
- PHP
- Node.js
public function post(Request $request)
{
$validated = $request->validate([
'message' => 'required|max:255'
]);
// $request の userinfo から各種情報を取得し、判断に使う
$message = Message::create([
'tenant_id' => $request->userinfo['tenants'][0]['id'],
'user_id' => $request->userinfo['tenants'][0]['user_attribute']['username'],
'message' => $request->message,
]);
$request->session()->regenerateToken();
return redirect()->route('board');
}
const postChats = async (req: Request, res: Response) => {
const mes = req.body.message;
const tenantId = req.userInfo?.tenants[0].id || "";
const userName =
req.userInfo?.tenants[0].user_attribute.username || "テストユーザー";
try {
await db.Messages.create({
tenant_id: tenantId,
user_id: userName,
message: mes,
});
} catch (error) {
console.error(error);
}
res.redirect("/chat");
};
渡ってきたユーザ属性をもとに、テナント ID、ユーザ名をセットで格納します。
画面表示の部分も、ユーザ ID を表示するようにしてみます。
api/resources/views/messageBoard/index.blade.php を編集します。
32 行目あたりの、この $message->user->name の部分を $message->user_id に変更します。
修正前:
<div class="mt-4">
<p>
{{ $message->user->name }}
<span class="text-xs text-gray-500">
{{ $message->created_at->format('Y/m/d H:i') }}
</span>
</p>
修正後:
<div class="mt-4">
<p>
{{ $message->user_id }}
<span class="text-xs text-gray-500">
{{ $message->created_at->format('Y/m/d H:i') }}
</span>
</p>
これでマルチテナント対応ができました!
では、早速ログインして試してみましょう。
先ほどと同じように、SaaSus Platform で作成したログイン画面からログインを行います。
ログインすると、テナント名が先ほど SaaS 開発コンソールで設定したものに変わっているのが確認できます。
まだデータが無いので、いくつか投稿をしてみましょう。
ユーザ名も表示されていることが確認できました。
では、もう一度ログイン画面に戻り、 user1-2@example.com でログインして、いくつか投稿してみましょう。
当然画面に反映されます。
では、もうひとつのテナントのユーザ、 user2-1@example.com でログインしてみましょう
テナント名の表示が変わり、内容が空になっていることが確認できます。
自分のテナントの情報にしかアクセスできないことが確認できました。
では、同じようにいくつか投稿をした後に、 user2-2@example.com でログインし、同一テナントの情報が表示できることを確認します。
このように、テナントごとの分離が完了しました。
今回の分離方式としては、プール型モデルで同一 DB 内での分離を行いシンプルな方式でテナント分離を行いました。スキーマ分離、データベース分離など、要件に応じてテナント分離の方式を選択する場合においても、同様に SaaSus SDK を利用してテナント情報を取得し実装することができます。
テナント分離ができたので、今度は料金関係の機能の実装をしてみましょう。
料金設定(プライシング)、利用量計測(メータリング)、請求(ビリング)の第一歩を実装してみます。請求においては Stripe という請求 SaaS を利用します。Stripe を使わない場合は、請求の部分はスキップしてください。