Skip to main content
Version: 20 R5 BETA

Users and sessions

When scalable sessions are enabled (recommended), REST requests can create and use web user sessions, providing extra features such as multiple requests handling, data sharing between web client processes, and control of user privileges.

When a web user session is opened, you can handle it through the Session object and the Session API. Subsequent REST requests reuse the same session cookie.

  • On 4D Server, opening a REST session might require that a free 4D client license is available, depending on the user login mode.
  • On 4D single-user, you can open up to three REST sessions for testing purposes.

User login modes

The user login mode allows you to control how REST requests acquire 4D Client licenses. You can choose between two user login modes: "default", or "force login".

You set the user login mode through the forceLogin property in the roles.json file:

  • the default mode is used if the "forceLogin" property is missing or set to "false",
  • the force login mode is used if the "forceLogin" property is set to "true".
caution

If you modify this property, the server must be restarted to take the change into account.

note

In Qodly Studio for 4D, the mode can be set using the Force login option in the Privileges panel.

Default mode

In the default mode, any REST request is processed in a web user session that automatically consumes a license (the web user session is created if it does not already exist). You can use this simple mode if you don't need to control how many licenses are retained on the server. When the default mode is enabled, you can authenticate users through the On REST Authentication database method (see below).

Force login mode

In "force login" mode, license usage is disconnected from web user sessions. A license is required only when the Session.setPrivileges() is executed, allowing you to control the number of used licenses.

Descriptive REST requests are always processed by the server, even if no web user session using a license is opened. In this case, they are processed through "guest" sessions.

All other REST requests (handling data or executing a function) will only be processed if they are executed within a web session with appropriate privileges, otherwise they return an error. To assign privileges to a web session, you need to execute the Session.setPrivileges() function for the session. Executing this function triggers the 4D license consumption.

This mode allows you to implement the following login sequence:

  1. At the first REST call (for a webform call for example), a "guest" web user session is created. It has no privileges, no rights to execute requests other than descriptive requests, no license consumption.
  2. You call your exposed datastore class function named authentify() (created beforehand), in which you check the user credentials and call Session.setPrivileges() with appropriate privileges.
  3. The /rest/$catalog/authentify request is sent to the server along with user credentials. This step only requires a basic login form that do not access data; it can be a Qodly form (called via the /rest/$getWebForm request).
  4. If the user is successfully authentified, a 4D license is consumed on the server and all REST requests are accepted.

alt-text

Descriptive REST requests

Descriptive REST requests can be processed in web user sessions that do not require licenses ("guest" sessions). These requests are:

  • /rest/$catalog requests (e.g. /rest/$catalog/$all) - access to available dataclasses
  • /rest/$catalog/authentify - the datastore function used to login the user
  • /rest/$getWebForm - the rendering of a Qodly form

alt-text

Function authentify

Syntax

exposed Function authentify({params : type}) {-> result : type}
// code

The authentify() function must be implemented in the DataStore class of the project and must be called through a REST request.

This function is the only available entry point from REST guest sessions when the "force login" mode is enabled: any other function call or data access is rejected until the session acquires appropriate privileges.

The function can receive any authentication or contextual information as parameter(s) and can return any value. Since this function can only be called from a REST request, parameters must be passed through the body of the POST request.

This function should contain two parts:

  • some code to identify and authenticate the REST request sender,
  • if the authentication is successful, a call to Session.setPrivileges() that assigns appropriate privileges to the session.

If the function does not call Session.setPrivileges(), no privileges are assigned, no license is consumed and subsequent non-descriptive REST requests are rejected.

Example

You only want to know users to open a web session on the server. You created the following authentify() function in the datastore class:

exposed Function authentify($credentials : Object) : Text

var $users : cs.UsersSelection
var $user : cs.UsersEntity

$users:=ds.Users.query("name = :1"; $credentials.name)
$user:=$users.first()

If ($user#Null) //the user is known
If (Verify password hash($credentials.password; $user.password))
Session.setPrivileges("vip")
Else

return "Wrong password"
End if
Else
return "Wrong user"
End if

To call the authentify() function:

POST 127.0.0.1:8111/rest/$catalog/authentify

Body of the request:

[{"name":"Henry",
"password":"123"}]

Using On REST Authentication

In default login mode (i.e. the "force login" mode is disabled), you can log in a user to your application by calling $directory/login in a POST request including the user's name and password in the header. This request calls the On REST Authentication database method (if it exists), where you can check the user's credentials (see example below).

If the On REST Authentication database method has not been defined, a guest session is opened.

Example

In this example, the user enters their email and password in an html page that requests $directory/login in a POST (it is recommended to use an HTTPS connection to send the html page). The On REST Authentication database method is called to validate the credentials and to set the session.

The HTML login page:

alt-text

<html><body bgcolor="#ffffff">

<div id="demo">
<FORM name="myForm">
Email: <INPUT TYPE=TEXT NAME=userId VALUE=""><br/>
Password: <INPUT TYPE=TEXT NAME=password VALUE=""><br/>
<button type="button" onclick="onClick()">
Login
</button>
<div id="authenticationFailed" style="visibility:hidden;">Authentication failed</div>
</FORM>
</div>

<script>
function sendData(data) {
var XHR = new XMLHttpRequest();

XHR.onreadystatechange = function() {
if (this.status == 200) {
window.location = "authenticationOK.shtml";
}
else {
document.getElementById("authenticationFailed").style.visibility = "visible";
}
};

XHR.open('POST', 'http://127.0.0.1:8044/rest/$directory/login'); //rest server address

XHR.setRequestHeader('username-4D', data.userId);
XHR.setRequestHeader('password-4D', data.password);
XHR.setRequestHeader('session-4D-length', data.timeout);

XHR.send();
};
function onClick()
{
sendData({userId:document.forms['myForm'].elements['userId'].value , password:document.forms['myForm'].elements['password'].value , timeout:120})
}
</script></body></html>

When the login page is sent to the server, the On REST Authentication database method is called:

    //On REST Authentication

#DECLARE($userId : Text; $password : Text) -> $Accepted : Boolean
var $sales : cs.SalesPersonsEntity

$Accepted:=False

//A '/rest' URL has been called with headers username-4D and password-4D
If ($userId#"")
$sales:=ds.SalesPersons.query("email = :1"; $userId).first()
If ($sales#Null)
If (Verify password hash($password; $sales.password))
fillSession($sales)
$Accepted:=True
End if
End if
End if

As soon as it has been called and returned True, the On REST Authentication database method is no longer called in the session.

The fillSession project method initializes the user session, for example:

#DECLARE($sales : cs.SalesPersonsEntity)
var $info : Object

$info:=New object()
$info.userName:=$sales.firstname+" "+$sales.lastname

Session.setPrivileges($info)

Use (Session.storage)
If (Session.storage.myTop3=Null)
Session.storage.myTop3:=$sales.customers.orderBy("totalPurchase desc").slice(0; 3)
End if
End use