To validate a user’s identity, specific security components must be in place. You need to:

  1. Create a suitable domain for the user.
  2. Define roles that can be granted to control access to specific areas of the application.

This can be accomplished using the DataAdmin API within ABL to interact with the database. The API allows you to check for existing users, domains, roles, and grants, and create them if they do not already exist.

Key terms and values

  • Domain Name

    A new security domain, for example "MyAppDomain", will be associated with authenticated clients. If a username provided to the OERealm authentication process does not include an “@” portion, this domain will be automatically appended.

  • Domain Access Code

    The Domain Access Code in this example is "s3cretp4ssword". When creating a Client-Principal for a user in a specific domain, it must be sealed and validated using that domain’s access code. Verify the same access code is consistently configured wherever required.
    Note: The OpenEdge database includes a built-in blank domain with a blank access code. It is recommended to disallow this blank access code in production environments.
  • Granted Roles

    Roles allow PAS for OpenEdge to return the appropriate security role for a user. ABL web applications define roles in their security templates, which specify access rights for URIs within the application.

    By default, the oeablSecurity.csv template uses roles such as "ROLE_PSCUser" to grant access to URI patterns. When the OERealm service interface returns a user’s roles, Spring Security prefixes them with ROLE_ when using default templates.

    Two tables manage roles and grants: _sec-role and _sec-granted-role. Each entry must include a user identifying the creator or grantor.

Sample CreateDomain.p

Copy the following procedural code into a file named CreateDomain.p, then run it using one of these commands:

If the database is being served in a multi-user mode:

mpro -db path_to_db -b -p CreateDomain.p

If the database is at rest in single-user mode:

pro -db path_to_db -b -p CreateDomain.p

Procedure source code:

/*------------------------------------------------------------------------
    File        : CreateDomain.p
    Purpose     : Create a single domain for OERealm.
    Syntax      : [m]pro -db <db_name> -b -p createDomain.p
    Description : Execute as a procedure while connected to a databases.
    Author(s)   : Progress Software Corporation
    Created     : Thu Oct 23 15:42:17 EST 2025
    Notes       : Adds domain to database(s), creates user accounts,
                  and creates/grants basic roles.
  ----------------------------------------------------------------------*/

using Progress.Lang.Error from propath.
using OpenEdge.DataAdmin.* from propath.
using OpenEdge.DataAdmin.Error.* from propath.
using OpenEdge.DataAdmin.Lang.Collections.* from propath.

block-level on error undo, throw.

/* NOTICE: Do not use the "@" symbol in any passcodes! */
&global-define DomainName MyAppDomain
&global-define DomainType _oeusertable
&global-define AccessCode s3cretp4ssword
&global-define DBUserName dbuser1
&global-define RealmUserName realmuser1
&global-define CommonUserPwd p4ssw0rd
&global-define UserRoles PSCAdmin,PSCUser

define variable oService   as DataAdminService no-undo.
define variable oDomain    as IDomain          no-undo.
define variable oDBUser    as IUser            no-undo.
define variable oRealmUser as IUser            no-undo.
define variable oRole      as IRole            no-undo.
define variable oGrant     as IGrantedRole     no-undo.
define variable oIterator  as IIterator        no-undo.

define variable iDB as integer no-undo.
define variable iRole as integer no-undo.
define variable cFQUN as character no-undo.
define variable cRole as character no-undo.

/* Apply changes to all connected databases. */
do iDB = 1 to num-dbs:
    assign oService = new DataAdminService(ldbname(iDB)).
    if valid-object(oService) then do on error undo, throw:
        message substitute("Modifying DB '&1'", ldbname(iDB)) view-as alert-box.

        assign oDBUser = oService:GetUser("{&DBUserName}").
        if not valid-object(oDBUser) then do:
            /* Create New User */
            message substitute("Creating DB User '&1'", "{&DBUserName}") view-as alert-box.
            assign
                oDBUser = oService:NewUser("{&DBUserName}")
                oDBUser:Name = "{&DBUserName}"
                oDBUser:Password = "{&CommonUserPwd}"
                oDBUser:Description = "DB User"
                oDBUser:Number = 1001
                .
            oService:CreateUser(oDBUser).
        end. /* not valid-object(oDBUser) */

        /* Set the current database user to that of the common DB user for purposes of creating and granting roles. */
        message substitute("Changing DB '&1' User from '&2' to '&3'", ldbname(iDB), userid(ldbname(iDB)), "{&DBUserName}") view-as alert-box.
        setuserid("{&DBUserName}", "{&CommonUserPwd}", ldbname(iDB)).

        assign oDomain = oService:GetDomain("{&DomainName}").
        if not valid-object(oDomain) then do:
            /* Create New Domain */
            message substitute("Creating Domain '&1'", "{&DomainName}") view-as alert-box.
            assign
                oDomain = oService:NewDomain("{&DomainName}")
                oDomain:AuthenticationSystem = oService:GetAuthenticationSystem("{&DomainType}")
                oDomain:AccessCode = "{&AccessCode}"
                oDomain:Description = "OERealm Domain"
                oDomain:IsEnabled = true
                .
            oService:CreateDomain(oDomain).
        end. /* not valid-object(oDomain) */

        /* Create a fully-qualified username from the user + domain names. */
        assign cFQUN = substitute("&1@&2", "{&RealmUserName}", "{&DomainName}").

        assign oRealmUser = oService:GetUser(cFQUN).
        if not valid-object(oRealmUser) then do:
            /* Create New User */
            message substitute("Creating Realm User '&1'", cFQUN) view-as alert-box.
            assign
                oRealmUser = oService:NewUser("{&RealmUserName}")
                oRealmUser:Name = "{&RealmUserName}"
                oRealmUser:Password = "{&CommonUserPwd}"
                oRealmUser:Description = "Realm User"
                oRealmUser:Number = 2001
                oRealmUser:Domain = oDomain
                .
            oService:CreateUser(oRealmUser).
        end. /* not valid-object(oRealmUser) */

        ROLEBLK:
        do iRole = 1 to num-entries("{&UserRoles}"):
            assign cRole = entry(iRole, "{&UserRoles}").
            message substitute("Examining Role '&1'...", cRole) view-as alert-box.

            assign oRole = oService:GetRole(cRole).
            if not valid-object(oRole) then do:
                /* Create New Role */
                message substitute("Creating Role '&1'", cRole) view-as alert-box.
                assign
                    oRole = oService:NewRole(cRole)
                    oRole:Description = substitute("OERealm Role &1", cRole)
                    oRole:Creator = substitute("&1@", userid(ldbname(iDB))) /* Must be a qualified name (using blank domain) */
                    .
                oService:CreateRole(oRole).
            end. /* not valid-object(oRole) */

            message substitute("Locating Granted Role for '&1'", substitute("&1:&2", cFQUN, cRole)) view-as alert-box.
            oIterator = oRole:GrantedRoles:Iterator().
            do while oIterator:HasNext():
                assign oGrant = cast(oIterator:Next(), IGrantedRole).
                if oGrant:Grantee eq cFQUN and oGrant:Role:Name eq cRole then do:
                    message "Found existing granted role, skipping..." view-as alert-box.
                    next ROLEBLK. /* When we have a match, skip to the next role. */
                end.
            end.

            /* Create New Granted-Role for this role and realm user, as a matching granted role was not found. */
            message substitute("Granting Role '&1' to '&2'", cRole, cFQUN) view-as alert-box.
            assign
                oGrant = oService:NewGrantedRole()
                oGrant:Grantee = cFQUN
                oGrant:Role = oRole
                oGrant:Grantor = userid(ldbname(iDB)) /* Needs only to be a valid user id (name). */
                oGrant:CanGrant = true
                .
            oService:CreateGrantedRole(oGrant).
        end.

        finally:
            delete object oService no-error.
        end.
    end. /* valid-object(oService) */
end. /* iDB */

catch e as Error:
    define variable errorHandler as DataAdminErrorHandler no-undo.
    errorHandler = new DataAdminErrorHandler().
    errorHandler:Error(e).
end catch.
finally:
    delete object oDomain no-error.
    return string(0).
end finally.

Restart the PAS for OpenEdge instance

After configuring the OERealm ABL Service Interface, Spring Security settings, and database domains as described, restart the PAS for OpenEdge instance to apply the new OERealm-based security model.

If you are using the “RealmExample” PAS for OpenEdge instance mentioned earlier, run the following command:

pasman oeserver -restart -I RealmExample

Service testing and troubleshooting

After configuring the authentication service and restarting the PAS for OpenEdge instance, you can test the setup by visiting:

http://localhost:port/[webapp_name/]static/auth/login.html

If the login succeeds, the REST service will return an HTTP response containing a JSESSIONID cookie. This cookie identifies the authenticated user and can be used in subsequent requests to the server.

If the service does not respond as expected, check the logs in CATALINA_BASE/logs.

Look for these files in the CATALINA_BASE/logs directory for potential errors or useful information:

  • app_name.agent.date.log
  • app_name.authn.date.log
  • app_name.date.log

To log out and invalidate the JSESSIONID cookie (ending the client session), visit:

http://localhost:port/[webapp_name/]static/auth/logout.html

For more information, see OERealm security considerations.