Express
認証モジュールの組み込み
SaaSus Platform の認証モジュールを組み込みます。
今回のアプリケーションでは、すべての URI のルートに認証を必要とします。認証されていない場合には、アプリケーションが利用できない仕様にします。
現状は api/app.ts にて認証機能が利用されているので、SaaSus の認証機能に置き換えます。
- Express
app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());
を
- Express
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());
個別の認証処理の削除
Express はルートごとに個別の認証処理があったため、それらを削除します。
api/routes/chat.ts を修正します。
- Express
router.get(
"/",
(req: Request, res: Response, next: NextFunction) => {
if (req.isAuthenticated()) {
next();
} else {
res.redirect(302, "/login");
}
},
getChats
);
を
- Express
router.get("/", getChats);
Callback URL の処理を実装する
先ほど SaaS開発コンソールでコールバック先を http://localhost/callback と定義したので、/callback で受け取れるようにします。
api/app.ts を修正します。
注意
Express ではミドルウェアとルートは登録された順番に処理されるため、404 ハンドラが先に登録されていると、/callback へのリクエストが 404 エラーになり、ログインに失敗します。
`import { router as tenantRouter } from "./routes/tenant";` の下に追加
```
import { router as callbackRouter } from "./routes/callback";
```
app.use("/login", loginRouter); の下に追加
```
app.use("/callback", callbackRouter);
```
`api/routes/callback.ts` を作成し、SaaSus SDK が提供する `CallbackRouteFunction` を使って `/callback` の GET リクエストを処理します。
<Tabs>
<TabItem value="express" label="Express" default>
```js
import express from "express";
const router = express.Router();
import { CallbackRouteFunction } from "saasus-sdk";
router.get("/", CallbackRouteFunction);
export { router };
```
</TabItem>
</Tabs>
ログイン後に遷移する画面として `views/callback.ejs` を作成します(今回は `/chat` へリダイレクト)。
<Tabs>
<TabItem value="ejs" label="Express (EJS)" default>
```html
<!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>
```
</TabItem>
</Tabs>
ここまで設定すると、アプリケーションの Controller にたどり着いた時点で、SaaSus Platform で設定した認証情報が request の一部として渡ってくるようになります。
`api/controllers/chat.ts` の `getChats` メソッド内で `console.log` を使い、`req` に `userinfo` が渡されているか確認してみましょう。
<Tabs>
<TabItem value="express" label="Express" default>
```js
const getChats = async (req: Request, res: Response) => {
// SaaSus Platformからユーザ情報が渡ってきているかを確認する
console.dir(req.userInfo,{depth:null});
```
</TabItem>
</Tabs>
ここまでで連携の基本ができました。
実際に SaaSus Platform からログインして動作を確認します。
### SaaSus SDK 組み込みの確認
SaaSus Platform で作成したログイン画面を表示します。
ログイン画面のURLはサイドメニューの「認証詳細設定」からログイン画面で確認できます。


先ほど作成したユーザのメールアドレスとパスワードでログインすると、Callback URL で設定した URL に、認証情報とともにリダイレクトされます。
たとえば、 [user1-1@example.com](mailto:user1-1@example.com) でログインしてみましょう。
まずターミナルで以下のコマンドを実行してログを確認できる状態にしてから、ログインを行ってください。
<Tabs>
<TabItem value="command" label="Command" default>
```bash
repo$ docker compose logs -f
```
</TabItem>
</Tabs>
<Tabs>
<TabItem value="docker-log" label="Express (Dockerログ)" default>
```js
{
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: {}
```
</TabItem>
</Tabs>
アプリケーション側で、先ほど SaaSus Platform で設定したユーザ情報、テナント情報が取得できているのがわかります。
リダイレクト先の URL は、今回 SaaSus SDK 標準の Callback 処理で受けるようになっており([http://localhost/callback](http://localhost/callback))、その処理の中でブラウザの Local Storage や Cookie の中に認証情報を記憶します。
そして、SaaSus SDK の Auth Middleware で、SaaSus Platform を利用し認証情報を検証しユーザ情報を取得して Request オブジェクトに詰めます。
そのあと、アプリケーションのコントローラに処理が移るので、この時点ですでにアプリケーションはログインした人の情報を持っていることになります。
では、この情報を使って、掲示板アプリケーションをマルチテナント対応にしてみましょう。
### サンプルアプリケーションのマルチテナント化
`api/controllers/chat.ts` がメインの処理なので、ここにマルチテナント対応にするための処理を入れてみましょう。
まず、表示の部分を変更します。下記の部分をまるごと書き換えてみましょう。
<Tabs>
<TabItem value="express" label="Express" default>
```js
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");
}
};
```
</TabItem>
</Tabs>
このようにして、渡ってきたテナント ID をもとに DB を検索するようにします。
次に投稿の部分です。
<Tabs>
<TabItem value="express" label="Express" default>
```js
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");
};
```
</TabItem>
</Tabs>
渡ってきたユーザ属性をもとに、テナント ID、ユーザ名をセットで格納します。
これでマルチテナント対応ができました。
では、早速ログインして試してみましょう。
先ほどと同じように、SaaSus Platform で作成したログイン画面からログインを行います。
ログインすると、テナント名が先ほど SaaS開発コンソールで設定したものに変わっているのが確認できます。

まだデータが無いので、いくつか投稿をしてみましょう。

ユーザ名も表示されていることが確認できました。
では、もう一度ログイン画面に戻り、 [user1-2@example.com](mailto:user1-2@example.com) でログインして、いくつか投稿してみましょう。

当然画面に反映されます。
では、もうひとつのテナントのユーザ、 [user2-1@example.com](mailto:user2-1@example.com) でログインしてみましょう。

テナント名の表示が変わり、内容が空になっていることが確認できます。
自分のテナントの情報にしかアクセスできないことが確認できました。
では、同じようにいくつか投稿をした後に、 [user2-2@example.com](mailto:user2-2@example.com) でログインし、同一テナントの情報が表示できることを確認します。

このように、テナントごとの分離が完了しました。
今回の分離方式としては、プール型モデルで同一 DB 内での分離を行いシンプルな方式でテナント分離を行いました。スキーマ分離、データベース分離など、要件に応じてテナント分離の方式を選択する場合においても、同様に SaaSus SDK を利用してテナント情報を取得し実装することができます。