# Web Single Sign-on for legacy apps # Introduction to WSSO for legacy applications ## What is WSSO for legacy applications? Nowadays, it's very common to have a mix of different technologies and products to deliver information to customers, providers or internal users. With Soffid IAM you can get the same user and password for every application, but the user still needs to identify itself each time a context switch is done. This can be leveraged by using Enterprise Single Sign-On technologies just like Soffid ESSO, but Enterprise Single Sign-On technologies requires to have a big degree of control onto the devices that are likely to use those services. [![image-1654608014328.png](https://bookstack.soffid.com/uploads/images/gallery/2022-06/scaled-1680-/image-1654608014328.png)](https://bookstack.soffid.com/uploads/images/gallery/2022-06/image-1654608014328.png) While this is suitable for internal users, this requirement cannot be met on customers and usually providers. In order to achieve this single sign on behaviour, Soffid Federatoin Services can offer identify do this job. On your application side, the application server must be ready to act as a SAML service provider. Unfortunately, most applications don't support SAML. Soffid Web Single Sign On (from now on WSSO) is a new layer of extra security that can be put over your legacy applications that don't support SAML authentication protocol, and can act as a credential broker in order to authenticate the user to the legacy application based on a SAML token # How to install WSSO for legacy app ## Installation In order to install Web Single Sign on, the following components must be installed and running - Soffid IAM Console version 1.2.1 or later - Soffid IAM Synchronization server 1.2.1 or later - Soffid SAML Identity Provider version 1.0.0 or later - An Apache server with Shibboleth Service Provider version 2.4 or later. The supported platforms are: - Red Hat 6.1 - Ubuntu 12.04 --- #### On RedHat Copy the library file wsso-mod-apache.so to modules directory /usr/lib64/httpd/modules Add the following line to /etc/httpd/conf/httpd.conf LoadModule soffid\_module modules/mod\_soffid\_wsso.so --- #### On Ubuntu Copy the library file wsso-mod-apache.so to modules directory /usr/lib/apache/modules Create the file /etc/apache2/mods-enabled/soffid-wsso.load LoadModule soffid\_module /usr/lib/apache2/modules/mod\_soffid\_wsso.so Execute:
`restorecon -Rv mod_soffidwsso.so `
# How web single sign-on works Web Single Sign On acts introducing credentials to the underlying web application on behalf of the user. To perform its job, WSSO can: - Identify the user when needed - Modify pages generated by the web application in order to adapt them to the single sign on context. - Pass credentials required to the web application. - Close the web single sign on the session. [![image-1654608266197.png](https://bookstack.soffid.com/uploads/images/gallery/2022-06/scaled-1680-/image-1654608266197.png)](https://bookstack.soffid.com/uploads/images/gallery/2022-06/image-1654608266197.png) ### Phase 1. Page request The user agent (actually the web browser), asks Apache for a web page. If the ShibRequireSession tag is present at the web page location, Shibboleth will redirect the request to the configured Soffid SAML Identity Provider. ### Phase 2. SAML Authentication Soffid SAML Identity Provider will ask the user to identify itself. Depending on the federation configuration, the user will be allowed to: - Use certificate login - Enter username and password - Register itself - Recover the password. # Configuring Soffid WSSO The system is configured using Apache configuration files, plus some ECMA script files that can be located anywhere you want. If you are using Ubuntu Server, it is necessary remove apache\_mpm\_worker module and install apache\_mpm\_prefork one. In general, Soffid WSSO acts intercepting and modifying any request made to Apache. This request can be processed by Apache itself or forwarded to another web or application server using ProxyPass module. # Shibboleth Installation notes Soffid Federation is based on shibboleth open source project. Actually the installation is a mixed procedure between Shibboleth installation and Soffid configuration. In the future Shibboleth installation will be integrated on Soffid installation in order to assume better integration level. This guides help administrators to streamline shibboleth installation process, but it does not replace the oficial shibboleth documentation in any way. ## Install shibboleth ### On ubuntu ```shell sudo apt-get install shibboleth-sp2-schemas libshibsp-dev sudo apt-get install libshibsp-doc libapache2-mod-shib2 opensaml2-tools sudo apt-get install libapr-memcache-dev libapr-memcache0 policycoreutils ``` ### On RedHat Follow Installing via Yum instructions on shibboleth wiki: [https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPLinuxRPMInstall](https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPLinuxRPMInstall) ### On Windows Server Follow installing via Windows Server instructions on Shibboleth wiki: [https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPWindowsInstall](https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPWindowsInstall) ### Configure SELinux (if needed) create shibd.te file with this content: ```shell module httpd_shibd 1.0; require { type tmp_t; type var_run_t; type httpd_t; type initrc_t; class sock_file write; class unix_stream_socket connectto; } #============= httpd_t ============== allow httpd_t initrc_t:unix_stream_socket connectto; allow httpd_t var_run_t:sock_file write; ``` Execute ```shell sudo checkmodule -M -m -o shibd.mod shibd.te sudo semodule_package -o shibd.pp -m shibd.mod sudo semodule -i shibd.pp sudo setsebool -P httpd_can_network_connect 1 ``` ### Create service provider Shibboleth keys & metadata Execute ``` sudo shib-keygen -h HOSTNAME -e https://HOSTNAME/shibboleth ``` Verify the permissions of the generated key. At this point, verify the hostname specified matches the ServerName directive at Apache config file, including scheme and port. ### Edit configuration file Update shibboleth2.xml in order to download the federation data from Soffid master or backup Synchronization Server. You will need to specify the Identity Provider public ID, as it is created on Soffid SAML Federation ```XML ... SAML2 SAML1 SAML2 Local ... ... ... ``` Finally, uncomment the required attributes on attribute-map.xml. You must also add the following ones: ```XML ``` ### Enable Single Logout back-channel It's advisable to use single logout back-channel while using non SAML-aware applications. To do this, add a new Logout intitator configuration at shibboleth2.xml file: ```XML ``` For security reasons, you should at the signing parameter at the application defaults tag in order to enable logout message signature: ```XML .. ``` ### Finally Restart services: ```shell sudo service apache2 start sudo service shibd start ``` # WSSO configuration ## Configuring Soffid WSSO The system is configured using Apache configuration files, plus some ECMA script files that can be located anywhere you want. In general, Soffid WSSO acts intercepting and modifying any request made to Apache. This request can be processed by Apache itself or forwarded to another web or application server using ProxyPass module. ### SoffidPostData directive Syntax: ``` SoffidPostData [path-regex] [system=...] [account=...] [password=....] [contains=....] [flags] ``` ``` SoffidPostData [path-regex] [system=...] [account=] [post= "DataToBePosted"] [replace | append | prepend | merge] [contains=...] [flags] ``` When the user agent performs a POST request to a path that matches the specified regular expression, Soffid WSSO will intercept it. Mind the path is missing any host, protocol or port qualifier. If the requested file matches the regular expression, and the user agent is using the POST method, Soffid WSSO will modify the posted content. Optionally you can specify a regular expression that must match the original content of the sent message. The first step performed by Soffid WSSO is to parse the posted data to get the user account sent. The user account sent will be the one whose name matches the account parameter of the SoffidPostData directive. If SoffidPostData directive lacks “account=...” the default (j\_user) parameter name will be used. The next step is to retrieve the password that belongs to the specified account on the system set at SoffidPostData directive. The final step is to add or change the password sent. The name of the password parameter should be set by the SoffidPostData directive. If none is specified, the default (j\_password) parameter name will be used. For more sophisticated applications, the full content of the post data can be specified the syntax post="DataToBePosted.". On this data to be posted, the administrator can specify some expressions that will be replaced by the proper value: - ${secret.xxx} where xxx names for a secret stored at the user secrets vault. - ${account} will be resplaced by the used account. - ${password} will be replace by the account password. The data sent by the browser will be combined with the new posted data in four different ways: - replace: the data sent by the browser is completely ignored. - append: the new data will be appended to the original one. - prepend: the new data will precede the original one. - merge: each parameter of the original request will keep its position despite its value is to be changed. New parameters will be appended after the original ones. Additionally, the following flags are allowed: - force: By default, the password will be injected only when no password is sent, or it's empty. If you'd like to overwrite the password sent by the user agent, this flag must be specified. - requiresAccount: If specified, Soffid WSSO will not try to guess the account to use if it is not send by the user agent. Example: ``` SoffidPostData .* /j_security_check system=soffid account=j_user "j_user=${account}&j_password=${password}" ``` ``` SoffidPostData .* /j_security_check system=soffid account=j_user password=j_password ``` ### SoffidBasicAuthorization directive Syntax: ``` SoffidBasicAuthorization [path-regex] system ``` This Appache directive allows WSSO to inject user name & password on web applications using HTTP BASIC authentication scheme. If the path being accessed matches the regular expression, Soffid WSSO will inject the “Authorization: Basic” header as stated at RFC 2617. The system must match a managed system, also named Agent, listed at Soffid IAM Console. Example: ``` ProxyBasicAuthorization ^/secure/.*$ "soffid" ``` ### SoffidOnLoadScript directive Syntax: ``` SoffidOnLoadScript [path-regex] content-regex maxSize scriptFile ``` To improve the user experience it's necessary to hide the underlying application-specific user authentication form. In order to do this, the web pages generated by the applications can be modified in order to skip unnecessary forms or to request a global login authentication. ProxyOnLoadScript directive triggers the execution of an ECMA script that is able to modify the page generated by the application server. As long as the script execution is a rather heavy task, WSSO must accurately detect which pages must trigger the script execution. Currently, the script execution can be triggered by contents and by page path. Allowed parameters:
path-regex Regular expression that will be matched against the page path, excluding any trailing parameter, sent after a question mark.
content-regex Regular expression that will be matched against the page contents.
maxSize Maximum page size that will be handled. Whether the page size exceeds this limit, it will be sent to the user agent.
scriptFile File containing the script to be executed. Note the script must be UTF-8 encoded.
Regards this directive could potentially manage any kind of media generated by a web server, it is designed to managed XML or HTML files. Anyway, image resources will never trigger the script execution. ### SoffidCookieName directive Syntax: ``` SoffidCookieName cookie-name ```
Sets the name of the cookie used by Soffid WSSO to track the single sign on session ### SoffidCookieName directive Syntax: ``` SoffidCookieDomain domain-name ``` Sets the domain of the cookie used by Soffid WSSO to track the single sign on session. By default, the cookie is attached to the virtual server name ### SoffidCookiePassthrough directive Syntax: ``` SoffidCookieDomain domain-name ```
By default the WSSO engine will hide actual cookies to the browser, and only the Soffid cookie will be sent. In order to share the cookie names and values with the browser, one can add a regular expression to match the cookies to unhide. To unhide all cookies, use: ``` SoffidCookiePassthrough .* ```
# WSSO scripting language ## Scripting language The scripting language is fully compatible with ECMAScript 3rd edition plus a small set of new objects and methods. Before running the script, Soffid WSSO will create some global variables referring to the request of the user agent and content generated by the application server. Here are the functions and classes implemented: ### Global Functions
debug text: string Sends a message to the apache log file
env text: string returns string Gets the value of an environment variable
logout Log out of Web Single Sign on session. Any further access to a protected resource will ask the user for credentials.
### Global objects
request Contains a **Request** object referring the request made by the user agent.
response Contains a **Response** object referring the data generated by the application server
secretStore Contains the secrets of the user, including account names and passwords.
document Contains a **Document** object referring the generated page.
### secretStore object The object is available from authenticated requests and provides access to the secrets of the user. Any usage of secretStore object on an anonymous request will produce an undefined value
getSecret text: string returns string Gets the value of a secret
getAccounts system: string return: array Returns the list of accounts that the user is allowed to use on a particular system
getAccounts system: string returns string Returns the first account that the user is allowed to use on a particular system.
getPassword system: string account: string returns string Returns the actual password for the specifieds account and system.
### Request class The Request class encapsulates the request made by the browser and forwarded to the application server. #### Attributes
method string Used method (POST, GET or others)
url string Contains the requested URL, including scheme, host and port
headers Array Headers sent with the request.
content String Data posted by the user agent on POST requests. Due to memory usage limits, its content may not be complete.
params Array Array containing the request parameters.
#### Methods
clone returns Request Create a new request from the current. The current response will be canceled and a new request will be processed
### Response class The class encapsulates the request response sent by the server application. #### Attributes
document Document Document generated by the server
headers Array Vector header with the request
### Document class Contains the page generated by the application server. Thus, scripts can access the website and its DOM tree in runtime. The document object implements a subset of the standard DOM HtmlDocument. #### Attributes
url string Full URL of the document.
title string Title of document.
cookie string Contains the cookies sent by the user agent
anchors Collection Contains elements with tag A.
forms Collection Contains elements with tag FORM.
images Collection Contains elements with tag IMG.
links Collection Contains elements with tags A and AREA.
documentElement Item Contains the root element, usually and HTML tag element.
content string Full content of the document in string format
#### Methods
getElementById id: string Element returns Find the first element with the specified ID
getElementsByTagName tag: string returns Collection Find all elements with the specified tag
### Element class Obtained from the document object, implements a subset of the DOM class HtmlElement. #### Attributes
childNodes Collection The vector containing children elements
disabled boolean Indicator of whether the element is disabled or not
id string The id attribute of the element
tagName string Tag of the element
parentNode Item Parent element
#### Methods
getAttribute name: string returns string Returns the value of the specified attribute name.
setAttribute name: string value: string Ses the value of the specified attribute.
removeAttribute name: string Remove the specified attribute
setText value: string Changes the inner content of the element
getText returns string Gets the element inner content.
getElementsByTagName tag: string returns Collection Find all children's elements with the specified tag
addChild tag: string before: element (opt) returns element Creates a new item with the specified tag. If a node is specified, the new element will be ordered just before the specified item in the child list
remove Removes the node from the document
### Collection class The document object implements a subset of the standard DOM HTMLCollection #### Attributes
length Long Number of items in the collection
#### Methods
item id: long returns Element Gets the n-position element within the collection. The first element is numbered as 0.
namedItem id: string returns Element Search for an item with the given name. First, search for an element with a matching id attribute. If none is found, search for an element with a matching name attribute.
# Sample configurations Sample configurations # WebSSO configuration for Drupal Add the following settings to Apache: ``` ShibRequireSession off ShibRequestSetting applicationId drupal AuthType shibboleth Require shibboleth SoffidOnLoadScript .* .* 60000 /etc/apache2/soffid/drupal-login.js SoffidPostData /?q=user system=ldap account=name password=pass ``` Finally, next is the login script ``` debug ("***********************************************************************"); debug ("****************** LOGIN ON DRUPAL *************************"); debug ("***********************************************************************"); debug (document.url); user = document.getElementById("edit-name"); pass = document.getElementById("edit-pass"); debug ("***********************************************************************"); if (user != undefined && pass != undefined) { account = secretStore.getAccount("ldap"); found = false; // Any error message found if (account != undefined) { user.setAttribute("value", account); } else { user.setAttribute("value", "AUTO-LOGIN"); found = true; } user.setAttribute("readonly", true); pass.parentNode.setAttribute("style", "display:none"); errors = document.getElementsByTagName("div"); for (i = 0 ; ! found && i < errors.length; i++) { if (errors.item(i).getAttribute("class") == "messages error") found = true; } if (! found) { body=document.getElementsByTagName("body").item(0); div = body.addChild("div"); div.setAttribute("style", "width: 90%; height: 90%; position: absolute; top: 5%; left: 5%; z-index: +10; background-color: #7F9FC2; color: white; opacity: 0.9; display: table-cell; text-align: center; vertical-align: middle; line-height: 90px; font-size: 250%; "); div2 = div.addChild("div"); div2.setAttribute("style", "width: 100%; height: 40%;"); div.addChild("span").setText ("Logging in. Please wait ...."); body.setAttribute("onLoad", "document.getElementById('edit-submit').click();"); } document.getElementById("edit-submit").setAttribute("style", "display:none"); } // Change login / logout button anchors = document.getElementsByTagName("a"); for (i = 0 ; i < anchors.length; i++) { if (anchors.item(i).getAttribute("href") == "/?q=user/logout") anchors.item(i).setAttribute("href", "/Shibboleth.sso/Logout?return=http://www.soffid.org/"); if (anchors.item(i).getAttribute("href") == "http://drupal.soffid.org/user") { anchors.item(i).setAttribute("href", "/Shibboleth.sso/LocalLogout?return=http://www.soffid.org/Shibboleth.sso/Login%3ftarget=http://www.soffid.org/%3fq=user"); anchors.item(i).setText("Login / Register"); } // Remove link to change user attributes if (anchors.item(i).getAttribute("href") == "/?q=user/59/edit") anchors.item(i).setAttribute("href", "/"); } ``` # WebSSO configuration for PHPBB The following attributes must be added to apache configuration ``` ShibRequestSetting applicationId forum ShibRequireSession off AuthType shibboleth Require shibboleth ShibRequireSession on ShibRequestSetting requireSession true AuthType shibboleth Require valid-user SoffidOnLoadScript 'index.php$' .* 20000 /etc/apache2/soffid/forum-front.js SoffidOnLoadScript '/ucp.php$' .* 20000 /etc/apache2/soffid/forum-ucp.js SoffidOnLoadScript '/posting.php$' .* 20000 /etc/apache2/soffid/forum-posting.js SoffidPostData '/ucp.php\?mode=login' system=ldap account=username password=password ``` Now, the following scripts must be added:
**forum-front.js**
``` // Script to remove user & password from front page // debug ("***********************************************************************"); account = secretStore.getAccount("ldap"); debug ("Account = "+account); debug ("***********************************************************************"); user = document.getElementById("username"); if (user != undefined) { fieldset = user.parentNode; children = fieldset.childNodes; for (i = 0; i < 6; i++) { children.item(i).setAttribute("style", "display:none"); } if (account != undefined) { user.setAttribute("value", account); user.setAttribute("readonly", true); user.setAttribute("style", ""); } fieldset.setAttribute("style", "display:none"); } // Change login button anchors = document.getElementsByTagName("a"); for (i = 0 ; i < anchors.length; i++) { if (/.*\/ucp.php\?mode=login.*/.test(anchors.item(i).getAttribute("href"))) { anchors.item(i).setText("Login / Register"); } } ```
**forum-ucp.js**
``` // Script to perform login / logout actions // account = secretStore.getAccount("ldap"); if (request.params["mode"] == "login") { user = document.getElementById("username"); account = secretStore.getAccount("ldap"); if (user != undefined) { user.setAttribute("value", account); user.setAttribute("readonly", true); pass = document.getElementById("password"); fieldset = pass.parentNode; dl = fieldset.parentNode; dl.setAttribute("style", "display:none"); div = dl.parentNode; login = div.childNodes.item(4).childNodes.item(2).childNodes.item(2); login.setAttribute("id", "loginButtonToClick"); body=document.getElementsByTagName("body").item(0); div = body.addChild("div"); div.setAttribute("style", "width: 90%; height: 90%; position: absolute; top: 5%; left: 5%; z-index: +10; background-color: #7F9FC2; color: white; opacity: 0.9; display: table-cell; text-align: center; vertical-align: middle; line-height: 90px; font-size: 250%; "); div2 = div.addChild("div"); div2.setAttribute("style", "width: 100%; height: 40%;"); div.addChild("span").setText ("Logging in. Please wait ...."); script = body.addChild("script"); script.setAttribute("type", "text/javascript"); script.setText("onload_functions.push('document.getElementById(\\'loginButtonToClick\\').click();');"); } } if (request.params["mode"] == "logout") { logout (); } if (request.params["mode"] == "reg_details") { // Remove change details link fieldsets=document.getElementsByTagName("fieldset"); for (i = 0; i < fieldsets.length; i++) { fieldsets.item(i).setAttribute("style", "visible: false;"); fieldsets.item(i).remove(); } } ```
**forum-posting.js**
``` // Script to perform login on post // user = document.getElementById("username"); if (user != undefined) { if (secretStore != undefined) { account = secretStore.getAccount("soffid.org-ldap"); user.setAttribute("value", account); } user.setAttribute("readonly", true); pass = document.getElementById("password"); fieldset = pass.parentNode; dl = fieldset.parentNode; dl.setAttribute("style", "display:none"); div = dl.parentNode; login = div.childNodes.item(4).childNodes.item(2).childNodes.item(1); login.setAttribute("id", "loginButtonToClick"); body=document.getElementsByTagName("body").item(0); div = body.addChild("div"); div.setAttribute("style", "width: 90%; height: 90%; position: absolute; top: 5%; left: 5%; z-index: +10; background-color: #7F9FC2; color: white; opacity: 0.9; display: table-cell; text-align: center; vertical-align: middle; line-height: 90px; font-size: 250%; "); div2 = div.addChild("div"); div2.setAttribute("style", "width: 100%; height: 40%;"); div.addChild("span").setText ("Logging in. Please wait ...."); // body.setAttribute("onLoad", "document.forms[1].submit()"); script = body.addChild("script"); script.setAttribute("type", "text/javascript"); script.setText("onload_functions.push('document.getElementById(\\'loginButtonToClick\\').click();');"); } ```