Unifying Identity and Data Security: Real Application Security with Active Directory
It’s no surprise that I’m a big fan of the Real Application Security (RAS) feature in the Oracle Database. RAS makes it possible to enforce data access policies based on the business role of your end users.
But here’s the catch: business roles are rarely stored inside the database. Instead, they usually live in a central directory such as Active Directory. And if you’ve worked with RAS before, you know there’s no out-of-the-box integration between Application Users & Roles and external directories.
That doesn’t mean the two worlds can’t be connected. In this article, we’ll explore how to bridge identity from Active Directory into Oracle RAS so that row- and column-level filtering can be driven directly by AD group membership.
We’ll walk through how to use Kerberos for authentication and CMU + Active Directory for authorization, allowing AD group membership to drive RAS policies inside the database.
Where is the Bridge Between the Two Worlds ?
Let’s be honest — I had been using Real Application Security extensively for several years without noticing this tiny detail in the documentation. And as we all know, the devil is in the details…
In the specification of the XS_ACL package, you’ll find some constants that are defined:
Looking further at the constructor of ACE_TYPE, we can observe that, by default (unless explicitly overwritten), the principal type defaults to XS — which stands for Real Application Security type (Application Role or Application User).
So, when you set up your ACL, if you follow the examples in the documentation, you will typically define your entries in that ACL by specifying Application Roles or Application Users (created earlier with XS_PRINCIPAL):
But here’s the key insight: according to the documentation, we can also link a principal of type DB. In other words, a classical Database Role or Database User.
So What ?
Up to this point — nothing really fancy. It works, but we’re still not integrated with a central directory.
But what if we leverage a feature that’s been around for more than 20 years: globally identified roles?
Instead of defining the role locally in the database, we can map it directly to a group in Active Directory.
Here’s what happens: when a globally identified user logs in, the database role is automatically activated if that user is a member of the associated group in Active Directory.
By using these globally identified roles inside an ACL configuration, we can now enforce fine-grained access control — row-level and column-level security — driven directly by Active Directory group membership!
Let’s Build the Configuration and Demo !
For this short demo, I’m using a 23ai database that has already been configured with CMU + Kerberos. Since those steps are outside the scope of this article, I won’t cover them here.
First, I’ll create a simple user HR in my PDB and add a demo table that I want to secure:
-- Creation of the base schema in which I will create a table to be protected
CREATE USER HR IDENTIFIED BY "xxxxxxxxxxx";
GRANT CONNECT, RESOURCE TO HR;
ALTER USER HR QUOTA UNLIMITED ON USERS;
ALTER USER HR DEFAULT TABLESPACE USERS;
-- Creation of a POLICY_ADMIN user that will be in charge of managing RAS config
exec sys.xs_principal.create_user(name => 'POLICY_ADMIN', schema => 'HR');
exec sys.xs_principal.set_password('POLICY_ADMIN', 'xxxxxxxx');
exec sys.xs_principal.grant_roles('POLICY_ADMIN', 'XSCONNECT');
EXEC SYS.XS_ADMIN_UTIL.GRANT_SYSTEM_PRIVILEGE('ADMIN_SEC_POLICY','POLICY_ADMIN', SYS.XS_ADMIN_UTIL.PTYPE_XS, 'HR');
EXEC SYS.XS_ADMIN_UTIL.GRANT_SYSTEM_PRIVILEGE('APPLY_SEC_POLICY','POLICY_ADMIN', SYS.XS_ADMIN_UTIL.PTYPE_XS, 'HR');
-- The below is needed to administer namespace
EXEC SYS.XS_ADMIN_UTIL.GRANT_SYSTEM_PRIVILEGE('ADMIN_ANY_SEC_POLICY','POLICY_ADMIN',SYS.XS_ADMIN_UTIL.PTYPE_XS);
-- Needed to creae user / role
EXEC SYS.XS_ADMIN_UTIL.GRANT_SYSTEM_PRIVILEGE('PROVISION','POLICY_ADMIN',SYS.XS_ADMIN_UTIL.PTYPE_XS);
-- Creation of my demo table
Create table HR.EMPLOYEES
(
FIRST_NAME VARCHAR2(20),
LAST_NAME VARCHAR2(25),
EMAIL VARCHAR2(25),
SALARY NUMBER(8,2)
);
-- Load sample d
Insert into HR.EMPLOYEES values('John','Smith','john.smith@example.com',50000);
Insert into HR.EMPLOYEES values('Jane','Doe','jane.doe@example.com',60000);
Insert into HR.EMPLOYEES values('Alice','Johnson','alice.johnson@example.com',70000);
Commit;Next, I’ll define my globally identified roles and map them to Active Directory groups:
-- I obfuscated the VCN Number
create role DBR_SENSITIVE_HR_ROLE identified globally
as 'CN=hr_sensitive_access_group,CN=Users,DC=subnet0xxxxxxx,DC=vcn0xxxxxxx,DC=oraclevcn,DC=com';
create role DBR_PUBLIC_HR_ROLE identified globally
as 'CN=hr_public_access_group,CN=Users,DC=subnet0xxxxxxx,DC=vcn0xxxxxxx,DC=oraclevcn,DC=com'; So far, so good. Now let’s add some Real Application Security policies to control access:
- Users with DBR_PUBLIC_HR_ROLE will not see the salary column.
- Users with DBR_SENSITIVE_HR_ROLE will see it.
-- Creation of a security class :
begin
sys.xs_security_class.create_security_class(
name => 'HR_SEC_CLASS',
description => 'Security Class to protect HR demo table',
parent_list => XS$NAME_LIST('SYS.DML'),
priv_list => xs$privilege_list(xs$privilege('view_employee_details')));
end;
/
-- Creation of the ACL & mapping of the previously created roles :
declare
aces xs$ace_list := xs$ace_list();
begin
aces.extend(2);
aces(1) := xs$ace_type(privilege_list => xs$name_list
('select'),
principal_name => 'DBR_PUBLIC_HR_ROLE',
principal_type => xs_acl.ptype_db);
aces(2) := xs$ace_type(privilege_list => xs$name_list
('select','view_employee_details'),
principal_name => 'DBR_SENSITIVE_HR_ROLE',
principal_type => xs_acl.ptype_db);
sys.xs_acl.create_acl(name => 'HR_SENSITIVE_DATA_ACL',
ace_list => aces,
sec_class => 'HR_SEC_CLASS');
end;
/
-- Creation of a policy
declare
realms xs$realm_constraint_list := xs$realm_constraint_list();
cols xs$column_constraint_list := xs$column_constraint_list();
begin
realms.extend(1);
realms(1) := xs$realm_constraint_type(
realm=> '1 = 1',acl_list => xs$name_list('HR_SENSITIVE_DATA_ACL'));
cols.extend(1);
cols(1) := xs$column_constraint_type
(column_list => xs$list('SALARY'),
privilege => 'view_employee_details');
sys.xs_data_security.create_policy(
name => 'PROTECT_EMPLOYEES',
realm_constraint_list => realms,
column_constraint_list => cols);
end;
/
-- Applying the policy
begin
XS_DATA_SECURITY.apply_object_policy(schema=>'HR', object=>'EMPLOYEES',
policy=>'PROTECT_EMPLOYEES',statement_types=>'SELECT');
end;
/Let’s test the result. First, I connect with tminne, who is a member of the AD group hr_public_access_group :
Now, let’s switch the same user tminne in Active Directory to the group hr_sensitive_access_group and test again:
This time, the salary column becomes visible — showing how RAS policies are dynamically enforced based on Active Directory group membership.
Conclusion
What we’ve seen in this demo is that Real Application Security doesn’t have to live in isolation from your central directory.
By combining:
- Kerberos for secure authentication (verifying the user’s identity)
- Centrally Managed Users (CMU) and Active Directory for authorization (user / group membership mapped to database principals)
- Globally identified roles mapped to AD groups
- and RAS policies for enforcement
The result is a security model where row- and column-level access is driven by enterprise identity and authorization, not by duplicated or manually synced roles.
Instead of reimplementing authorization in the database, you use the existing AD group model and let CMU + globally identified roles translate that into database principals that RAS can enforce.
This approach creates a cleaner, more scalable, and identity-driven way to protect sensitive data — with Kerberos ensuring the user is who they say they are, and CMU + AD providing the authorization context that RAS enforces inside the DB.
Some useful resources
Configuring Centrally Managed Users with Microsoft Active Directory
RAS documentation :
