10.5 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.

10.5.1 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.

  • End-user identity (mandatory): The end user's name as asserted in the IAM access token. For end users managed locally, this is the name of the end user created in the database.
  • Database access token (mandatory): Authorizes the application’s connection to the database. Obtain this token from your IAM system using one of the following flows:
    • Client credentials flow: The application authenticates as itself.
    • On-behalf-of (OBO) flow: The application exchanges the end-user’s token for a database-access token.
  • Data roles (optional): A list of additional data roles that the application can enable for the end-user security context, beyond those mapped to application roles in IAM and those enabled by default for the application identity.
  • Context attributes (optional): A dictionary of application-defined key-value pairs to include in the security context. Used when application logic or data grants rely on custom end-user context attributes.

10.5.2 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 the end-user identity, database-access token, and any optional data roles or attributes. 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.

10.5.3 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.
    Every application framework maintains 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.
    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 (token-based identity):
    EndUserSecurityContext securityContext =
        EndUserSecurityContext.createWithToken(
            databaseAccessToken, endUserToken)
          .withDataRoles(Set.of(dataRoles))                // optional
          .withAttributes(contextAttributes);              // optional
    return securityContext;
    
    For users managed in the application's own user store (user name and lookup key):
    EndUserSecurityContext securityContext =
        EndUserSecurityContext.createWithUsername(
            databaseAccessToken, endUserName, key)
          .withAttributes(contextAttributes);              // optional
    return securityContext;
    
    If no end-user identity is available for the current request (for example, a health-check endpoint that does not authenticate users), return null. The driver proceeds without attaching a security context payload, and the connection operates with its default privileges.

10.5.4 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.

10.5.5 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);

10.5.6 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:
  • Application code temporarily adds data roles (with a distinguishing prefix) to the framework’s security context before the database call.
  • The provider reads the prefixed roles from the security context and includes them in the EndUserSecurityContext payload.
  • After the method returns, the application restores the original security context.

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.

10.5.7 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 ODP.NET driver, see the Oracle Data Provider for .NET documentation.