メインコンテンツまでスキップ

Express

認証モジュールの組み込み

SaaSus Platform の認証モジュールを組み込みます。

今回のアプリケーションでは、すべての URI のルートに認証を必要とします。認証されていない場合には、アプリケーションが利用できない仕様にします。

現状は api/app.ts にて認証機能が利用されているので、SaaSus の認証機能に置き換えます。

app.use(
session({
secret: "secret",
resave: false,
saveUninitialized: false,
})
);
app.use(passport.initialize());
app.use(passport.session());

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 を修正します。

router.get(
"/",
(req: Request, res: Response, next: NextFunction) => {
if (req.isAuthenticated()) {
next();
} else {
res.redirect(302, "/login");
}
},
getChats
);

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"; //./routes/tenant の下に追加

// 他のコードは省略

app.use("/login", loginRouter);
app.use("/callback", callbackRouter); //app.use("/login", loginRouter);の下に追加

app.use((req: Request, res: Response, next: NextFunction) => {
next(createError(404));
});

api/routes/callback.ts を作成し、SaaSus SDK が提供する CallbackRouteFunction を使って /callback の GET リクエストを処理します。

import express from "express";
const router = express.Router();
import { CallbackRouteFunction } from "saasus-sdk";

router.get("/", CallbackRouteFunction);

export { router };

ログイン後に遷移する画面として views/callback.ejs を作成します(今回は /chat へリダイレクト)。

<!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/controllers/chat.tsgetChats メソッド内で console.log を使い、requserinfo が渡されているか確認してみましょう。

const getChats = async (req: Request, res: Response) => {
// SaaSus Platformからユーザ情報が渡ってきているかを確認する
console.dir(req.userInfo,{depth:null});

ここまでで連携の基本ができました。

実際に SaaSus Platform からログインして動作を確認します。

SaaSus SDK 組み込みの確認

SaaSus Platform で作成したログイン画面を表示します。

ログイン画面のURLはサイドメニューの「認証詳細設定」からログイン画面で確認できます。 15

09

先ほど作成したユーザのメールアドレスとパスワードでログインすると、Callback URL で設定した URL に、認証情報とともにリダイレクトされます。

たとえば、 user1-1@example.com でログインしてみましょう。

まずターミナルで以下のコマンドを実行してログを確認できる状態にしてから、ログインを行ってください。

repo$ docker compose logs -f
{
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 オブジェクトに詰めます。

そのあと、アプリケーションのコントローラに処理が移るので、この時点ですでにアプリケーションはログインした人の情報を持っていることになります。

では、この情報を使って、掲示板アプリケーションをマルチテナント対応にしてみましょう。

サンプルアプリケーションのマルチテナント化

api/controllers/chat.ts がメインの処理なので、ここにマルチテナント対応にするための処理を入れてみましょう。

まず、表示の部分を変更します。下記の部分をまるごと書き換えてみましょう。

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 を検索するようにします。

次に投稿の部分です。

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、ユーザ名をセットで格納します。

これでマルチテナント対応ができました。

では、早速ログインして試してみましょう。

先ほどと同じように、SaaSus Platform で作成したログイン画面からログインを行います。

ログインすると、テナント名が先ほど SaaS開発コンソールで設定したものに変わっているのが確認できます。

10

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

11

ユーザ名も表示されていることが確認できました。

では、もう一度ログイン画面に戻り、 user1-2@example.com でログインして、いくつか投稿してみましょう。

12

当然画面に反映されます。

では、もうひとつのテナントのユーザ、 user2-1@example.com でログインしてみましょう。

13

テナント名の表示が変わり、内容が空になっていることが確認できます。

自分のテナントの情報にしかアクセスできないことが確認できました。

では、同じようにいくつか投稿をした後に、 user2-2@example.com でログインし、同一テナントの情報が表示できることを確認します。

14

このように、テナントごとの分離が完了しました。

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