Webセッションのワンタイムパスワード (OTP) の使い方

今日、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 をダウンロードして、実行してみてください。詳細についてはドキュメンテーションを参照ください。

    Avatar
    - プロダクトオーナー - Marie-Sophie Landrieu-Yvertは、2017年にプロダクトオーナーとして4Dプロダクトチームに参加しました。プロダクトオーナーとして、彼女はユーザーストーリー(ユーザーが期待する新機能とその使用法)を書き、それを具体的な機能仕様に変換する役割を担っています。また彼女の役割は、実装された機能が顧客のニーズを満たしているかどうかを確認することでもあります。彼女は1995年にESIGELEC Engineering Schoolを卒業し、IBMでエンジニアとしてのキャリアをスタートさせました。様々なプロジェクト(保守や新規のプロジェクト)に参加し、Cobolのデベロッパーとして働きました。その後、UMLデザイナーおよびJavaデベロッパーとして勤務。最近は、機能要件の分析・記述、ビジネスチームと開発チームの調整などを主に担当しています。