Skip to content

Microsoft Graph Proxy for PowerShell Clients

Introduction

In enterprise environments, managing and securing API keys, application secrets, and access tokens is critical to preventing credential sprawl and unauthorized data exposure. The 12Port HTTP Proxy for Microsoft Graph introduces identity-aware HTTP interception capabilities. This feature forces all Microsoft Graph API calls through the PAM gateway, ensuring that sensitive application secrets are centralized, managed securely, and never exposed to client workstations or scripts, enabling secure, Zero Trust access to Microsoft Graph APIs for managing Microsoft Entra ID, Azure, and Microsoft 365 environments.


Technical Overview

The 12Port HTTP Proxy operates as a transparent proxy for incoming graph.microsoft.com traffic. When a client application initiates a request, the proxy performs TLS termination, enforces access control policies, dynamically injects OAuth access tokens, and securely forwards the authenticated payload to the Microsoft Graph endpoint.

Request and Traffic Flow

  1. Proxy Redirection & NTLM Authentication: The Windows client redirects its HTTP/HTTPS traffic to the 12Port HTTP Proxy using a .NET WebProxy configuration. The client authenticates via NTLM using a structured routing username formatting convention: [user]#[target-asset-name] (e.g., user#graph).
  2. Principal & Authorization Resolution: The proxy interceptor processes the NTLM credentials, maps the connection to an authorized 12Port user, and evaluates role-based access control (RBAC) permissions against the requested target asset.
  3. TLS Interception: The proxy establishes a TLS session with the client using a dynamically generated leaf certificate signed by its internal, user-trusted root CA.
  4. Token Swapping and Injection: The proxy captures the transmission, strips out the client's placeholder Bearer token, fetches a valid Entra ID access token from Microsoft using the application client secret securely stored in the gateway, and injects the live token into the upstream request payload.
  5. Upstream Forwarding & Auditing: The proxy forwards the request to https://graph.microsoft.com. The response is channeled back through the proxy to the caller, establishing a fully audited and logged session.

Use Cases

  • Elimination of Secret Sprawl: Prevents developers, administrators, and automated scripts from storing Entra ID Client Secrets locally on workstations, in configuration files, or within source control.
  • Centralized API Auditing: Audits and streams all enterprise Microsoft Graph API requests through a single gateway for compliance, logging, and security evaluation.
  • Granular Role-Based Access: Restricts access to Microsoft Graph endpoints at the gateway level. Even if an application registration has broad tenant-wide permissions, access can be restricted to specific authorized 12Port users and groups.

Before You Start

Before starting configuration, verify that the following environment criteria are met:

Requirement Details
12Port HTTP Proxy Status Enabled under Configuration -> HTTP Proxy Configuration with a valid key pair generated.
Entra ID Application Registration An active App Registration containing the Tenant ID, Client ID, and an active Client Secret configured with appropriate application permissions (e.g., User.Read.All).
Windows Client Environment PowerShell 5+ running with the Microsoft.Graph module installed. The user must possess elevated administrative permissions locally to modify the machine's Trusted Root Certification Authorities store.
Network Topology Unhindered network reachability from the client host to the proxy listener IP and port (Default example: 8800).

Configuration & Implementation Steps

Step 1: Download the Proxy Root CA Certificate

To perform TLS interception seamlessly, the client must trust the gateway's certificate chain.

  1. Authenticate to the 12Port Web Console with a Site Administrator account.
  2. Navigate to Configuration -> HTTP Proxy Configuration.
  3. Verify that the proxy status is Enabled and a valid cryptographic key pair is assigned.
  4. Click Download CA Certificate and choose the appropriate format (CER is recommended for most Windows deployments).
  5. Save the generated public certificate to your local client path (e.g., %USERPROFILE%\Documents\proxy.cer).

Note: The download payload contains only the public certificate component. The private key remains secure and never leaves the 12Port gateway.

Step 2: Establish the MS Graph Endpoint Asset

Create the Target Asset registry within the 12Port Asset Database to securely isolate the Entra ID application parameters.

  1. Navigate to the Assets database and add a new asset of type MS Graph Endpoint. Note this is a hidden, by default, asset type so it needs to be unhidden first from the Management > Asset Types page.
  2. Configure the resource using the parameters defined below:
Field Configuration Value
Name A unique, shorthand routing identifier used by the proxy client login (e.g., graph).
Tenant Id The directory / Tenant GUID of your Entra ID instance.
Client Id The Application / Client GUID of your target App Registration.
Client Secret The plaintext application secret. (Stored encrypted securely; locked post-save).
URL https://graph.microsoft.com
Authentication Type Secret

3. Assign Access Control: Assign target users an explicit asset permission role within 12Port (Asset Viewer permission level or higher) to grant access rights through the proxy.

Routing Logic Example: When a client establishes an authentication context targeting user#graph, the gateway uses the suffix tag graph to dynamically match the configuration asset Name designated in this step.

Step 3: Provision the CA Certificate onto the Client Workstation

Import the downloaded root CA into the local machine's system configuration to allow the .NET runtime environment and Microsoft Graph SDK to trust the proxy's runtime leaf certificates.

Open an elevated administrative PowerShell console on the target client and execute the following commands:

# Import the public root CA into the local machine's Trusted Root Certificate store
certutil -addstore -f Root "$env:USERPROFILE\Documents\proxy.cer"

# Verify successful installation by looking for the designated proxy identifier string using the "TenantName" of the certificate
certutil -store Root | Select-String "<TenantName>"

Warning: Trusting this root CA gives the proxy the technical capability to intercept TLS contexts matching its designated routing rules on this system. Deploy this certificate strictly on managed corporate workstations destined for proxied graph paths.

Step 4: Establish Proxy Connection via Microsoft Graph PowerShell SDK

Use the following script example framework to route your PowerShell Graph SDK sessions through the proxy. This script configures a global .NET proxy context, passes NTLM credentials, and initiates an SDK runtime context with a placeholder token.

  • Replace $proxyUrl with your 12Port Host and HTTP Proxy Port Number (line 4 - "http://{pam.contoso.com}:8800")
  • Replace $proxuAuth -UserName with your 12Port User and Target Asset Name (line 8 - "12Port-User#Asset-Name")
# ==========================================
# 1. ENVIRONMENT & PROXY CONFIGURATION
# ==========================================
$proxyUrl   = "http://{pam.contoso.com}:8800"

# Prompt the administrator for local proxy credentials using standard secure string prompts
# Note the syntax value formatting: user#asset_name
$proxyAuth  = Get-Credential -UserName "12Port-User#Asset-Name" -Message "Enter credentials for the NTLM Proxy Server ($proxyUrl)"

Write-Host "Configuring session-wide proxy settings with explicit credentials..." -ForegroundColor Cyan

# Instantiate and configure the global .NET WebProxy payload object
$proxy = New-Object System.Net.WebProxy($proxyUrl)
$proxy.UseDefaultCredentials = $false  # Explicitly prevent automatic Windows fallback login
$proxy.Credentials = $proxyAuth        # Inject proxy authentication object

# Register proxy settings into global WebRequest context for background SDK engine ingestion
[System.Net.WebRequest]::DefaultWebProxy = $proxy

# Enforce secure modern TLS compliance
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13

# ==========================================
# 2. CONNECT TO MICROSOFT GRAPH SDK
# ==========================================
# Ensure the authentication module is loaded into the session
Import-Module Microsoft.Graph.Authentication -ErrorAction SilentlyContinue

# Disconnect cleanly (this resets the internal context natively)
Disconnect-MgGraph -ErrorAction SilentlyContinue

# Proceed with token injection
$rawToken = "fake_jwt_token" # Replace with real token injection mechanism if required
$secureToken = ConvertTo-SecureString -String $rawToken -AsPlainText -Force
Connect-MgGraph -AccessToken $secureToken -Environment Global

# ==========================================
# 3. EXECUTE GRAPH QUERY
# ==========================================
Write-Host "Fetching users from Graph API..." -ForegroundColor Cyan
try {
    # Execute standard Graph commands normally
    Get-MgUser -Top 5 | Select-Object DisplayName, Mail
}
catch {
    Write-Host "=== Get-MgUser failed - exception chain ===" -ForegroundColor Red
    $e = $_.Exception
    while ($e) {
        Write-Host ($e.GetType().FullName + " :: " + $e.Message) -ForegroundColor Yellow
        $e = $e.InnerException
    }
}

Functional Code Analysis

  • $proxyUrl = "http://...:8800": Points to the internal 12Port proxy network listener interface.

  • Get-Credential -UserName "12Port-User#Asset-Name": Generates the NTLM identity verification challenge. The #Asset-Name suffix defines the target asset created in Step 2.

  • [System.Net.WebRequest]::DefaultWebProxy = $proxy: Binds the global .NET network sub-layer, forcing downstream HTTP commands issued by the Graph SDK engine to route directly through the proxy listener.

  • Connect-MgGraph -AccessToken $secureToken: Forces the local Graph SDK module instance into passing structural requests without authenticating against Entra ID directly. The proxy drops this token value and injects the actual production Entra token payload.

  • Get-MgUser -Top 5: Standard downstream commands execute normally. All communications route directly through the auditable gateway infrastructure.

Note: After Connect-MgGraph completes successfully, the PowerShell session remains connected to the configured Microsoft Graph endpoint for this current session (or until Disconnect-MgGraph is executed). You do not need to rerun the full connection script before every Graph operation. Simply execute additional Microsoft Graph PowerShell cmdlets (for example, Get-MgGroup, Get-MgDevice, or New-MgUser) from the existing PowerShell prompt, and they will continue to be routed through the established proxy connection using the same configured MS Graph Endpoint asset.

Expanded Proxy Connection Example Script

Building upon the basic connection PowerShell script from Step 4, this advanced script transforms the session into an interactive, reusable management tool. Instead of executing a single hardcoded query, it leverages Invoke-MgGraphRequest to let administrators dynamically issue GET, POST, and PATCH requests against any Microsoft Graph endpoint within a repeatable loop. It features on-the-fly JSON body ingestion for data updates, custom OData query parsing, and interactive data visualization choices (including tables, lists, and Out-GridView).

# ==========================================
# 1. ENVIRONMENT & PROXY CONFIGURATION
# ==========================================
$proxyUrl = "http://{pam.contoso.com}:8800"

# Securely prompt the user for proxy credentials via a standard popup/prompt
$proxyAuth = Get-Credential -UserName "12Port-User#Asset-Name" -Message "Enter credentials for the NTLM Proxy Server ($proxyUrl)"

Write-Host "Configuring session-wide proxy settings with explicit credentials..." -ForegroundColor Cyan

# Configure the global .NET environment proxy (used by the Graph SDK background engine)
$proxy = New-Object System.Net.WebProxy($proxyUrl)
$proxy.UseDefaultCredentials = $false  # Disable automatic login fallback
$proxy.Credentials = $proxyAuth        # Apply the user's provided credentials
[System.Net.WebRequest]::DefaultWebProxy = $proxy

# Force modern TLS compliance
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13

# ==========================================
# 2. CONNECT TO MICROSOFT GRAPH SDK
# ==========================================
# Ensure the authentication module is loaded into the session
Import-Module Microsoft.Graph.Authentication -ErrorAction SilentlyContinue

# Disconnect cleanly (this resets the internal context natively)
Disconnect-MgGraph -ErrorAction SilentlyContinue

# Proceed with token injection
$rawToken = "fake_jwt_token" # Replace with real token injection mechanism if required
$secureToken = ConvertTo-SecureString -String $rawToken -AsPlainText -Force
Connect-MgGraph -AccessToken $secureToken -Environment Global

# ==========================================
# REPEATABLE LOOP FOR GRAPH QUERIES & UPDATES
# ==========================================
do {
    # ==========================================
    # 3. DYNAMIC PROMPT FOR METHOD & PATH
    # ==========================================
    Write-Host "`n=== MICROSOFT GRAPH DYNAMIC INTERFACE ===" -ForegroundColor Cyan

    # Prompt 1: Choose the HTTP Operation
    $methodChoice = Read-Host "Choose Method: [1] GET (Read), [2] PATCH (Update), [3] POST (Create/Action)"
    switch ($methodChoice) {
        "1"     { $method = "GET" }
        "2"     { $method = "PATCH" }
        "3"     { $method = "POST" }
        Default { $method = "GET" } # Default to safe read-only if they mess up
    }

    # Prompt 2: The Graph API Endpoint URI path
    $apiPath = Read-Host "Enter Graph URI Path (e.g., v1.0/users, v1.0/users/{id}, v1.0/groups)"
    if ([string]::IsNullOrWhiteSpace($apiPath)) { 
        Write-Host "API Path cannot be empty." -ForegroundColor Red
        continue 
    }

    # Initialize request parameters
    $requestArgs = @{
        Method  = $method
        Headers = @{ "ConsistencyLevel" = "eventual" }
    }

    # Handle Method-Specific Logic
    if ($method -eq "GET") {
        # Prompt 3: OData Query parameters (Only for GET requests)
        $queryString = Read-Host 'Enter Query Parameters (e.g., $select=id,displayName or leave blank)'

        $fullUri = $apiPath
        if (-not [string]::IsNullOrWhiteSpace($queryString)) {
            if (-not $queryString.StartsWith("?")) { $fullUri += "?" + $queryString }
            else { $fullUri += $queryString }
        }
        $requestArgs.Uri = $fullUri
    } 
    else {
            # PATCH / POST require a Body rather than URL query parameters
            $requestArgs.Uri = $apiPath
            Write-Host "Enter JSON Request Body (Type your JSON, then type 'DONE' or press Enter on an empty line when finished):" -ForegroundColor Yellow

            # Manually collect lines to avoid pipeline buffer bugs
            $jsonLines = @()
            while ($true) {
                $line = Read-Host
                if ([string]::IsNullOrEmpty($line) -or $line -eq "DONE") { break }
                $jsonLines += $line
            }
            $jsonBody = $jsonLines -join "`n"

            if ([string]::IsNullOrWhiteSpace($jsonBody)) {
                Write-Host "Error: PATCH and POST operations require a JSON body payload." -ForegroundColor Red
                continue 
            }

            $requestArgs.Body = $jsonBody
            $requestArgs.Headers += @{ "Content-Type" = "application/json" }
        }

    # ==========================================
    # 4. EXECUTE GRAPH REQUEST
    # ==========================================
    Write-Host "`nExecuting request: $method https://graph.microsoft.com/$($requestArgs.Uri)" -ForegroundColor Cyan

    try {
        # Splatting the arguments dynamically based on whether it's a GET, PATCH, or POST
        $response = Invoke-MgGraphRequest @requestArgs

        Write-Host "Operation completed successfully!" -ForegroundColor Green

        # Only handle data rendering if there actually is a response payload (PATCH often returns status 204 No Content)
        if ($null -ne $response) {
            if ($null -ne $response.value) {
                $data = $response.value
                Write-Host "Returned $($data.Count) record(s)." -ForegroundColor Green
            } else {
                $data = $response
            }

            # Interactively ask how the user wants to view the output
            $outputChoice = Read-Host "`nChoose output format: [T]able, [L]ist, [G]ridView, [O]bject Variable, or [D]efault Raw"
            switch ($outputChoice.ToUpper()) {
                "T" { $data | Format-Table }
                "L" { $data | Format-List }
                "G" { $data | Out-GridView -Title "Graph API Output: $apiPath" }
                "O" { 
                    $global:GraphOutput = $data
                    Write-Host "Data saved to global variable `$GraphOutput" -ForegroundColor Magenta
                }
                Default { $data }
            }
        }
    }
    catch {
        Write-Host "=== Graph API Request failed - exception chain ===" -ForegroundColor Red
        $e = $_.Exception
        while ($e) {
            Write-Host ($e.GetType().FullName + " :: " + $e.Message) -ForegroundColor Yellow
            $e = $e.InnerException
        }
    }

    # Prompt the user to see if they want to repeat the process
    Write-Host ""
    $repeatChoice = Read-Host "Would you like to run another operation? (Y/N)"

} while ($repeatChoice -eq "Y" -or $repeatChoice -eq "y")

Write-Host "`nExiting script. Goodbye!" -ForegroundColor Cyan

Session Reporting Behavior

12Port Microsoft Graph proxy activity is recorded as logical sessions within the 12Port Session Report rather than as individual HTTP proxy connections. This allows related activity from the same user to be grouped into a single auditable session, even if the underlying PowerShell process or network connection is disconnected or restarted.

A logical session is correlated using the authenticated user identity and the target Microsoft Graph Endpoint asset. The session lifecycle behaves as follows:

The Session Report statuses are reported as follows:

Session Stage Session Report Behavior
Session Created A new Microsoft Graph proxy connection creates a Session Report entry with a status of Active.
Session Completion After a period of inactivity, the existing Session Report entry automatically transitions to Completed.
Session Resumption If the same user reconnects to the same Microsoft Graph Endpoint asset before the logical session expires, the existing Session Report entry is resumed by changing its status back to Active.
New Logical Session Once the logical session has expired, the previous Session Report entry remains Completed. Any subsequent connection by the same user to the same Microsoft Graph Endpoint asset creates a new Session Report entry with a status of Active.

Note: It is therefore normal for multiple PowerShell processes or proxy connections initiated by the same user against the same Microsoft Graph Endpoint asset to appear as a single Session Report entry when they occur within the session continuation window.


Troubleshooting

Symptom Probable Cause Remediation Action
TLS / SSL Trust Errors calling Microsoft Graph Root CA certificate not installed, invalid, or isolated inside the wrong user store scope. Ensure the installation script is executed from an elevated (Run as Administrator) PowerShell window. Verify certificate store integration using certutil -store Root.
HTTP Error 407: Proxy Authentication Required NTLM authentication parameters are invalid, or the user profile lacks the required permissions matching the asset. Validate that the user credentials format strictly adheres to 12Port-User#Asset-Name. Verify that the caller identity has been assigned a valid Asset Viewer permission on the target asset.
HTTP Error 401: Unauthorized from Graph The upstream Entra ID App Registration parameters are incorrect, or permissions are insufficient. Access the Microsoft Entra ID admin center. Validate that the application secret is active and that your application permissions (e.g., User.Read.All) have been granted Tenant Admin Consent.
Requests bypass the proxy entirely The Graph SDK fails to honor the global DefaultWebProxy context configurations. Verify that the global system variable configs are declared prior to executing Connect-MgGraph. Ensure that local environmental system override values such as HTTP_PROXY or HTTPS_PROXY are not conflicting with script runtime declarations.