SaaSus SDK をWebアプリケーションに組み込みマルチテナント化する
Web アプリケーションに SaaSus SDK を組み込む
※ここからはプログラミングの知識が必要になります
SaaS ID と API キー、クライアントシークレットの再確認
まず、SaaSus 開発コンソールにて API キーを表示しておきましょう。これをアプリケーションの設定に使います
(API キーは決して外部に漏らさないようにお気をつけください。このチュートリアルに表示されている API キーは既に無効化してあります)
SaaSus SDK 利用の準備
では、さきほど準備したサンプルアプリケーションを開発環境で開きましょう。
冒頭で行ったとおり、 init.sh を利用して docker コンテナが立ち上がっており、
PHPならhttp://localhost/board で、
Node.jsならhttp://localhost/login/で、サンプルアプリケーションが動いていることを確 認してください。
まず、 SaaSus Platform を簡単に使うためには、 SaaSus SDK をアプリケーションに組み込みます。
PHP なら composer を
Node.js なら npm を使います。
ターミナルを開きSDKのセットアップをします。
- 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
root@xxx:/var/www/api# composer require saasus-platform/saasus-sdk-php
repo$ docker compose exec node bash
root@xxx:/app# npm install saasus-sdk
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 のルートに認証を必要とします。認証されていない場合には、アプリケーションが利用できない仕様にします。
現状は、
PHPの場合は、api/routes/web.php
Node.jsの場合は、api/app.ts
にて認証機能が利用されているので、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 の認証ミドルウェアが適用されたため、個別のルートでの認証チェックは不要 になります。
PHPの場合(変更不要)
Node.jsの場合は、api/routes/chat.ts
- Node.js
router.get(
"/",
(req: Request, res: Response, next: NextFunction) => {
if (req.isAuthenticated()) {
next();
} else {
res.redirect(302, "/login");
}
},
getChats
);
を
- Node.js
router.get("/", getChats);
そして、認証画面からのコールバックの受口を用意します。
先ほど SaaSus 開発コンソールでコールバック先を
と定義したので、 /callback で受け取れるようにします。
PHPの場合は、 api/routes/web.php の最後の行に以下を追加します。
- PHP
// SaaSus SDK標準のCallback Controllerを利用して、JWTをCookieやLocal Storageに入れる
Route::get('/callback', 'AntiPatternInc\Saasus\Laravel\Controllers\CallbackController@index');
さらに、SaaSus SDK が提供する View を使えるように
api/config/view.php にパスを追加します。
- PHP
'paths' => [
resource_path('views'),
# ↓この行を追加:SaaSus SDKが提供するViewのディレクトリ
resource_path('../vendor/saasus-platform/saasus-sdk-php/src/Laravel/Views'),
],
Node.jsの場合は、 api/app.ts に import文 と app.use を追加することでルートを追加します。
- Node.js
import { router as callbackRouter } from "./routes/callback";
// 他のコードは省略
app.use("/callback", callbackRouter);
/callback の処理を実装するために、api/routes/callback.ts を作成し、以下を追加してください。
- Node.js
import express from "express";
const router = express.Router();
import { CallbackRouteFunction } from "saasus-sdk";
router.get("/", CallbackRouteFunction);
export { router };
api/views/callback.ejs を作成し、以下を追加してください。
- Node.js
<!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 の一部として渡ってくるようになります。
PHPの場合は、api/app/Http/Controllers/MessageController.php の index に Request の引数を追加し、 dd を使って $request に userinfo が入っているか確認してみましょう。
Node.jsの場合は、api/controllers/chat.ts の getChats に、console.logを使って req に userinfo が入っているか確認してみましょう。
- PHP
- Node.js
public function index(Request $request)
{
// SaaSus Platformからユーザ情報が渡ってきているかを確認する
dd($request->userinfo);
const getChats = async (req: Request, res: Response) => {
// SaaSus Platformからユーザ情報が渡ってきているかを確認する
console.dir(req.userInfo,{depth:null});
ここまでで連携の基本ができました。
では、実際に SaaSus Platform からログインして動作確認してみましょう。
SaaSus SDK 組み込みの確認
SaaSus Platform で作成したログイン画面を表示します。
https://auth.sample.saasus.jp など、ご自身で設定したドメインのログイン画面を表示してください。
先ほど作成したユーザのメールアドレスとパスワードでログインすると、Callback URL で設定した URL に、認証情報とともにリダイレクトされます。
たとえば、 user1-1@example.com でログインしてみましょう。
PHPの場合、実際に先ほどのコードが動作すると、ログイン後にこのような表示になるはずです。
Node.jsの場合は、ターミナルを開き以下のコマンドを実行してください。
root@xxx:/app# exit
repo$ docker docker compose logs -f
- PHP
- Node.js
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"
]
]
]
]
{
email: 'user1-1@example.com',
id: '951fe2e3-b89b-40cf-95db-2fc11f09cbdf',
tenants: [
{
back_office_staff_email: 'saasus-sample-tenant1@example.com',
completed_sign_up: true,
envs: [
{
id: 3,
name: 'prod',
roles: [ { display_name: '一般利用者', role_name: 'user' } ]
}
],
id: '2d76c5ed-8462-4de0-b107-97bb97b7e9e2',
is_paid: true,
name: 'テナントサンプルその1',
plan_id: 'ddb8b9e9-5fe4-48cc-846e-b9031552877a',
user_attribute: { username: 'ユーザ1−1' }
}
],
user_attribute: {}
}
アプリケーション側で、先ほど SaaSus Platform で設定したユーザ情報、テナント情報が取得できているのがわかります。
リダイレクト先の URL は、今回 SaaSus SDK 標準の Callback 処理で受けるようになっており(http://localhost/callback)、その処理の中で ブラウザの Local Storage や Cookie の中に認証情報を記憶します。
そして、 SaaSus SDK の Auth Middleware で、SaaSus Platform を利用し認証情報を検証しユーザ情報を取得して Request オブジェクトに詰めます。
そのあと、アプリケーションのコントローラに処理が移るので、この時点ですでにアプリケーションはログインした人の情報を持っていることになります。
では、この情報を使って、掲示板アプリケーションをマルチテナント対応にしてみましょう!
サンプルアプリケーションのマルチテナント化
PHPの場合は、api/app/Http/Controllers/MessageController.php が
Node.jsの場合は、api/controllers/chat.ts が
メインの処理なので、ここにマルチテナント対応にするための処理を入れてみましょう。
まず、表示の部分を変更します。
下記の部分をまるごと書き換えてみましょう。
- 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: req.userInfo?.tenants[0].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 を表示するようにしてみます。
PHPの場合は、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>
Node.jsの方は修正不要です。
これでマルチテナント対応ができました。
では、早速ログインして試してみましょう。
先ほどと同じように、SaaSus Platform で作成したログイン画面からログインを行います。
ログインすると、テナント名が先ほど SaaS 開発コンソールで設定したものに変わっているのが確認できます。
まだデータが無いので、いくつか投稿をしてみましょう。
ユーザ名も表示されていることが確認できました。
では、もう一度ログイン画面に戻り、 user1-2@example.com でログインして、いくつか投稿してみましょう。
当然画面に反映されます。
では、もうひとつのテナントのユーザ、 user2-1@example.com でログインしてみましょう
テナント名の表示が変わり、内容が空になっていることが確認できます。
自分のテナントの情報にしかアクセスできないことが確認できました。
では、同じようにいくつか投稿をした後に、 user2-2@example.com でログインし、同一テナントの情報が表示できることを確認します。
このように、テナントごとの分離が完了しました。
今回の分離方式としては、プール型モデルで同一 DB 内での分離を行いシンプルな方式でテナント分離を行いました。スキーマ分離、データベース分離など、要件に応じてテナント分離の方式を選択する場合においても、同様に SaaSus SDK を利用してテナント情報を取得し実装することができます。
テナント分離ができたので、今度は料金関係の機能の実装をしてみましょう。
料金設定(プライシング)、利用量計測(メータリング)、請求(ビリング)の第一歩を実装してみます。請求においては Stripe という請求 SaaS を利用します。Stripe を使わない場合は、請求の部分はスキップしてください。