Page History
...
This scenario can be detected by attaching a unique identifier to each request, a randomly generated GUID we call a request-ward. For a given session, the Runtime always knows what is the next expected request-ward and can detect a de-synchronized request, as in the following example:
Step | Actor | Description |
---|---|---|
1 | User/Browser | The User clicks on button1. A request to handle the event is sent, together with the current request-ward |
2 | Load Balancer | The load balancer forwards the request to Runtime 1. |
3 | Runtime 1 | The Runtime reads the session from the key-value store. |
4 | Runtime 1 | The Runtime validates the request ward, generates a new request ward and handles the event. The session is now on Page 2 |
5 | Runtime 1 | The Runtime writes the session in the key-value store. The new request ward is also saved as part of the session. |
6. | Runtime 1 | The Runtime crashes before being able to send the response. |
7 | Load Balancer | The load balancer re-sends the requests to Runtime 2 |
8 | Runtime 2 | The Runtime reads the session from the key-value store, validates the request ward and sees that the received ward (ward1) doesn't match the expected ward (ward2) |
9 | Runtime 2 | The Runtime response with an HTTP 400 Bad Request and a JSON in the response body indicating that an invalid request ward was received |
10 | Load Balancer | The load balancer sends the response back to the User. The user sees an error message stating that the page should be refreshed. |
11 | User/Browser | The User refreshes the page. This consists of a request to the current UI (for example: /session/{sessionId}/mvc/index.html when MVC UI is used) |
12 | Load Balancer | The load balancer forwards the request to Runtime 2 |
13 | Runtime 2 | The Runtime reads the session from the key-value store |
14 | Runtime 2 | The Runtime generates the current page model (for Page 2) and sends it in the response. The current request ward (ward2) is also send as part of the response |
15 | Load Balancer | The load balancer forwards the response to the User. The user now correctly sees that he/she is on Page 2. |
16 | User/Browser | The user clicks another button on Page 2. A request to handle the event is sent, together with the new request ward. |
17 | Load Balancer | The load balancer fowards the request to Runtime 2 |
18 | Runtime 2 | The Runtime reads the session from the key-value store and validates the request ward. This time, the request ward is valid. |
19 | Runtime 2 | The Runtime generates a new request ward (ward3) and handles the event |
20 | Runtime 2 | The Runtime writes the session in the key-value store, together with the new request ward |
21 | Runtime 2 | The Runtime response with the page changes and the new request ward |
22 | Load Balancer | The load balancer forwards the response to the User. The user correctly sees the changes on the page, and also has the new request ward for the next request. |
Please note that while the request ward is similar to a CSRF token, it differs in a few key areas:
...
- /Runtime/server/session/{sessionId}/load - this endpoint loads the page model. Request ward validation is disabled for this endpoint, as the front-end doesn't yet know the request ward. Request ward renewal is enabled however.
- /Runtime/server/session/{sessionId}/keepalive - this endpoint extends the session timeout. Request ward validation and renewal is disabled for this endpoint as keep-alive requests may be performed in parallel with other modification requests for the same session
- /Runtime/server/session/{sessionId}/close - this endpoint closes the session. Request ward validation and renewal is disabled for this endpoint as session de-synchronization is not possible. If the session is deleted and then a failover occurs, then repeating the request on the backup node will result in an error (because the session is already deleted).
The response in case of an invalid request ward has status code 400 and a JSON in the response body like in the following example:
Code Block |
---|
{
"type":"INVALID_REQUEST_WARD",
"title":"Invalid Request",
"message":"Please refresh the page"
} |
The title and message are internationalized and can be configured in the usual way in messages.properties. The language of the request is determined based on the Accept-Language request header.
The default MVC v2 front-end implementation just displays this message to the user. Custom implementations may use a combination of the response status code and type field to detect this exception and automatically reload the page. However, notifying the user is recommended, so the user has a change to take note of the data entered on the page which will be lost once the page is reloaded.
Multipart Requests
For multipart requests the request ward is also accepted as a request parameter named X-Request-Ward. This exception is made in order to accomodate older browsers which do not support true AJAX multipart requests / file uploads an require the use of hidden forms and iframes to simulate AJAX file uploads. The request ward is accepted as a parameter only for multi-part requests. If both the header and parameter are present, the header takes precedence. The same rules as for regular requests apply as well.
Configuration
Request wards can be enabled or disabled globally with the following property in application.properties:
Code Block |
---|
blueriq.session.request-ward-enabled=false |
By default, request wards are disabled, as Dashboards do not work with request wards enabled due to a few endpoints that accept parallel requests for the same session.
The error messages may be internationalized in messages.properties and message_<language>-<country>.properties using the following keys:
Code Block |
---|
request-ward.invalid.title=Invalid request
request-ward.invalid.message=Please refresh the page |