今日、Webアプリケーションは私たちの生活に欠かせないものとなり、時間を節約し、日々の業務を簡素化する便利な機能を提供しています。たとえば、様々なプラットフォームでアカウントを作成することは、Webサイト上で最も頻繁におこなわれるユーザーアクションの一つといえます。
自宅でも、通勤中でも、ビーチでくつろいでいるときでも、このようなプロセスは手軽に完了できることが期待されています。
しかし、シンプルさの裏には、より複雑な現実があります。このような操作には、電子メール認証サービスなど、サードパーティのシステムとの連携が必要になることが多いため、セキュリティ、ユーザー体験の継続性、中間者攻撃 (MitM攻撃) からの保護に関する課題が生じます。
デベロッパーにとって、スムーズなユーザー体験を保証することは、外部システムと 4D Webセッション間のやりとりを管理することを意味します。これには、ユーザーのコンテキストを維持すること、つまりデータや権限、プロセスをどこまで完了したかといった情報を再取得することが含まれます。
複雑そうですね? 実際にはそうでもありません! 4D 20 R9 で、サードパーティーシステムと安全かつ効率的に通信する堅牢な Webアプリケーションを構築する方法をご覧ください。
HDI: ワンタイムパスワード (OTP) セッショントークン
多くの場合、Webアプリケーションでは、次のようないくつかのステップが必要です:
商品の倉庫在庫を扱う場合:
- 商品を選択する
- 商品の選択を外部の登録システムに送信する
- アプリの商品一覧に戻る
または
Webサイトでアカウントを作成する場合:
- メールアドレスとパスワードを入力してアカウントを作成する
- 確認メールに記載されたリンクをクリックしてメールアドレスを認証し、アカウントの登録を完了する
これらのステップでは、サードパーティーのシステムと行き来する必要があります。したがって、4D Webセッションは、後で再取得できるように、サーバー上で一意に識別されなければなりません。
Session オブジェクトの createOTP() 関数
4D Webセッションの識別子は セッションクッキー (“4DSID_AppName”) です。このクッキーの値を、4D Webアプリとサードパーティーシステムの間でネットワーク上で交換することは、セキュリティ上の問題があります。
代わりに、Session オブジェクトで利用可能な新しい createOTP() 関数を使えば、どの Webプロセスでもワンタイムパスワード (OTP)を生成することができます。
この OTP はこのセッションに紐づけられ、当該セッションクッキーを持たないリクエストを受信した場合でも、セッションを再取得することができます。
この OTP は一度だけ使用でき、有効期限を設定することができます。
例
var $token : Text
$token:=Session.createOTP()
OTP を使用して Webセッションを再取得する
Webセッションは 2つの方法で取得できます。
URL の $4DSIDパラメーター
受信したリクエストの $4DSID パラメーターに、Webセッションに対応する有効な OTP が含まれている場合、そのセッションは自動的に再取得されます。
例
この例では、create() 関数を使用して、Users データクラスにユーザーアカウントを作成します。
電子メールとパスワードを格納する $infoオブジェクトが受信されると、新しいユーザーが作成され、いくつかの情報がセッションに保存されます。とくに、ユーザーアカウント登録プロセスの現在のステップ (メールアドレス確認中) とユーザーID が格納されます。
カレントセッションに対応する OTP が生成されます。最後に、$4DSID パラメーターにこの OTP を指定した URL が返されます。
// Users データクラス
Function create($info : Object) : Text
var $user : cs.UsersEntity
var $status : Object
var $token : Text
// Usersデータクラスの新規エンティティを作成します
$user:=This.new()
$user.fromObject($info)
$status:=$user.save()
Use (Session.storage)
Session.storage.status:=New shared object("step"; "メールアドレス確認中"; "email"; $user.email; "ID"; $user.ID)
End use
$token:=Session.createOTP()
return "https://my4DApp/validateEmail?$4DSID="+$token
その後、この URL が記載された確認メールをユーザーに送信します。URLプレフィックス /validateEmailは、HTTPリクエストハンドラー (HTTPHandlers.jsonファイル) によって処理されます。
[
{
"class": "RequestHandler",
"method": "validateEmail",
"regexPattern": "/validateEmail",
"verbs": "get"
}
]
以下は、RequestHandlerシングルトンの validateEmail() 関数です。
Function validateEmail() : 4D.OutgoingMessage
var $result:=4D.OutgoingMessage.new()
var $user : cs.UsersEntity
var $status : Object
If (Session.storage.status.step="メールアドレス確認中")
$user:=ds.Users.get(Session.storage.status.ID)
$user.validated:=True
$status:=$user.save()
$result.setBody("Congratulations <br>"\
+"Your email "+Session.storage.status.email+" has been validated")
$result.setHeader("Content-Type"; "text/html")
Use (Session.storage.status)
Session.storage.status.step:="メールアドレス確認済"
End use
Else
// OTPによるセッションの再取得に失敗した場合
$result.setBody("認証に失敗しました")
End if
return $result
$4DSIDパラメーターには元のセッションに対応する有効な OTP が含まれているため、Sessionオブジェクトはその OTP を作成したセッションを参照します。
ブラウザーでの結果は以下のとおりです:
Sessionオブジェクトの restore() 関数
通常、サードパーティーシステムが関与する場合は、コールバックメカニズムを適用します。原理は次のとおりです:
- サードパーティーシステムにリクエストを送り、コールバックURL を渡します
- そのリクエストを処理した後、サードパーティーシステムはコールバックURL を呼び出します
サードパーティーシステムは時々、$4DSID のようなカスタムパラメーターをコールバックURLに 含めることを許可しない場合があります。
しかし、クライアント情報 (state など) を送信することはできるため、サードパーティーシステムはこの情報を呼び出し元に返します。これは、4D WebセッションOTP におけるコールバック方法の一つです。
サードパーティーシステムの API のドキュメントをチェックして、使用できるパラメーター名を確認する必要があります。以下の例では、stateパラメーターを使用します。
restore() 関数は、Session オブジェクトが提供する関数で、指定された OTP に対応するセッションを復元することができます。
以下の例で、OTP は $4DSID パラメーターではなく、予約された stateパラメーターを使って URL に入れられます 。
例
4D Webアプリケーションで、倉庫の従業員が在庫を作るために製品を選択し、登録のために外部システムに送信します。ここでは、sendProducts() 関数を呼び出します:
exposed Function sendProducts($chosenProducts : cs.ProductsSelection) : Text
var $token; $callBackURL; $callExternalAppURL; $result : Text
var $request : Object
// 選択した商品を Sessionオブジェクトに保存します
Use (Session.storage)
Session.storage.info:=New shared object("inventoryStatus"; "Calling registring app")
Session.storage.chosenProducts:=$chosenProducts
End use
// 例として、$token は C318C81651F84F238EE72C14B46A45C3 とします
$token:=Session.createOTP()
// コールバックURL 構築します - ここで OTP を state パラメーターに設定します
$callBackURL:="https://my4DApp/callBack?state="+$token
// 外部の登録システムを呼び出します - redirectパラメーターにコールバックURL を渡します
$callExternalAppURL:="https://acme.com/callRegistringApp?redirect="+$callBackURL
$requestObj:={method: HTTP POST method}
$request:=4D.HTTPRequest.new($callExternalAppURL; $requestObj).wait()
// 外部の登録システムがコールバックを呼び出しました。このコールバックはセッションを復元し、更新しました。
// これにより、inventoryStatus 属性も最新に更新されています。
If (Position("Products registered"; Session.storage.info.inventoryStatus)#0)
$result:=Session.storage.info.inventoryStatus
Else
$result:="Registering failed"
End if
return $result
選択された商品が Sessionオブジェクトに保存され、OTP が生成されます。
サードパーティーのシステムが呼び出されます。redirectパラメーターはコールバックURL を示します。この URL は stateパラメーターを含み、これにセッションOTP が渡されます。
上記の例では、サードパーティーシステムの URL は次のとおりです:
https://acme.com/callRegistringApp?redirect=https://my4DApp/callBack?state=C318C81651F84F238EE72C14B46A45C3
URLプレフィックス /callBackは、4D Webアプリの HTTPリクエストハンドラー (HTTPHandlers.jsonファイル) によって処理されます。
[
{
"class": "RequestHandler",
"method": "handleCallBack",
"regexPattern": "/callBack",
"verbs": "post"
}
]
以下は、RequestHandlerシングルトンの handleCallback() 関数です:
Function handleCallBack($request : 4D.IncomingMessage)
// URL の state パラメーターを取得します
$otp:=$request.urlQuery.state
// OTP を使ってセッションを復元します
$restore:=Session.restore($otp)
// セッションの復元に成功しました
If ($restore=True)
// サードパーティーシステムが送信した情報が格納されている、リクエストの本文を取得します: "Products registered" (商品登録完了)
$text:=$request.getText()
// Sessionオブジェクトの情報を登録結果で更新します
If ($text#Null)
Use (Session.storage.info)
Session.storage.info.inventoryStatus:=$text
If (Session.storage.chosenProducts#Null)
Session.storage.info.inventoryStatus+=" total price: "+String(Session.storage.chosenProducts.sum("price"))
End if
End use
End if
End if
4Dクライアントのライセンス消費について
4Dクライアントライセンスを消費するのは、OTP を生成する元のセッションのみです。(たとえば、確認メールのリンクで) セッションを再取得しても、追加の 4D クライアントライセンスは消費されません。
添付の HDI をダウンロードして、実行してみてください。詳細についてはドキュメンテーションを参照ください。