Advanced: Implement a Security Context Provider

If your authentication and authorization workflow requires custom logic, or if there’s no out-of-the-box (OOTB) security context provider available for your application framework, you can build your own provider.

A custom provider bridges your application’s security context and the Oracle client driver, enabling automatic propagation of the end-user security context payload to the database without modifying your application code.

The instructions in this section are primarily for the Oracle JDBC driver and Java-based application frameworks. If you are using a different client driver (such as python-oracledb or ODP.NET), apply the same principles using the equivalent provider interface and registration mechanism in your driver. See the respective driver documentation for API details.

Topics:

Understand the Security Context Payload

Before building a provider, understand the components of the end-user security context payload that the database expects. Every payload must include the following mandatory components and may include optional components.

Understand the Driver’s Provider Interface

To propagate the end-user security context payload, the Oracle JDBC driver defines a Service Provider Interface (SPI) named EndUserSecurityContextProvider. Your custom provider must implement this interface and its getEndUserSecurityContext() method.

The JDBC driver invokes getEndUserSecurityContext() before every database operation on the connection. Your implementation must return an EndUserSecurityContext object containing information that identifies the end user or application, the database-access token, and any optional data roles or end-user context attributes. In a single-token flow, the database-access token supplies the identity information; a separate end-user token is not required. The driver piggybacks this payload to the database on the next round-trip. If the method returns null, the driver proceeds without attaching a security context payload.

Note: For other client drivers, use the equivalent provider interface. For example, the python-oracledb driver’s end_user_sec_provider plug-in uses a configuration-driven mechanism with middleware hooks. See the respective driver documentation for the specific interface contract.

Build the Security Context Provider

Your provider implementation must perform three tasks each time the driver calls getEndUserSecurityContext(). The following steps describe these tasks for a JDBC provider. Adapt the approach to your client driver as needed.

  1. Extract the end-user identity from your framework’s security context when required for the selected flow.

    Many application frameworks maintain a request-scoped or thread-local security context that holds the authenticated user’s identity after the framework’s authentication filter has processed the request. Your provider must read the end-user identity from this context. Examples of framework security context mechanisms:

    • Jakarta EE / Servlet containers: HttpServletRequest.getUserPrincipal() or the JAX-RS SecurityContext

    • Helidon: io.helidon.security.SecurityContext

    Other frameworks provide equivalent mechanisms. Consult your framework’s security documentation. Extract either the raw OAuth 2.0 access token (for IAM-managed users) or the user name and lookup key (for users managed in the application’s own user store). If your framework stores the token as a parsed object (such as a JWT), you may need to retrieve the original serialized token string as the database expects the raw token.

  2. Acquire the database-access token from your IAM.

    Your provider must obtain a token that authorizes the application to access the database resource. This token is separate from the end user’s token, when one is used. Use an OAuth 2.0 client library available in your framework or ecosystem to request the token from your IAM’s token endpoint. The specific library depends on your environment:

    • Microsoft Entra ID: Use MSAL for Java (com.microsoft.azure:msal4j) for both client credentials and OBO flows.

    • OCI IAM: Use the OCI SDK for Java or a standard HTTP client to call the OCI IAM token endpoint with client credentials.

    When requesting the token, use the client ID, client secret, and scopes from your application’s IAM registration. Database-access tokens are typically valid for a limited duration (for example, one hour). Cache the token and reuse it until it nears expiry to avoid requesting a new token on every database call. For OBO tokens, use the end-user token as the cache key, because each user produces a distinct OBO token.

  3. Construct and return the EndUserSecurityContext.

    Use the JDBC driver’s builder methods to assemble the security context object from the components gathered in previous steps.

    For IAM-managed users (two-token payload):

    EndUserSecurityContext securityContext =
        EndUserSecurityContext.createWithToken(
            databaseAccessToken, endUserToken)
          .withDataRoles(Set.of(dataRoles))                // optional
          .withAttributes(contextAttributes);              // optional
    return securityContext;

    For a single-token payload, pass only the database-access token to createWithToken. This requires Oracle JDBC 23.26.0.0.4 (October RU) or later. In this example, databaseAccessToken is a char[] containing the token obtained in step 2.

    EndUserSecurityContext securityContext;
    try {
        securityContext = EndUserSecurityContext.createWithToken(
            java.nio.CharBuffer.wrap(databaseAccessToken));
    } finally {
        java.util.Arrays.fill(databaseAccessToken, (char) 0);
    }
    return securityContext;

    For users managed in the application’s own user store (user name and lookup key):

    EndUserSecurityContext securityContext =
        EndUserSecurityContext.createWithName(
            databaseAccessToken, endUserName, key)
          .withAttributes(contextAttributes);              // optional
    return securityContext;

    If the current request does not require an end-user security context, return null. The driver proceeds without attaching a security context payload, and the connection operates with its default privileges.

Register the Provider with the Driver

The JDBC driver discovers your provider through the standard Java ServiceLoader mechanism.

To register your provider:

  1. Create a file named oracle.jdbc.spi.EndUserSecurityContextProvider in your provider JAR’s META-INF/services/ directory.

  2. Add a single line containing the fully qualified class name of your provider implementation. For example:

    # META-INF/services/oracle.jdbc.spi.EndUserSecurityContextProvider
    com.example.myapp.MySecurityContextProvider

    When a new JDBC connection is created (that is, when java.sql.Driver.connect(String, Properties) is called), the driver scans the class path for implementations of EndUserSecurityContextProvider and loads the one matching the provider name configured in the connection properties.

Configure the Driver to Use Your Provider

Set the oracle.jdbc.provider.endUserSecurityContext connection property to the name of your provider.

This name is the value returned by your provider’s getName() method. Set this property in your connection pool’s data source configuration.

For example, in a HikariCP-based configuration:

# Activate the custom provider
spring.datasource.hikari.data-source-properties.\
  oracle.jdbc.provider.endUserSecurityContext = my-custom-provider

Or programmatically when configuring the data source:

Properties props = new Properties();
props.setProperty(
    "oracle.jdbc.provider.endUserSecurityContext",
    "my-custom-provider"
);
dataSource.setDataSourceProperties(props);

Support Privilege Elevation (Optional)

Some application operations require temporary elevated privileges, for example, reading all employees’ salary data to generate a summary report. To support this, your provider can accept additional data roles injected at runtime for the duration of a specific code block.

The recommended approach is to pass elevated data roles through your framework’s existing security context mechanism, keeping the provider decoupled from application code. The OOTB Spring Boot provider uses this pattern:

To implement this pattern in your framework, use the equivalent of a method interceptor or decorator that wraps the target method, augments the security context with additional data roles, invokes the method, and restores the original context in a finally block.

For the Spring Boot reference implementation of this pattern (using GrantedAuthority and the @RunWithDataRoles annotation), see the Oracle JDBC Extensions source code on GitHub.

Note for Other Client Drivers

If your application uses python-oracledb or ODP.NET instead of JDBC, the same architectural pattern applies: implement a component that extracts the end-user identity, acquires the database-access token, constructs the security context payload, and hooks into the driver’s connection life cycle. The specific interfaces, registration mechanisms, and configuration properties differ by driver.

For the python-oracledb driver, see the python-oracledb documentation.

For the node-oracledb driver, see the node-oracledb documentation.

For the ODP.NET driver, see the Oracle Data Provider for .NET documentation.