Oracle APEX – Custom Authentication
Complete Guide to Setting Up Custom Authentication Learn how to set up Custom authentication. To create a custom authentication scheme: On the Workspace home page, click the App Builder icon. Select an application. On the Application home page, click Shared Components. The Shared Components page appears. Under Security, select Authentication Schemes. On the Authentication Schemes page, click Create. Select Based on a pre-configured scheme from the gallery and click Next. Under Name: Name – Enter the name used to reference the authentication scheme by other application developers. Scheme Type – Select Custom. Fill in the appropriate fields. To learn more about a specific field, see field-level Help. Click Create Authentication Scheme. Creating a custom authentication scheme alone is not sufficient for building a fully tailored authentication system. There are many additional, actionable, and important considerations involved. In this article, we’ll dive deep into this complex subject and provide you with practical, step-by-step guidance to help you implement a complete custom authentication solution in your Oracle APEX application. Here are the key topics we’ll cover to help you gain a thorough understanding of the subject. Create a custom authentication scheme – covered above. Create Custom Logic – a PL/SQL code Password Security Register a User Authenticate User Using Username and Password Using Username and OTP (optional) Password Reset Role Base Access Control – RBAC Writing a separate article on RBAC. Lets start writing our custom logic for our Oracle APEX Application’s custom authentication. Login to your APEX instance as developer. Navigation: Home > SQL Workshop > Object Browser On the left panel, click on + sign and choose Package to create Give a name – LAMCX_AUTH Enter the name of your choice for the Package to be created. The name must conform to Oracle naming conventions and cannot contain spaces, or start with a number or underscore. Click on Create Package Now refresh page and check in left panel, you will find your newly created package. Navigate to Package Specification Create function – HASH_PASSWORD FUNCTION hash_password ( p_user_name IN VARCHAR2, p_password IN VARCHAR2 ) RETURN VARCHAR2; Create procedure – SIGNUP PROCEDURE signup ( p_first_name IN VARCHAR2, p_last_name IN VARCHAR2, p_user_name IN VARCHAR2, p_email IN VARCHAR2, p_app_id NUMBER, p_company_id NUMBER, p_referral varchar2, p_password IN VARCHAR2 DEFAULT NULL ); Create procedure – AUTHENTICATE_USER FUNCTION authenticate_user ( p_username IN VARCHAR2, p_password IN VARCHAR2 DEFAULT NULL, p_app_id IN NUMBER DEFAULT NULL, p_by_otp IN VARCHAR2 DEFAULT 'N', p_otp IN NUMBER DEFAULT NULL ) RETURN BOOLEAN; Create procedure – RESET_PASSWORD PROCEDURE reset_password ( p_user_name IN VARCHAR2, p_password IN VARCHAR2, p_password_new IN VARCHAR2 ); Create function – IS_ACTIVE FUNCTION is_active( p_username varchar2 default null ) return boolean; Click on button – Save and Compile You must check and confirm, if package is compiled successfully Create database table – LAMCX_USERS, to store user data. CREATE TABLE "LAMCX_USERS" ( "ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE, "FIRST_NAME" VARCHAR2(2000 CHAR), "LAST_NAME" VARCHAR2(2000 CHAR), "USER_NAME" VARCHAR2(100), "START_DATE" DATE, "END_DATE" DATE, "STATUS" VARCHAR2(30 CHAR), "REMARKS" VARCHAR2(4000 CHAR), "ATTRIBUTE1" VARCHAR2(150 CHAR), "ATTRIBUTE2" VARCHAR2(150 CHAR), "ATTRIBUTE3" VARCHAR2(150 CHAR), "ATTRIBUTE4" VARCHAR2(150 CHAR), "ATTRIBUTE5" VARCHAR2(150 CHAR), "PASSWORD_HASH" VARCHAR2(2000 CHAR), "PASSWORD_RAW" VARCHAR2(30 CHAR), "MOBILE" VARCHAR2(30 CHAR), "EMAIL" VARCHAR2(100 CHAR), "SOURCE" VARCHAR2(30 CHAR), "USER_TYPE" VARCHAR2(30 CHAR), "PWD_UPDATE_STATUS" VARCHAR2(10 CHAR), "PWD_UPDATE_DATE" DATE, "PWD_UPDATED_BY" NUMBER, "PWD_OTP" VARCHAR2(10 CHAR), "PWD_OTP_REQ_START" DATE, "PWD_OTP_REQ_END" DATE, "EMP_IMG" BLOB, "COMPANY_ID" NUMBER, "CREATED" DATE, "CREATED_BY" VARCHAR2(255 CHAR), "UPDATED" DATE, "UPDATED_BY" VARCHAR2(255 CHAR), "ACCESS_ROLES" VARCHAR2(500), "FULL_NAME" VARCHAR2(150), "APPLICATION_ID" NUMBER, "BUYER" VARCHAR2(1) DEFAULT 'N', "AGENT" VARCHAR2(1) DEFAULT 'N', "LINKEDIN" VARCHAR2(200), "LAST_LOGGED_METHOD" VARCHAR2(30), "LAST_LOGGED" DATE, "DISPLAY_IMAGE" VARCHAR2(4000), "EMAIL_VERIFIED" VARCHAR2(10) DEFAULT 'N', "COUNTRY" VARCHAR2(100), "STATE" VARCHAR2(100), "CITY" VARCHAR2(100), "ADDRESS_1" VARCHAR2(100), "ADDRESS_2" VARCHAR2(100), "POSTAL_CODE" VARCHAR2(15), "ORG_TAX_ID" VARCHAR2(20), "ORG_INCOME_ID" VARCHAR2(20), "CONNECT_REQ" VARCHAR2(10) DEFAULT 'N', "REQUESTED_BY" NUMBER, "REQUEST_DATE" DATE, "ISD" NUMBER, "REQUESTED_BY_COMP" NUMBER, "ACCOUNT_TYPE" VARCHAR2(20), "RELEASE_REQUEST" VARCHAR2(10) DEFAULT 'N', "RELEASE_REQUEST_DATE" DATE, "RELEASE_REQUEST_BY" NUMBER, "RELEASE_REQUEST_REM" VARCHAR2(1000), "WF_ID" NUMBER, "ACCEPT_PRIVACY_POLICY" VARCHAR2(10) DEFAULT 'N', "ACCEPT_PRIVACY_POLICY_DATE" DATE, "REFERRAL_USER" NUMBER, "OTP_LOGIN" NUMBER, "OTP_LOGIN_DATE" DATE, "PARSE_COUNT" NUMBER DEFAULT 0, "PARSE_COUNT_MAX" NUMBER DEFAULT 3, CONSTRAINT "LAMCX_USERS_ID_PK" PRIMARY KEY ("ID") USING INDEX ENABLE ) ; Create an Index on the table CREATE OR REPLACE EDITIONABLE TRIGGER "LAMCX_USERS_BIU" BEFORE INSERT OR UPDATE ON LAMCX_USERS FOR EACH ROW BEGIN IF inserting THEN — Make sure to save the full name — Make sure to save the username in upper case :new.user_name := upper(:new.user_name); — Hash the password so we are not saving clear text — :new.password_hash := lams_auth.hash_password(upper(:new.user_name), :new.user_name||'@123'); :new.password_hash := lamcx_auth.hash_password(upper(:new.user_name), nvl(:new.password_raw,:new.user_name||'@123')); :new.created := sysdate; :new.created_by := e(coalesce(sys_context('APEX$SESSION', 'APP_USER'), user)); END IF; :new.first_name := initcap(substr(:new.first_name,1,100)); :new.last_name := initcap(substr(:new.last_name,1,100)); :new.full_name := trim(:new.first_name||' '||:new.last_name); :new.updated := sysdate; :new.updated_by := e(coalesce(sys_context('APEX$SESSION', 'APP_USER'), user)); END LAMCX_USERS_biu; CREATE TABLE "LAMCX_APEX_WORKSPACE_SESSIONS" ( "WORKSPACE_ID" NUMBER NOT NULL ENABLE, "WORKSPACE_NAME" VARCHAR2(255) NOT NULL ENABLE, "WORKSPACE_DISPLAY_NAME" VARCHAR2(4000), "APEX_SESSION_ID" NUMBER, "USER_NAME" VARCHAR2(255), "REMOTE_ADDR" VARCHAR2(255), "SESSION_CREATED" DATE NOT NULL ENABLE, "SESSION_IDLE_TIMEOUT_ON" DATE NOT NULL ENABLE, "SESSION_LIFE_TIMEOUT_ON" DATE NOT NULL ENABLE, "SESSION_MAX_IDLE_SEC" NUMBER(5,0) NOT NULL ENABLE, "SESSION_TIME_ZONE" VARCHAR2(255), "SESSION_LANG" VARCHAR2(5), "SESSION_TERRITORY" VARCHAR2(255), "SESSION_DEBUG_LEVEL" NUMBER(1,0), "SESSION_TRACE_MODE" VARCHAR2(5), "SESSION_AUTHENTICATION_ID" NUMBER, "SESSION_TENANT_ID" VARCHAR2(32), "SESSION_AUTHENTICATION_NAME" VARCHAR2(255), "APPLICATION_ID" NUMBER, "SESSION_END" DATE ) ; As now we have created our package specification, ready with our database table, now we proceed to create our package body. Inside package, select Body tab and start writing your actual logic for the process. Function – HASH_PASSWORD FUNCTION hash_password ( p_user_name IN VARCHAR2, p_password IN VARCHAR2 ) RETURN VARCHAR2 IS l_user lamcx_users.user_name%TYPE := upper(p_user_name); l_password VARCHAR2(1000); BEGIN — — The following encrypts the provided password and returns the encrypted string. — This is a one-way encryption using SHA512 — SELECT standard_hash(p_user_name || p_password, 'SHA512') INTO l_password FROM dual; RETURN l_password; END hash_password; Procedure – SIGNUP PROCEDURE signup ( p_first_name IN VARCHAR2, p_last_name IN VARCHAR2, p_user_name IN VARCHAR2, p_email IN VARCHAR2, p_app_id NUMBER, p_company_id NUMBER, p_referral varchar2, p_password IN VARCHAR2 DEFAULT NULL ) IS l_status VARCHAR2(1000) := NULL; l_status2 VARCHAR2(1000) := NULL; l_id NUMBER; x NUMBER := 0; cnt NUMBER := 0; — e_oldpass EXCEPTION; — PRAGMA exception_init ( e_oldpass, -20001 ); BEGIN BEGIN SELECT COUNT(1) INTO x FROM lamcx_users WHERE user_name = p_user_name AND (company_id is null or company_id = p_company_id); IF x > 0 THEN cnt





