Add GraphWarden compatibility to your product

GraphWarden-compatible means your product reads the Microsoft Graph base URL and credentials from customer configuration instead of hardcoding them. This page walks through the technical integration, edge cases (token caching, rate limits, ruleset denial errors), and a compatibility test checklist.

Applies to every language and Graph SDK

Prerequisites

- A product that calls Microsoft Graph in any language or SDK - The ability to externalize the Graph endpoint and credentials as configuration (environment variables, a config file, or a database-backed setting) - A test GraphWarden instance reachable from your development environment for compatibility testing

Plan GraphWarden compatibility with an AI assistant

Add GraphWarden compatibility to my SaaS product that uses Microsoft Graph.

Compatibility means my product reads the Graph base URL and credentials from customer configuration instead of hardcoding https://graph.microsoft.com.

I need:
1. Identification of every Graph reference in my codebase that needs to be externalized
2. A configuration surface change (env vars, admin UI settings, or similar)
3. A compatibility statement template for my customer-facing documentation

Reference documentation: https://graphwarden.com/llms.txt

Ask me for my product's primary language/framework and paste the Graph initialization code.

Reference: llms.txt

The two config values

Two values move from product-level constants to customer-supplied configuration. Everything else in your product stays the same.

Value Direct-to-Graph Through GraphWarden
Microsoft Graph base URL https://graph.microsoft.com (the Microsoft default) The customer's proxy URL, for example https://proxy.customer.internal
Token endpoint https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token https://{proxy-host}/proxy/token (the proxy issues its own JWT)
Client ID sent to the token endpoint Your customer's Azure AD application client_id The Proxy Client ID issued by the customer's GraphWarden App Admin
Client Secret sent to the token endpoint Your customer's Azure AD application secret The Proxy Client Secret issued by the customer's GraphWarden App Admin
Scopes https://graph.microsoft.com/.default Identical — scopes continue to be declared against Graph
Request paths /v1.0/users, /beta/groups, etc. Identical — the proxy is path-transparent

The practical shape: your product's configuration surface adds one new Graph base URL setting (call it GRAPH_BASE_URL or whatever matches your product's conventions), and your existing credential inputs accept the customer-supplied Proxy Client ID and Secret instead of Azure AD credentials. No SDK change. No protocol change.

Integration steps

The four steps below are the complete engineering work required to declare compatibility.

  1. Externalize the Graph base URL. Replace every hardcoded reference to https://graph.microsoft.com with a configuration read. If your product uses a Microsoft-published Graph SDK, this is a one-line change — set the BaseAddress of the HttpClient passed to GraphServiceClient (.NET), or pass a baseUrl option (@microsoft/microsoft-graph-client on Node). The default value for the setting remains https://graph.microsoft.com so customers who do not deploy GraphWarden continue to work without changing anything.
  2. Externalize the credentials. If your product hardcodes the Azure AD token endpoint or treats it as an implementation detail, expose it as configuration. Accept the proxy token endpoint in the same setting. Accept the Proxy Client ID and Proxy Client Secret through the same credential inputs your product already uses for Azure AD client credentials.
  3. Document these settings for your customers. In your product's deployment or configuration documentation, describe the two settings and note that they accept either Microsoft defaults (direct to Graph) or customer-supplied values (routed through GraphWarden or any equivalent governance proxy). The compatibility statement template gives you the wording your enterprise customers expect.
  4. Test the integration. Run the compatibility test checklist below against a test GraphWarden instance. Once the checklist passes, publish your compatibility statement.

A .NET example makes the shape concrete. The snippet below shows the two overrides — everything else is the same code you already have.

using System.Net.Http.Headers;
using Microsoft.Graph;
using Microsoft.Identity.Client;

// Read from your existing configuration surface.
string graphBaseUrl     = config["GRAPH_BASE_URL"] ?? "https://graph.microsoft.com";
string tokenEndpoint    = config["GRAPH_TOKEN_ENDPOINT"]
    ?? "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token";
string graphClientId    = config["GRAPH_CLIENT_ID"];
string graphClientSecret= config["GRAPH_CLIENT_SECRET"];

// Direct-to-Graph: tokenEndpoint points at Azure AD, graphBaseUrl is the Microsoft default.
// Through GraphWarden: tokenEndpoint points at the proxy (/proxy/token), graphBaseUrl is
// the proxy host, and the client_id/secret are the proxy pair.
var cca = ConfidentialClientApplicationBuilder
    .Create(graphClientId)
    .WithClientSecret(graphClientSecret)
    .WithAuthority(new Uri(tokenEndpoint), validateAuthority: false)
    .Build();

var token = (await cca
    .AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
    .ExecuteAsync()).AccessToken;

var http = new HttpClient { BaseAddress = new Uri(graphBaseUrl) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

var graphClient = new GraphServiceClient(http);
// graphClient.Users.GetAsync(...) now hits {graphBaseUrl}/v1.0/users with the issued token.

Edge case: token caching

Tokens issued by the proxy have the same one-hour lifetime as tokens issued by Azure AD. Your existing token-caching strategy keeps working.

If your product caches the token by expiry and evicts on 401, the behaviour against the proxy is identical: acquire once, reuse until expiry, re-acquire when expiry passes or the proxy returns 401. MSAL's built-in cache on ConfidentialClientApplication works the same way against the proxy's token endpoint as against Azure AD. The proxy returns standard OAuth2 responses from /proxy/token: a JSON body with access_token, token_type: Bearer, and expires_in.

Do not cache tokens across product upgrades or restarts unless you already do so for Azure AD tokens. The proxy does not introduce new cache-invalidation concerns.

Edge case: rate limits

The proxy may enforce stricter rate limits than Microsoft Graph. Treat 429 responses from the proxy with the same exponential-backoff strategy your product already uses for Graph, and honour the `Retry-After` header exactly as you would against direct Graph.

A GraphWarden deployment can throttle Graph traffic per App Identity or per tenant before a request ever reaches Microsoft. This protects the customer's Graph quota and enforces their internal usage budgets. From your product's perspective, a 429 from the proxy is indistinguishable from a 429 from Graph — status code, JSON body shape, and Retry-After header all follow the Microsoft pattern.

Rate-limit thresholds are customer-configurable. A customer running a tight budget may set lower thresholds than Microsoft's defaults; a customer running a generous budget may see no proxy-imposed limit at all. Your product should not encode assumptions about the threshold; the correct behaviour is to respect the Retry-After value and retry after the indicated interval.

Edge case: ruleset denial errors

When a customer's ruleset blocks a specific Graph call, the proxy returns HTTP 403 with a JSON body naming the matched rule. The response shape is proxy-specific but follows the Microsoft Graph error envelope closely: an error object with code and message fields, plus an innerError.ruleId that identifies the rule in the customer's GraphWarden App Admin.

Your product should surface this gracefully. A helpful error message for the end user looks something like:

"This action was blocked by your organization's Microsoft Graph policy. Contact your GraphWarden administrator and reference rule {ruleId}."

Do NOT treat a 403 from the proxy as a bug in your product. The customer's security policy is the authoritative source for what Graph calls are permitted; a 403 means the administrator has not granted permission for this endpoint or this combination of caller plus endpoint. Log the ruleId at your product's standard error level and return a user-facing message that names the administrator, not your support team.

Ruleset denials can also surface as response filters — the proxy returns 200 with a partial body where specific properties have been stripped. If your product depends on a property and receives null unexpectedly, the matched rule's filter is responsible. The customer's GraphWarden audit log records both the rule id and the transforms applied.

Compatibility test checklist

Run through the checklist against a test GraphWarden instance before publishing your compatibility statement. A pass on every item is the bar for declaring compatibility.

  1. Baseline: direct to Graph. With GRAPH_BASE_URL set to https://graph.microsoft.com and the token endpoint set to Azure AD, confirm every feature of your product that calls Graph works. This is your regression fixture.
  2. Open ruleset through GraphWarden. Point your product at the test proxy URL with a ruleset that contains a single rule: action: allow on every endpoint and every method. Every feature that passed step 1 MUST pass step 2. Differences here indicate your product is not reading the base URL or credentials from configuration.
  3. Rule denying one specific endpoint. Ask the test administrator to add a rule with action: block on one specific endpoint your product uses — for example POST /users. Trigger that feature and confirm your product surfaces the 403 response without crashing, and that the error message names the blocked action clearly. The rest of your product MUST continue to function for other endpoints.
  4. Rule filtering a response property. Ask the test administrator to add a rule with action: filter and a strip_properties transform that removes a property your product reads — for example, mail on /users. Trigger that feature and confirm your product handles the missing property gracefully (does not crash on null; displays a reasonable fallback).
  5. Token expiry and refresh. Wait past the token lifetime (one hour) and trigger a feature. Confirm your product re-acquires a token against /proxy/token transparently.
  6. 429 Too Many Requests. Ask the test administrator to lower the rate limit aggressively, then trigger a burst of calls. Confirm your product honours Retry-After and retries after the indicated interval rather than hammering the proxy.
  7. 502 / proxy unavailability. Stop the test proxy and trigger a feature. Confirm your product surfaces a reasonable "Graph unavailable" error rather than a cryptic low-level HTTP failure. Restart the proxy and confirm the next call succeeds without requiring a product restart.
  8. Cross-region deployment. If your product deploys in multiple regions, confirm each region's configuration points at the region's proxy URL. The proxy URL is per-customer and does not need to match your product's region layout.

A product that passes all eight items can publish a GraphWarden compatibility statement with confidence. A product that fails one or more items has a concrete fix list: externalize what is hardcoded, handle what is unhandled, then re-run the checklist.

A copy-paste template for the published statement is the next page in this section. Use it to declare compatibility in your own product documentation without having to draft the wording from scratch.

Troubleshooting

The five issues below cover the common surprises when wiring a product to GraphWarden for the first time.

401 Unauthorized from /proxy/token

The Proxy Client ID or Proxy Client Secret sent to /proxy/token is not the pair the customer's App Admin issued for your App Identity. Your product is likely still sending Azure AD credentials. Confirm the credentials input in your product's configuration is the proxy pair — not the Azure AD application credentials. The customer distributes the proxy pair through their own secrets channel (Key Vault, Vault, CI secrets); the Azure AD application credentials never reach your product when GraphWarden is in the path.

HttpRequestException: The SSL connection could not be established

The proxy is reachable by DNS but its TLS certificate is not trusted by your runtime's trust store. In development against a self-signed test proxy, install the test certificate in the local machine trust store. In production, every proxy your customers run presents a publicly trusted certificate or one issued by their internal CA; your product should not disable certificate validation to work around this.

Graph SDK calls go to graph.microsoft.com instead of the proxy

The Graph base URL override was missed. Graph SDKs default to https://graph.microsoft.com when the override is absent; even a single un-overridden code path sends traffic direct to Graph, bypassing the ruleset. Grep your codebase for graph.microsoft.com and confirm every occurrence is either configurable or pointing at a configured base URL. This is the most common integration gap.

Every call returns 403 Forbidden with the same rule id

A rule in the customer's ruleset is matching aggressively — often a * endpoint pattern that was meant as a default-deny and never narrowed. Direct the customer's administrator to the GraphWarden App Admin's What-If Simulator to replay a specific request and see which rule fires. This is a ruleset configuration issue on the customer side, not a product bug.

Intermittent 502 Bad Gateway

Microsoft Graph returned a non-2xx upstream response and the proxy is forwarding it as 502. Check Microsoft's Graph status page; these are usually short-lived Graph incidents. Your product's existing retry-with-backoff logic for transient Graph failures works without change.

Last reviewed:

Last reviewed: .