Adding new pages
17.3.2.4.4.3. Adding new pages using the Loginapp REST UI SDK

To provide maximal flexibility, the Loginapp REST UI SDK allows adding of entire pages written completely in JavaScript. This mechanism should be chosen with care since it also comes with additional costs and complexity.

  • However, there are reasons why it might be necessary to write a page using JavaScript:
  • For custom server-side flow steps that require a front-end that cannot be done using the Configurable Step UI Config plugin UI in IAM's server-side configuration.
  • For existing product pages that have to be altered in a way such that the mechanism provided for 17.3.2.4.4.2. Customizing an existing product page using the Loginapp REST UI SDK is insufficient.

On top of all API methods defined for 17.3.2.4.4.2. Customizing an existing product page using the Loginapp REST UI SDK, this method provides additional API methods for custom pages.

While writing custom pages in JavaScript is very powerful, it also comes with additional responsibilities: rendering the page's HTML, handling form input and validation, dealing with button clicks and interacting with the back-end must be implemented individually. Therefore, choose this option only if needed and make sure to have a good understanding of JavaScript.

To avoid problems with caching, it is recommended to use SASS whenever possible.

Rendering a page using JavaScript

When writing a page in JavaScript, one of the key tasks is to render the content of the page itself. For that, the UI SDK provides the following methods:

iam.api.customizing.initialize = function () { 
    class MyCustomJavaScriptPage extends iam.api.types.CustomJavaScriptPage { 
  
        // Defines the page's HTML content 
        contentHtml() { 
            return '...'; 
        } 
  
        // Invoked when the above provided HTML has been rendered into the page 
        afterRender() { 
            // Whatever logic required, e.g. putting focus on a field 
        } 
    } 
  
    // Register the page: 
    iam.api.customizing.registerPage(new MyCustomJavaScriptPage()); 
}
…Show more…Show less

In order to be available in the SDK's menu, custom pages 17.3.2.3.1.1. Loginapp REST UI SDK navigation menu configuration.

For the remainder of this section, the surrounding function iam.api.customizing.initialize is not shown in the code examples. However, remember that all JavaScript customizations must happen within this function as shown above.

Handing control back to the Loginapp REST UI

  • With custom pages, the Loginapp REST UI hands control over to the custom JavaScript to:
  • Render the page.
  • Handle the input.
  • Perform calls to Flow API REST endpoints.

The REST responses are either successful or failed flow-responses.

  • A custom page has two options to handle them:
  • Deal with them autonomously.
  • Hand control back to the Loginapp REST UI to handle them.

Example:

iam.api.customizing.initialize = function () { 
    class MyCustomJavaScriptPage extends iam.api.types.CustomJavaScriptPage { 
  
        // Defines the page's HTML content 
        contentHtml() { 
            return '...'; 
        } 
  
        // Invoked when the above provided HTML has been rendered into the page 
        afterRender() { 
            // Whatever logic required, e.g. putting focus on a field 
        } 
    } 
  
    // Register the page: 
    iam.api.customizing.registerPage(new MyCustomJavaScriptPage()); 
}

In this basic example, when the POST to the back-end was successful, handleSuccessfulFlowResponse is called.
The UI will then automatically determine what to do, e.g. navigate to the next page. On the other hand, if interaction fails, handleFailedFlowResponse is called.

Note that the error handling in this example might be oversimplified for the general case. I.e. a failed interaction might require the REST UI to render validation errors or alerts and only status codes like 401 or 403 might lead to the invocation of handleFailedFlowResponse.

To use the UI SDK without a back-end may require mocking the REST endpoints called by JavaScript. For more details, refer to 17.3.2.3.1.3. Loginapp REST UI SDK REST response mocking configuration.

Full page render example

The following example illustrates how to render a page that looks like the standard mTAN authentication page and how to add back-end invocations in afterRender. Note that this is a simple example that could be further improved, e.g. by dealing with other status codes or by handling error responses that contain field validation errors.

copy
iam.api.customizing.initialize = function () { 
    class MyCustomPage extends iam.api.types.CustomJavaScriptPage { 
  
        isApplicable({pageId, flowId, stepId}) { 
            return pageId === 'my-custom-page'; 
        } 
  
        afterRender() { 
            document.getElementById('otp').focus(); 
            const me = this; 
  
            // Example using XHR: 
            document.getElementById('myCustomMtanForm').addEventListener('submit', function (event) { 
                event.preventDefault(); 
                const data = me.formToJson(document.getElementById('myCustomMtanForm')); 
                const xhr = new XMLHttpRequest(); 
                xhr.onreadystatechange = function () { 
                    if (this.readyState !== 4) { 
                        return; 
                    } 
                    if (this.status === 200) { 
                        me.handleSuccessfulFlowResponse(xhr); 
                    } else { 
                        me.handleFailedFlowResponse(xhr); 
                    } 
                }; 
                xhr.open('POST', me.getRestBaseUrl() + '/public/authentication/mtan/otp/check'); 
                xhr.setRequestHeader('Content-Type', 'application/json'); 
                xhr.setRequestHeader('X-Same-Domain', 1); 
                xhr.send(JSON.stringify(data)); 
            }); 
  
            // Same example using 'fetch': 
            // document.getElementById('myCustomMtanForm').addEventListener('submit', function (event) { 
            //    event.preventDefault(); 
            //    const data = me.formToJson(document.getElementById('myCustomMtanForm')); 
            //    fetch(me.getRestBaseUrl() + '/public/authentication/mtan/otp/check', { 
            //        method: 'POST', 
            //        headers: { 
            //            'Content-Type': 'application/json', 
            //            'X-Same-Domain': 1 
            //        }, 
            //        body: JSON.stringify(data) 
            //    }).then(response => { 
            //        if (response.status === 200) { 
            //            me.handleSuccessfulFlowResponse(response); 
            //        } else { 
            //            me.handleFailedFlowResponse(response); 
            //        } 
            //    }); 
            // }); 
  
            document.getElementById('otp').addEventListener('keyup', function () { 
                const hasNoInput = document.getElementById('otp').value.length === 0; 
                document.getElementById('checkOtp').disabled = hasNoInput; 
            }); 
        } 
  
        getRestBaseUrl() { 
            const base = document.getElementsByTagName('base')[0].href; 
            const regex = new RegExp('/ui/?$'); 
            return base.replace(regex, '') + '/rest'; 
        } 
  
        formToJson(form) { 
            const formData = {}; 
            const elements = form.querySelectorAll("input, select, textarea"); 
            for (let i = 0; i < elements.length; ++i) { 
                const element = elements[i]; 
                if (element.name) { 
                    formData[element.name] = element.value; 
                } 
            } 
            return formData; 
        } 
  
        onPageEnter() { 
            console.log('entering: my-custom-page'); // for the sake of illustration 
        } 
  
        onPageExit() { 
            console.log('exiting: my-custom-page'); // for the sake of illustration 
        } 
  
        contentHtml() { 
            return `<form id="myCustomMtanForm" novalidate="" autocomplete="off"> 
                    <div class="iam-row-group"> 
                        <div class="iam-form-group iam-row"> 
                            <label class="iam-col-12 iam-col-md-5 iam-col-form-label" for="otp"> 
                                {{#translate}}authentication.mtan.otp{{/translate}} 
                            </label> 
                            <div class="iam-col-12 iam-col-md-7"> 
                                <input class="iam-form-control" type="text" id="otp" name="otp" placeholder=""> 
                            </div> 
                        </div> 
                    </div> 
                    <div class="iam-form-group iam-row"> 
                        <div class="iam-col-12 iam-button-group"> 
                                <button class="iam-btn iam-btn-block-level-sm-down iam-float-right iam-btn-outline-primary" 
                                        type="submit" id="checkOtp" title="" disabled> 
                                    {{#translate}}authentication.mtan.actions.check-otp{{/translate}} 
                                </button> 
                        </div> 
                    </div> 
                </form>`; 
        } 
    } 
  
    // Register the page: 
    iam.api.customizing.registerPage(new MyCustomPage()); 
};
…Show more…Show less