Oracle APEX Security Best Practices: Protecting Your Application
Building an Oracle APEX application that works is one thing. Building one that is secure is another challenge entirely. Security is not a feature you add at the end — it is a discipline you apply throughout the development process.
In this guide, we cover the most important APEX security best practices: authentication, authorization, session state protection, SQL injection prevention, Cross-Site Scripting (XSS) defense, and the APEX security settings you should review on every application.
1. Use HTTPS Everywhere
This is non-negotiable. All APEX applications in any environment beyond your local machine must run over HTTPS. HTTP exposes session tokens, credentials, and data to network eavesdropping.
Configure HTTPS at the web server level (Oracle HTTP Server, NGINX, Apache) with a valid TLS certificate. Force HTTP to redirect to HTTPS — never allow unencrypted access.
2. Enable Session State Protection
Session State Protection (SSP) prevents users from manipulating page item values through URL parameters. Without it, a user could craft a URL like:
https://yourapp.com/apex/f?p=100:10:SESSION::NO::P10_USER_ID:999
…and potentially impersonate another user or access unauthorized data.
Enable SSP in Application Properties → Security → Session State Protection. Then set appropriate protection levels on sensitive items:
- Checksum Required — Item can be set via URL but requires a valid checksum (generated by APEX)
- Restricted — No Direct Browser Access — Item cannot be set via URL at all
- Unrestricted — No protection (use only for non-sensitive items)
Any item that holds a user ID, a role, or sensitive business data should be set to Restricted.
3. Implement Proper Authorization Schemes
Authentication tells you who the user is. Authorization controls what they can do. Every sensitive page, region, button, and menu item should have an Authorization Scheme applied.
-- Authorization Scheme: Check if user has admin role
-- Type: PL/SQL Function Body Returning Boolean
RETURN (
SELECT COUNT(*)
FROM app_user_roles
WHERE username = UPPER(:APP_USER)
AND role_code = 'ADMIN'
) > 0;
Apply this scheme to the Admin menu item, admin-only pages, and any buttons that perform privileged operations. Non-admin users see a clean interface without any hint that restricted features exist.
Also set a Security → Authorization on pages that contain sensitive data. A user who types a page URL directly without the right authorization will be redirected or shown an error, not the data.
4. Prevent SQL Injection
APEX largely protects you from SQL injection because it uses bind variables for page items automatically. However, you can still introduce vulnerabilities through careless dynamic SQL:
-- DANGEROUS: User input concatenated into SQL
v_sql := 'SELECT * FROM employees WHERE last_name = ''' || :P1_NAME || '''';
-- SAFE: Bind variable (APEX resolves these automatically in query regions)
SELECT * FROM employees WHERE last_name = :P1_NAME
-- SAFE: If dynamic SQL is necessary, use DBMS_SQL or EXECUTE IMMEDIATE with binds
EXECUTE IMMEDIATE 'SELECT count(*) FROM employees WHERE dept_id = :1'
INTO v_count
USING p_dept_id; -- Never concatenate user input
Key rules:
- Always use bind variables — never concatenate user input into SQL strings
- If you must use dynamic SQL, parameterize it with USING clause
- Validate and sanitize any input that will be used in dynamic SQL (whitelist valid values, use DBMS_ASSERT for object names)
5. Prevent Cross-Site Scripting (XSS)
XSS happens when user-supplied content is rendered as HTML in the browser without escaping. An attacker can inject JavaScript that steals session tokens or performs actions on behalf of the victim.
APEX escapes output by default in most contexts, but you need to be aware of where you may disable this:
- Report columns with Escape Special Characters = No — Only disable this for columns you fully control (not user input)
- HTML regions that include
&ITEM_NAME.substitutions — Escaped by default, but verify - JavaScript that writes user data to the DOM — Always use
apex.util.escapeHTML()when inserting dynamic content
// Safe: Escaped before inserting into DOM
var userName = apex.util.escapeHTML($v('P1_USERNAME'));
document.getElementById('greeting').innerHTML = 'Hello, ' + userName;
// Safer: Use text content property instead of innerHTML
document.getElementById('greeting').textContent = 'Hello, ' + $v('P1_USERNAME');
6. Configure Security Settings in Application Properties
In Application Builder → Edit Application Properties → Security, review and configure:
- Browser Security → HTTP Response Headers: Enable security headers including X-Frame-Options (prevents clickjacking), X-Content-Type-Options, and Content Security Policy
- Maximum Session Idle Time: Set an appropriate timeout (30–60 minutes for business apps)
- Maximum Session Duration: Absolute session limit (8–12 hours typical)
- Session Management → Session Shared Components: Consider disabling if sharing sessions between apps is not needed
- Allow Public Access: Should be No for any application with sensitive data
7. Apply the Principle of Least Privilege to the Database User
The database schema that APEX connects to should have only the permissions it needs — nothing more:
-- Create a dedicated APEX application schema
CREATE USER apex_app_user IDENTIFIED BY your_secure_password;
-- Grant only what is needed
GRANT CREATE SESSION TO apex_app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON app_schema.orders TO apex_app_user;
GRANT SELECT ON app_schema.employees TO apex_app_user;
-- NOT: GRANT DBA TO apex_app_user (never do this)
-- Use synonyms so the app user does not need schema prefix in SQL
CREATE SYNONYM apex_app_user.orders FOR app_schema.orders;
If the APEX application user is compromised, limiting its database privileges limits the blast radius of the attack.
8. Audit Sensitive Operations
For applications handling sensitive data, log user actions in an audit table. Who viewed what, who changed what, and when:
-- Audit trigger on sensitive table
CREATE OR REPLACE TRIGGER trg_salary_audit
AFTER UPDATE OF salary ON employees
FOR EACH ROW
BEGIN
INSERT INTO salary_audit_log (
audit_date, changed_by, employee_id,
old_salary, new_salary, apex_session
) VALUES (
SYSDATE,
NVL(SYS_CONTEXT('APEX$SESSION', 'APP_USER'), USER),
:NEW.employee_id,
:OLD.salary,
:NEW.salary,
SYS_CONTEXT('APEX$SESSION', 'SESSION_ID')
);
END;
9. Protect Against CSRF
Cross-Site Request Forgery (CSRF) tricks a logged-in user’s browser into making unauthorized requests. APEX has built-in CSRF protection for forms — the Duplicate Submission feature generates and validates a unique token on each form submission.
Ensure that all pages with forms have Duplicate Page Submission set to Prevent duplicate page submissions in the page properties. For custom AJAX calls using the APEX JavaScript API, use apex.util.getRequestData which includes CSRF tokens automatically.
Security Checklist for Every APEX Application
- HTTPS enforced on all environments
- Session State Protection enabled
- Authorization schemes on all sensitive pages and components
- No SQL injection vectors (no concatenated user input in SQL)
- XSS-safe output (Escape Special Characters = Yes on user-generated content columns)
- Reasonable session timeout configured
- Database user has minimal required privileges
- Audit logging for sensitive operations
- HTTP security headers configured
- Duplicate submission protection enabled on forms
Conclusion
Security in Oracle APEX is not complicated if you build it in from the start. The platform provides excellent built-in mechanisms — Session State Protection, Authorization Schemes, secure session management — that handle the heavy lifting. Your job is to configure them correctly and remain vigilant about the handful of areas where custom code can introduce vulnerabilities.
Treat the security checklist above as a requirement for every application before it goes to production. Once you build the habit, these practices become automatic rather than afterthoughts.