Today, web applications have become integral to our lives, offering convenient features that save time and simplify everyday tasks. As an example, creating accounts on various platforms is one the most frequent user action on web sites.
They expect this kind of process to be quick and accessible—whether at home, commuting, or relaxing on the beach.
Behind this simplicity lies a more complex reality. These operations often require integration with third-party systems, such as email verification services. This introduces challenges related to security, user experience continuity, and protecting against man-in-the-middle attacks.
For developers, ensuring a smooth experience means managing interactions between external systems and the 4D web session. This involves maintaining the user’s context—retrieving data, privileges, and the exact step of their journey to complete the process.
Sounds complicated? It doesn’t have to be! Discover how to build robust web applications that securely and efficiently communicate with third-party systems with 4D 20 R9.
Before going into details, watch this video. It will help you running the attached HDI.
Often, on web applications, the user experience requires several steps like:
When handling warehouse inventories of products:
- make a selection of products
- send the selection to an external registering system
- get back on the products list in the app
or
When creating an account on a website:
- enter the email + password to create the account
- validate the email by clicking on a link received in an email to finalize the account creation
Those steps require going back and forth with third-party systems. Thus, a 4D web session must be uniquely identified on the server to be retrieved later.
THE createOTP() FUNCTION ON THE SESSION OBJECT
The identifier of a 4D web session is the session cookie (“4DSID_AppName“). Exchanging this cookie value on the network between a 4D web application and a third-party system is a clumsy security breach.
To avoid that, in any web process, a One-Time-Passcode (OTP) can be generated thanks to the new createOTP() function available on the Session object.
This OTP is linked to this session and allows retrieval in a received request, which is not given the session cookie.
This OTP can be used once only and can be associated with a lifespan.
example
var $token : Text
$token:=Session.createOTP()
How do I retrieve a web session thanks to the OTP?
The web session can be retrieved in two ways.
the $4DSID parameter in a URL
The web session will be automatically retrieved if the received request contains in the $4DSID parameter a valid OTP corresponding to the session.
example
In this example, a user account is created in the Users dataclass with the create() function.
An $info object is received with the email + password. A new user is created, and some information is stored in the session, especially the current step of the user account creation process (Waiting for validation email) and the user ID.
An OTP corresponding to the current session is generated. Finally, an URL is returned with this OTP given in the $4DSID parameter.
// In the Users dataclass
Function create($info : Object) : Text
var $user : cs.UsersEntity
var $status : Object
var $token : Text
//A new entity of the Users dataclass is created
$user:=This.new()
$user.fromObject($info)
$status:=$user.save()
Use (Session.storage)
Session.storage.status:=New shared object("step"; "Waiting for validation email"; "email"; $user.email; "ID"; $user.ID)
End use
$token:=Session.createOTP()
return "https://my4DApp/validateEmail?$4DSID="+$token
Afterward, the user is sent this URL as a link in an email. The URL prefix /validateEmail is handled by the HTTP request handlers (HTTPHandlers.json file).
[
{
"class": "RequestHandler",
"method": "validateEmail",
"regexPattern": "/validateEmail",
"verbs": "get"
}
]
Here is the validateEmail() function of the RequestHandler singleton.
Function validateEmail() : 4D.OutgoingMessage
var $result:=4D.OutgoingMessage.new()
var $user : cs.UsersEntity
var $status : Object
If (Session.storage.status.step="Waiting for validation email")
$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:="Email validated"
End use
Else
// Retrieving the session with the OTP failed
$result.setBody("Validation failed")
End if
return $result
Because the $4DSID parameter contains a valid OTP corresponding to the original session, the Session object refers to the session that created the OTP
Here is the result in a browser:
THE restore() FUNCTION ON THE SESSION OBJECT
Usually, a callback mechanism is applied when a third-party system is involved. The principle is:
- send a request to the third-party system and pass a callback URL
- after processing the request, the third-party system calls the callback URL
Sometimes, third-party systems do not allow callback URLs to contain custom parameters like $4DSID.
However, they allow client information (e.g. a state) to be sent, and the third-party system returns this information to the caller. This is a way to be called back with a 4D web session OTP.
Check the documentation of the third-party system API to find the parameter name in question. The example below will use the state parameter.
A restore() function is available on the Session object and allows the retrieval of the session corresponding to a given OTP.
In the example below, the OTP is put in the reserved state parameter instead of being added in the URL as a $4DSID parameter.
example
On a 4D web application, a warehouse employee selects products to make an inventory and sends them to an external system for registring. Here is the sendProducts() function called:
exposed Function sendProducts($chosenProducts : cs.ProductsSelection) : Text
var $token; $callBackURL; $callExternalAppURL; $result : Text
var $request : Object
//The chosen products are put in the session
Use (Session.storage)
Session.storage.info:=New shared object("inventoryStatus"; "Calling registring app")
Session.storage.chosenProducts:=$chosenProducts
End use
// $token is C318C81651F84F238EE72C14B46A45C3 (example)
$token:=Session.createOTP()
//Build the callback URL - Put the OTP in the state parameter
$callBackURL:="https://my4DApp/callBack?state="+$token
// Call the external registring system - Put the callback URL in the redirect parameter
$callExternalAppURL:="https://acme.com/callRegistringApp?redirect="+$callBackURL
$requestObj:={method: HTTP POST method}
$request:=4D.HTTPRequest.new($callExternalAppURL; $requestObj).wait()
// The external registring system made the call back. This call back retrieved and updated the Session
// So now the inventoryStatus attribute is up to date
If (Position("Products registered"; Session.storage.info.inventoryStatus)#0)
$result:=Session.storage.info.inventoryStatus
Else
$result:="Registering failed"
End if
return $result
The chosen products are put in the session, and an OTP is generated.
The third-party system is called. The redirect parameter indicates the callback URL. This URL contains the state parameter, which is the session OTP.
In the example above, the third-party system URL is:
https://acme.com/callRegistringApp?redirect=https://my4DApp/callBack?state=C318C81651F84F238EE72C14B46A45C3
The URL prefix /callBack is handled by the HTTP request handlers (HTTPHandlers.json file) in the 4D web app.
[
{
"class": "RequestHandler",
"method": "handleCallBack",
"regexPattern": "/callBack",
"verbs": "post"
}
]
Here is the handleCallback() function of the RequestHandler singleton:
Function handleCallBack($request : 4D.IncomingMessage)
//Get the state parameter in the URL
$otp:=$request.urlQuery.state
//Restore the session thanks to the OTP
$restore:=Session.restore($otp)
//Restoring the session is successful
If ($restore=True)
//Get the body of the request (information sent by the third party system) - It contains "Products registered"
$text:=$request.getText()
//Update the session with the registring result
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
About the 4D Client licence consuming
Only the original session (which generates the OTP) consumes a 4D Client licence. Restoring a session (for example with a validation email link) does not consume any additional 4D Client licence.
Download the attached HDI to run those examples live and check the documentation to learn more.