5.9 Build, Run, and Verify

Compile the application, start the Spring Boot server, authenticate an end user through Microsoft Entra ID, and verify that the data grants correctly enforce fine-grained data access control and privilege elevation.

For this section, you'll need two separate terminal sessions:
  • Terminal 1 (build and run): Use to compile the project, load environment variables, and start the Spring Boot application. This terminal remains occupied while the application runs.
  • Terminal 2 (test): Use to generate Proof Key for Code Exchange (PKCE) values, obtain Entra ID access tokens, and execute curl requests against the running API.

5.9.1 Build the Application

Configure your Java and Maven environments, then compile the parent project, the Spring provider module, and the sample API into an executable JAR. Perform these tasks in Terminal 1.

  1. Set the active JDK.
    Ensure Java 17 is your active JDK (if it isn't already your system default).
    export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
    export PATH=$JAVA_HOME/bin:$PATH
  2. Set up Maven.
    If Maven is not on your system path, point MAVEN_HOME to your installation directory. (If mvn -version already works, you can skip this step.)
    export MAVEN_HOME=/opt/maven/apache-maven-3.9.x
    export PATH=$MAVEN_HOME/bin:$PATH
    
    # Verify installation (requires Apache Maven 3.9.x or later)
    mvn -version
  3. Build the project.
    Build the project components in the following order. Starting from the repository root, compile the parent project, the Spring provider module, and finally the sample application.
    # a. Build the parent project
    cd $HOME/ojdbc-extensions
    mvn -DskipTests clean install
    
    # b. Build the Spring provider module
    cd $HOME/ojdbc-extensions/ojdbc-provider-spring
    mvn -DskipTests clean install
    
    # c. Build the sample application
    cd APP_DIR
    mvn -DskipTests clean package

Note:

A successful build ends with BUILD SUCCESS. The executable JAR is located at target/employee-records-api-0.0.1-SNAPSHOT.jar.

5.9.2 Run the Application

Continuing in Terminal 1, source your environment variables and launch the Spring Boot application.

  1. Load environment variables.
    Ensure that you are in the application directory, then source your .env file.
    cd APP_DIR
    set -a && source .env && set +a
  2. Start the Spring Boot application.
    Run the application using the Maven Spring Boot plug-in.
    mvn -DskipTests spring-boot:run
    Leave this terminal running. Wait for the console to display Started EmployeeRecordsApiApplication before continuing with the next steps.

Note:

If your host requires an HTTP proxy to reach Entra ID, append the following Java Virtual Machine (JVM) arguments to the run command:
mvn -DskipTests spring-boot:run \
    -Dspring-boot.run.jvmArguments="\
    -Dhttps.proxyHost=<proxy-host> \
    -Dhttps.proxyPort=<port> \
    -Dhttp.proxyHost=<proxy-host> \
    -Dhttp.proxyPort=<port> \
    -Dhttp.nonProxyHosts=localhost"

5.9.3 Get an Access Token

Switching to Terminal 2, execute the Authorization Code with PKCE flow to simulate a user login, ultimately exchanging an Entra ID authorization code for a JWT access token.

  1. Create the PKCE helper script.
    Navigate to the application directory and create a utility script. You can use this script to easily generate fresh PKCE verifier and challenge codes each time you need to fetch an access token.
    cd APP_DIR
    
    cat > generate_pkce.sh << 'EOF'
    #!/bin/bash
    CODE_VERIFIER=$(openssl rand -base64 64 | tr -d '=+/' | tr -d '\n' | cut -c1-64)
    CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=')
    
    # Update .env file in place
    sed -i "s|CODE_VERIFIER=.*|CODE_VERIFIER='$CODE_VERIFIER'|" .env
    sed -i "s|CODE_CHALLENGE=.*|CODE_CHALLENGE='$CODE_CHALLENGE'|" .env
    
    echo "Generated and updated .env:"
    echo "CODE_VERIFIER=$CODE_VERIFIER"
    echo "CODE_CHALLENGE=$CODE_CHALLENGE"
    EOF
    
    chmod +x generate_pkce.sh
  2. Run the script and load variables.
    Execute the script to update your .env file with verifier and challenge codes, then source the file to load the new variables into your terminal session.
    ./generate_pkce.sh
    set -a && source .env && set +a
  3. Generate the authorization URL.
    Construct the Entra ID login URL and print it to the console.
    AUTH_URL="${HRAPP_AUTH_URI}?client_id=${HRAPP_CLIENT_ID}"
    AUTH_URL+="&response_type=code&redirect_uri=${REDIRECT_URI}"
    AUTH_URL+="&scope=${HRAPP_DELEGATED_SCOPE}"
    AUTH_URL+="&response_mode=query&prompt=consent"
    AUTH_URL+="&code_challenge=${CODE_CHALLENGE}&code_challenge_method=S256"
    
    echo -e "\nOpen this URL in your browser:\n$AUTH_URL\n"
  4. Authenticate in the browser.
    1. Copy the generated URL and open it in a web browser.
    2. Sign in as your test user (Emma).
    3. After authenticating, your browser redirects to http://localhost:3000?code=<AUTH_CODE>. It is normal for this page to fail to load.
    4. From the URL bar, copy the authorization code (everything after code= and before the next &).
  5. Exchange the code for an access token.
    Use curl to swap the authorization code for a JWT access token. In the command below, replace <AUTH_CODE_FROM_REDIRECT> with the code you copied.
    curl -s -X POST "$HRAPP_TOKEN_URI" \
        -H "Content-Type: application/x-www-form-urlencoded" \
        --data-urlencode "grant_type=authorization_code" \
        --data-urlencode "client_id=$HRAPP_CLIENT_ID" \
        --data-urlencode "client_secret=$HRAPP_CLIENT_SECRET" \
        --data-urlencode "code=<AUTH_CODE_FROM_REDIRECT>" \
        --data-urlencode "redirect_uri=$REDIRECT_URI" \
        --data-urlencode "code_verifier=$CODE_VERIFIER" \
        --data-urlencode "scope=$HRAPP_DELEGATED_SCOPE"
    In the JSON response, find the access_token value and save it.

5.9.4 Verify Employee Access

In Terminal 2, call the /api/employees endpoint using Emma's access token you obtained previously.

curl -s --http1.1 -X GET "http://localhost:8080/api/employees" \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Accept: application/json" | python3 -m json.tool
Emma's access token carries EMPLOYEE in its roles claim, which activates the EMPLOYEE_ROLE data role in the database. The EMPLOYEES_OWN_RECORD data grant on this data role ensures only Emma's record is returned.
[
    {
        "id": 400,
        "name": "Emma Baker",
        "salary": 8200,
        "phone": "555-0400"
    }
]

5.9.5 Verify Privilege Elevation

Now, use the same access token and call the /api/employees/salary-summary endpoint. This demonstrates how the application temporarily elevates Emma's database privileges, allowing her to view aggregate salary statistics without exposing individual employee salaries.

curl -s --http1.1 -X GET "http://localhost:8080/api/employees/salary-summary" \
    -H "Authorization: Bearer $ACCESS_TOKEN" \
    -H "Accept: application/json" | python3 -m json.tool
Emma's token still carries only the EMPLOYEE role claim, but for this endpoint the application temporarily activates the COMPENSATION_ANALYST data role. The corresponding data grant (EMPLOYEES_SALARY_SUMMARY) allows access to aggregate salary data across all employees, without exposing individual records:
{
    "minSalary": 6900,
    "maxSalary": 13000,
    "averageSalary": 9826.00,
    "employeeCount": 5
}

What happens behind the scenes

  1. The application invokes the getSalarySummary() method, which is tagged with the @RunWithDataRoles(dataRoles = {"COMPENSATION_ANALYST"}) annotation.
  2. A Spring AOP interceptor (RunWithDataRolesAopConfig.java) catches this request and temporarily adds the compensation_analyst data role to Emma’s end-user security context.
  3. The database activates the hr.employees_salary_summary data grant, permitting the aggregate salary query.
  4. Emma receives the minimum salary, maximum salary, average salary, and employee count of her team; however, individual salaries remain hidden.
  5. After the method returns, the privilege elevation is immediately deactivated. Emma's security context reverts to her standard EMPLOYEE_ROLE data role.

This temporary privilege elevation is scoped strictly to the execution of that specific method, controlled by the application, and securely enforced at the database level.