Article

What are the basic steps to authenticate REST clients against the OpenEdge database _User table ?

« Go Back

Information

 
Article Number000058538
EnvironmentProduct: OpenEdge
Version: 11.3.3 , 11.4 , 11.5, 11.6
OS: All supported platforms
Question/Problem Description
What are the basic steps to authenticate REST clients against the OpenEdge database _User table ?
How to authenticate REST clients against an OpenEdge database user list ?
What are the basic steps to implement the OpenEdge Single Point of Authentication (SPA) configurations with an OpenEdge REST Web application ?
Steps to Reproduce
Clarifying Information
Error Message
Defect/Enhancement Number
Cause
Resolution
This article provides the most basic steps to get you started with the usage of the OpenEdge Single Point of Authentication (SPA) configurations to configure your REST Web application's security. The OpenEdge SPA implementation extends the Spring Security authentication process. The implementation consists of 2 components, the OpenEdge Realm (OERealm) Client and the OERealm AppServer ABL interface (OOABL singleton class implementation). The OERealm Client represents the OpenEdge REST application (Java servlet) and the OERealm AppServer OOABL singleton class implementation provides the OERealm Client access to user account field information maintained in a state-free AppServer. For more detailed information on these components, please refer to the OpenEdge documentation.

The specific example discussed in this article is to have your REST clients authenticated against a users list that has been defined in the _User table of an OpenEdge database within a development environment (OpenEdge Developer Studio). This is achieved by using Client-Principal and Security Domains. In reality the user accounts may be from any source available to the ABL. Please note that this article is not intended to address all possible scenarios to configure SPA with an OpenEdge REST Service and it's also not intended to explain all the aspects of OpenEdge security and Spring security. If you require more detailed information or are unfamiliar with some of the terminology used in this article, please refer to the OpenEdge documentation.



Things you should know before you start with the SPA configuration (optional for the example provided in this article)
  • The user accounts you return field information from should support:
    • a numeric account ID # (required)
    • account states:  i) enabled state; ii) locked state;  iii) expired state  (optional)
    • [ABL application] ROLE names (optional)
  • The name of the AppServer's OOABL OERealm class implementation.
  • (optional) The name of the OpenEdge Domain (and Access Code) that can be used to validate a Client-Principal generated by the Spring Security's authentication process and used to set the AppServer's application/database connection.
  • (optional) The name of the OpenEdge Domain (and Access Code) that will be used by the AppServer's OOABL OERealm class implementation to authenticate and authorize access to individual OERealm client(s).


Database Configuration
  • Create a new Security Domain with an authentication system type of "_oeusertable" (authentication via _User table) or use an existing Security Domain which has already been defined in your database. You can review the existing Security Domains defined within your database or create new ones by going into the Data Administration tool and going to the Admin menu -> Security -> Domain Maintenance -> Domains... . A similar option exists in the OpenEdge Explorer/Management. Alternatively you can also create a Security Domain via ABL, for example:
CREATE _sec-authentication-domain.
ASSIGN
_sec-authentication-domain._domain-name = "RESTDomain"
_sec-authentication-domain._domain-type = "_oeusertable"
_sec-authentication-domain._domain-access-code = audit-policy:encrypt-audit-mac-key("Password")
_sec-authentication-domain._domain-enabled = YES.

In this example a new Security Domain called RESTDomain will be created to authenticate the REST clients. This is also the Security Domain which will be used in the rest of this article. The Domain Access Code used in this example is Password. When a Client-Principal is created for a user belonging to a certain Domain, it must be sealed and validated using the Domain Access Code of that domain. Thus in all places where you need to configure the Domain Access Code (or Key), you must ensure that you provide the same Access code.

Note: The OpenEdge database contains a built-in blank Domain with a blank Domain Access Code. It is recommended that you modify the Domain Access Code of the blank domain for your production environment.
 
  • Create users in the _Users table for the corresponding Security Domain (e.g. RESTDomain). You can do this via the Data Administration tool through the Admin menu -> Security -> Edit User List... . A similar option exists in OpenEdge Explorer/Management. Alternatively you can also create the users via ABL, for example:
/* For the built-in blank Domain */
CREATE _User.
assign _User._Userid = "user1"
_User._Password = ENCODE("pass1")
_User._User-Name = "user1"
_User._User_number = 1001
_User._Domain-Name = ""
_User._Disabled = NO.

/* For the RESTDomain domain */
CREATE _User.
assign _User._Userid = "restuser1"
_User._Password = ENCODE("pass1")
_User._User-Name = "restuser1"
_User._User_number = 2001
_User._Domain-Name = "RESTDomain"
_User._Disabled = NO.

 
Note: The sample OERealm AppServer ABL interface which is used in this article requires the _User._User-number field to be populated with a unique number and the _User._Disabled field with NO or FALSE. So make sure these fields are populated if you are using existing users which have already been created in the _User table.
  • Create security roles and grant these to the user accounts. This allows the AppServer to return the corresponding security role for a user. The OpenEdge REST applications specify roles within their security templates (e.g. the /WEB-INF/appSecurity-*.xml configured within <Developer Studio Project>/RESTContent/WEB-INF/web.xml). The access rights for the URLs that belong to a REST application are specified via these roles. By default, the OpenEdge REST security templates are configured to use the ROLE_PSCUser role to allow users access to a REST application. When the AppServer returns the security role of a user, the REST application (via Spring security) will prefix the security role name with ROLE_ when using one of the default security templates. So create the security role and grant them to the users as follows:
CREATE _Sec-role.
ASSIGN _Role-name = "PSCUser"
_Role-description = "User level role".
_Role-creator = "". /* Name of the user or Role that created this role */

CREATE _sec-granted-role.
ASSIGN _sec-granted-role._grantee = "restuser1@RESTDomain".
_sec-granted-role._role-name = "PSCUser".
_sec-granted-role._grantor = "". /* the user or role that granted use of this role */
_sec-granted-role._grant-rights = YES.
_sec-granted-role._granted-role-guid = substring(base64-encode(generate-uuid), 1, 22).



AppServer Configuration
  • Configure a state-free AppServer. For this example use the default restbroker1 AppServer instance defined in OpenEdge Developer Studio. In a production environment, please make sure you use a separate AppServer instance which is dedicated to the OERealm class (see next step). For security reasons it's not recommended that you use the same AppServer instance to run both your application logic and OERealm class.
  • Create a singleton OOABL class that implements the OERealm HybridRealm interface where the following methods are mandatory: 
/*-----------------------------------------------------------------------------
Purpose: Looks up the user account and returns a numeric user number
INPUT : 1. fully qualified user name
e.g.: fred@SPAClient or
fred (indicating blank domain)
------------------------------------------------------------------------------*/

METHOD PUBLIC INTEGER ValidateUser( INPUT userName AS CHARACTER )


/*------------------------------------------------------------------------------
Purpose: validates the client supplied account password with the physical account's value
INPUT : 1. userNum – numeric user number returned by ValidateUser()
2. passwd – password text
------------------------------------------------------------------------------*/

METHOD PUBLIC BOOLEAN ValidatePassword (INPUT userNum AS INTEGER,
INPUT passwd AS CHARACTER ):


/*-----------------------------------------------------------------------------
Purpose: retrieves an attribute for the specified user.
INPUT : 1. userNum – numeric user number returned by ValidateUser()
2. attrName – name of the Attribute
------------------------------------------------------------------------------*/

METHOD PUBLIC CHARACTER GetAttribute( INPUT userNum AS INTEGER,
INPUT attrName AS CHARACTER ):
 
For this example, within your OpenEdge Developer Studio please copy the attached HybridRealm.cls to <Developer Studio REST Project>/AppServer/OpenEdge/Security/Realm/ and Properties.cls to <Developer Studio REST Project>/AppServer/OpenEdge/Security/Util/ . These classes are provided as an example, so you can use your own classes if you prefer.
  • Secure the HybridRealm class to prevent it from being called by any other AppServer client (e.g. internal ABL clients or Open Client). You can do this by generating a Client-Principal file using the genspacp.bat utility by running the following in a Proenv window:
genspacp -password RESTSPAPassword -role RESTSpaClient

genspacp 1.0
Generated sealed Client Principal...
    User: BPSServer@OESPA
    Id: SmjnCQ1kTm2fY5r8pxQg5A
    Role: RESTSpaClient
    Encoded Password: oech1::02171c130115120331213c303d3737
    File: oespaclient.cp
    State: SSO from external authentication system
    Seal is valid

The serialized Client-Principal file (oespaclient.cp) can then be used by the OERealm Service (i.e. REST web application) to authenticate itself against the AppServer OpenEdge.Security.Realm.HybridRealm class in the ValidateClient() method (a sample oespaclient.cp file is attached). In later steps you will see where the oespaclient.cp file has to be specified. Please note that the values provided for the password and role in the genspacp command are independent to the ones used earlier for the Security Domain password and the REST client role. The sample OpenEdge.Security.Realm.HybridRealm class reads these two values from a spaservice.properties file and compares them with the values that are sent by the OERealm Service.
  • Place the oespaclient.cp file at <Developer Studio REST Project>/RESTContent/WEB-INF/classes/ .
  • Update the spaservice.properties file with the following values (a sample spaservice.properties file is attached):
ValidateCP=true
Password=oech1::02171c130115120331213c303d3737
Role=RESTSpaClient
DebugMsg=true
  • Place the spaservice.properties file at <Developer Studio REST Project>/AppServer/ or any other location which is within the AppServer's Propath.


Spring Security Configuration within OpenEdge Developer Studio
  • Open <Project>/RESTContent/WEB-INF/web.xml .
  • If you want the OERealm Service to supply user account information like user-number, password, roles, state (disabled or not), locked status, expired status to common Spring Security authentication process, you will need to configure your REST service to use the appSecurity-xxxx-oerealm.xml template. Configure the use of the "/WEB-INF/appSecurity-form-oerealm.xml" security template as follows:
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            <!-- USER EDIT: Select which application security model to employ
            /WEB-INF/appSecurity-basic-local.xml
            /WEB-INF/appSecurity-anonymous.xml
            /WEB-INF/appSecurity-form-local.xml
            /WEB-INF/appSecurity-container.xml
            /WEB-INF/appSecurity-basic-ldap.xml
            /WEB-INF/appSecurity-form-ldap.xml
            /WEB-INF/appSecurity-basic-oerealm.xml
            /WEB-INF/appSecurity-form-oerealm.xml
            /WEB-INF/appSecurity-form-saml.xml
            /WEB-INF/appSecurity-basic-saml.xml
            -->

            /WEB-INF/appSecurity-form-oerealm.xml
        </param-value>
    </context-param>
  • Open <Project>/RESTContent//WEB-INF/appSecurity-form-oerealm.xml .
  • The OERealmUserDetails bean allows the Spring Security framework to know where the OERealm AppServer is running. It also allows the Spring Security framework to know how to communicate and fetch the user account details for performing authentication. This is done by calling the GetAttribute method in the OERealm HybridRealm interface (e.g. HybridRealm.cls) and passing it the values of the property (e.g. ATTR_ENABLED). Configure the OERealmUserDetails properties as follows:
    <b:bean id="OERealmUserDetails"
            class="com.progress.rest.security.OERealmUserDetailsImpl" >
            <b:property name="realmURL" value="AppServer://localhost:5162/restbroker1" />
            <b:property name="realmClass" value="OpenEdge.Security.Realm.HybridRealm" />
            <b:property name="grantedAuthorities" value="ROLE_PSCUser" />
            <b:property name="rolePrefix" value="ROLE_" />
            <b:property name="roleAttrName" value="ATTR_ROLES" />
            <b:property name="enabledAttrName" value="ATTR_ENABLED" />
            <b:property name="lockedAttrName" value="ATTR_LOCKED" />
            <b:property name="expiredAttrName" value="ATTR_EXPIRED" />
            <b:property name="realmPwdAlg" value="0" />
            <b:property name="realmTokenFile" value="oespaclient.cp" />
            <!-- For SSL connection to the oeRealm appserver provide the complete
                 path of psccerts.jar as the value of 'certLocation' property
             -->

            <b:property name="certLocation" value="" />
    </b:bean>

Note 1: The "realmURL" property in the OERealmUserDetails bean specifies the AppServer where the OERealm server class is located (see "realmClass" property). The AppServer location for your business logic (AppServer procedures and classes) can be configured as detailed in KBase article 000038774, "How to change the properties of a REST application".

Note 2: If the OERealm HybridRealm interface (e.g. HybridRealm.cls) does not return a role for a user (using roleAttrName), the value of grantedAuthorities will be used for role-based access. Similarly, if the user account does not support enabledAttrNamelockedAttrName or expiredAttrName and returns null or an invalid value (so neither 0 or 1), then the default values will be used, i.e. the user will be assumed to be enabled, not locked and not expired. For example in the sample HybridRealm class that has been provided, the GetAttribute method does not have a check for the lockedAttrName attribute, so the default value will be returned. Starting with OpenEdge 11.6.1, alternatively you can stop the OpenEdge REST Service from asking the OERealm server class for those attributes by setting the relevant property values to a blank string. In such a case the default values will be used.
  • Once the OERealm Interface returns the user account details and Spring Security authenticates and authorizes the user, you may want to create a new Client-Principal with the user details, seal it and send it to the AppServer where the REST service classes or procedures run. In order to do that you will need to configure the OERealmAuthProvider bean in the appSecurity-form-oerealm.xml file:
    <b:bean id="OERealmAuthProvider"
            class="com.progress.rest.security.OERealmAuthProvider" >
            <b:property name="userDetailsService">
                        <b:ref bean="OERealmUserDetails"/>
            </b:property>
            
            <b:property name="createCPAuthn" value="true" />
            <b:property name="multiTenant" value="false" />
            <b:property name="userDomain" value="RESTDomain" />
            <b:property name="key" value="oech1::00333c34252a2137" />
            <b:property name="authz" value="true" />
            <!--
            <b:property name="properties" >
                <b:map>
                     <b:entry key="prop-1" value="string1"/>
                     <b:entry key="prop-2" value="string2"/>
                </b:map>
            </b:property>
            <b:property name="expires" value="600" />
            -->

    </b:bean>


Note: In this example we use the oech1::<hex-string> format for the Security Domain Code as opposed to clear-text. You can generate the <hex-string> value using the genpassword utility, for example:
genpassword -password Password
 


Usage of the Domain Access Code Explained

During the initial authentication phase (e.g. the login page) the REST application (via the Spring Security Framework) will create a sealed Client-Principal by using the "oespaclient.cp" Client-Principal file and send this to the HybridRealm class. So in the ValidateClient() method of the HybridRealm class it checks whether the sealed Client-Principal object is valid by using the VALIDATE-SEAL() method, for example:
 
METHOD PROTECTED LOGICAL ValidateClient( ):
   DEFINE VARIABLE result AS LOGICAL NO-UNDO INITIAL FALSE.
   DEFINE VARIABLE hCP AS HANDLE NO-UNDO.

   hCP = SESSION:CURRENT-REQUEST-INFO:GetClientPrincipal().

   IF (? <> hCP) THEN
      result = hCP:VALIDATE-SEAL (passwd).

If the Client-Principal was sealed using the same password (i.e. Domain Access Code), then it continues authenticating the user who is trying to login.

Once the login is successful the user can make calls to the business logic (AppServer procedures / classes), but when calling the business logic another sealed Client-Principal object is used. In this case the Client-Principal is sealed using the value of the "key" attribute in the OERealmAuthProvider bean as the password. So then in your business logic you can do something like this to authorize the user:
 
DEFINE VARIABLE hCP AS HANDLE NO-UNDO.

hCP = SESSION:CURRENT-REQUEST-INFO:GetClientPrincipal().
MESSAGE "Username: " + hCP:QUALIFIED-USER-ID.
MESSAGE "Client-Principal is sealed: " hCP:VALIDATE-SEAL ("oech1::00333c34252a2137").

IF NOT SET-DB-CLIENT(hCP, "Sports2000") THEN DO:
   MESSAGE "User not authorized!".
   QUIT.
END.

DELETE OBJECT hCP.

In the SET-DB-CLIENT() method it's possible to check whether the sealed Client-Principal which has been sent by the REST application has been sealed with the same Domain Access Code that has been defined for the database. It's simply an extra layer of security before allowing a user to connect to your database.
 


REST Service Test
  • For example using a web browser (you can use any other REST client that you prefer), login to the REST application by going to http://localhost:8980/<REST web application name>/static/auth/login.html . In our example you would login with username restuser1 (no need to add "@RESTDomain") and password pass1. If the login was successful an HTTP response is returned from the REST Service which contains a JSESSIONID cookie. The JSESSIONID cookie then needs to be used in subsequent requests to the REST Service by including an HTTP header property as follows:
Cookie: JSESSIONID=11EDA108E2CFD30E142BECDC69D494AA
  • From the same web browser session, call your REST web application, for example: http://localhost:8980/RESTTestService/rest/RESTTest/Roundtrip/myparam1/myparam2 . Note that if you call your REST web application without logging in first, then you will be redirected to the login page in the previous step.
  • The REST client can invalidate the JSESSIONID cookie (and thus the client session) by logging out of the REST Service. To do this go to http://localhost:8980/<REST web application name>/static/auth/logout.html .


OERealm (Form) Security Overview

OERealm (Form) Security Overview
Note: Right-click on the image and save it or open it in a separate window/tab to see it in full size.
Workaround
Notes
To enable debug logging for security related issues within a REST web application, please follow these steps:
  • In the REST web application's logging configuration file (<Developer Studio REST Project>/RESTContent/WEB-INF/classes/log4j.properties) change the logging level at the following line from ERROR to DEBUG: 
log4j.logger.com.progress.rest.security=ERROR, xxxxxxxx
  • Restart the restmgr1 instance within OpenEdge Developer Studio for the change to take effect. The logging will appear in the REST service's log file located under %DLC%\servers\tomcat\pdsoe\<REST web application name>\WEB-INF\adapters\logs\ . If Tomcat was started via the protc start command, the logging will appear in the REST service's log file located under %DLC%\servers\tomcat\webapps\<REST web application name>\WEB-INF\adapters\logs\ 
For the latest version of the Security Realm classes provided in attachment are check your OpenEdge installation: %DLC%\src\samples\security\OpenEdge\Security\Realm

Progress Article(s):
000056769, How do HTTP REST requests to the AppServer use the client-principal object?
000051207, What are the basic steps to enable JSESSIONID cookies for an OpenEdge REST Service ?
000052762, How to specify an expiration time for the JSESSIONID that is generated by an OpenEdge REST web application.
000062714, What is the difference between the clear-text and digest form of the ValidatePassword() method which can be used in the OERealm server class when using SPA with the REST Adapter ?
000062448, What is the difference between the appSecurity-basic-oerealm.xml and the appSecurity-form-oerealm.xml REST Adapter security templates ?
000062762, The ClientContextId is 0 when an OpenEdge REST web application is configured with the appSecurity-form-oerealm.xml security template
000064078, The REST Adapter OERealm returns java.lang.NullPointerException if one of the relevant properties of the user attributes is set to null
000062375, How to configure OERealm authentication with PASOE


References to Other Documentation:
OpenEdge Application Server: Administration, Chapter 20: "REST Management Agent and REST Web Application Security Configurations"
Last Modified Date7/24/2017 1:28 PM


Feedback
 
Did this article resolve your question/issue?

   

Your feedback is appreciated.

Please tell us how we can make this article more useful. Please provide us a way to contact you, should we need clarification on the feedback provided or if you need further assistance.

Characters Remaining: 1025