Yes, it is possible. Use apex_util.create_user. Some 19.1 documentation is at https://docs.oracle.com/en/database/oracle/application-express/19.1/aeapi/CREATE_USER-Procedure.html#GUID-95721E36-4DAB-4BCA-A6F3-AC2BACC52A66.
The current user (i.e. workspace schema) does need administration privileges, but you can get around this if necessary by setting up an Oracle Scheduler job to run the PLS code. Your workspace will need the privileges to create scheduler jobs in that case.
In your application's shared components, in the Security section at the bottom of the page, Runtime API Usage, set it to "Modify Workspace Repository".
In your code procedure you will need to use wwv_flow_api.set_security_group_id (workspace_id); before running create_user. You can get the workspace id from the view APEX_WORKSPACES. You may also need to set wwv_flow.g_flow_id and wwv_flow.g_instance to the app_id and session_id respectively. Create a separate procedure to do these things as it will often be needed if you want to access apex views and API's when calling procedures from within APEX.
Beside Rick's reply, I would like to tell you that you could customize the workspace login page. Please take a look at these links
I hope that helps
Rick, is there some example of this action plan?
Sorry for late reply. I do have apps which create new users in the background. I don't know whether you have solved your issue yet, but here is a packaged procedure that creates a new Apex user. I call this from a process which reads new records placed in a table by an application. It runs on a schedule every few minutes. But you can call it directly from the app if your application has the settings referred to in my answer above.
p_user_name IN VARCHAR2,
p_email_address IN VARCHAR2,
p_first_name IN VARCHAR2,
p_last_name IN VARCHAR2,
p_web_password IN VARCHAR2,
p_initial_group IN VARCHAR2,
p_change_password_on_first_use IN VARCHAR2,
p_app_id IN PLS_INTEGER,
p_session_id IN INTEGER)
WHEN OTHERS THEN
create_message('create_user','Creating user '||p_user_name||': '||SQLERRM);
"set_workspace_id" is this:
CREATE OR REPLACE PROCEDURE "SET_WORKSPACE_ID" (
p_app_id IN PLS_INTEGER DEFAULT NULL,
p_session_id IN NUMBER DEFAULT NULL)
ln_workspace_id := tprf_utils.get_workspace_id;
IF NVL(p_app_id,0) != 0 THEN
wwv_flow.g_flow_id := p_app_id;
IF NVL(p_session_id,0) != 0 THEN
wwv_flow.g_instance := p_session_id;