Docs

Set Up SSO with Microsoft Entra ID (Azure AD)

Configure Apache APISIX with Microsoft Entra ID for browser-based single sign-on using the OpenID Connect Authorization Code flow with PKCE.

Applies to:Apache APISIX 3.18.0

Roles:Platform engineerSecurity engineerTime:20 minutesImpact:No restart, no traffic change

Goal

A route that redirects unauthenticated browser requests to Microsoft Entra ID, completes the OpenID Connect (OIDC) Authorization Code flow with Proof Key for Code Exchange (PKCE), keeps tokens in an encrypted APISIX session, and removes authentication data before proxying upstream.

Before you begin

  • You have a Microsoft Entra workforce tenant and the Application Developer role or another role that can register applications.
  • You have a tenant user that can sign in to the registered application. Your tenant's consent policy might require an administrator to approve the requested OIDC permissions.
  • You have an Apache APISIX installation, cURL, jq, and OpenSSL. Get APISIX installs APISIX in Docker or Kubernetes.
  • Your browser can reach http://localhost:9080. For any non-local deployment, substitute one stable HTTPS gateway origin in both Microsoft Entra ID and APISIX.

Set the APISIX variables this guide uses:

export APISIX_ADMIN_URL="http://127.0.0.1:9180"
export GATEWAY_URL="http://localhost:9080"
export SSO_CALLBACK_URL="${GATEWAY_URL}/anything/user/callback"
read -rsp "Admin API key: " APISIX_ADMIN_KEY && export APISIX_ADMIN_KEY && echo
  • The Admin API accepts your key. Check it with:

    curl -s -o /dev/null -w '%{http_code}\n' "${APISIX_ADMIN_URL}/apisix/admin/routes" \
      -H "X-API-KEY: ${APISIX_ADMIN_KEY}"

    Expected: 200.

  • The data plane is reachable. Check it with:

    curl -sI "${GATEWAY_URL}" | grep -i '^server'

    Expected: a Server: APISIX/3.18.0 header.

  • The route ID entra-browser and path /anything/user/* are unused. Check both with:

    curl -s -o /dev/null -w '%{http_code}\n' \
      "${APISIX_ADMIN_URL}/apisix/admin/routes/entra-browser" \
      -H "X-API-KEY: ${APISIX_ADMIN_KEY}"
    
    curl -s "${APISIX_ADMIN_URL}/apisix/admin/routes" \
      -H "X-API-KEY: ${APISIX_ADMIN_KEY}" | \
      jq -r '.list[] | select(.value.uri == "/anything/user/*") | .key'

    Expected: 404 from the first command and no output from the second. A 200 or listed key means a collision; stop and choose a unique route ID and path rather than overwriting or shadowing an existing route.

Steps

Three steps register the Microsoft Entra web application, capture its OIDC settings, and create the protected APISIX route.

Step 1: Register the web application

In the Microsoft Entra admin center, select Entra ID → App registrations → New registration and configure:

  • Name: APISIX Authorization Code
  • Supported account types: accounts in this organizational directory only
  • Redirect URI platform: Web
  • Redirect URI: the exact value of ${SSO_CALLBACK_URL}—http://localhost:9080/anything/user/callback for this guide

Register the application. On its Authentication page, confirm the Web platform contains the callback URL and that neither Access tokens nor ID tokens is enabled under Implicit grant and hybrid flows.

Expected: the single-tenant application has one Web redirect URI and does not enable an implicit flow.

What changed: Microsoft Entra ID can return the browser only to the registered APISIX callback for this web application.

Step 2: Create and capture the client credential

Copy the Application (client) ID and Directory (tenant) ID from the application overview. Select Certificates & secrets → Client secrets → New client secret, enter a description, choose an expiration that follows your rotation policy, and add it.

Copy the client secret Value immediately; the Secret ID is not the credential. For production, Microsoft recommends a certificate or federated credential instead of a client secret. Use a credential mode supported by both your identity platform and APISIX deployment.

Copy the values into the shell and generate the APISIX session secret:

read -rp "Microsoft Entra tenant ID: " ENTRA_TENANT_ID && export ENTRA_TENANT_ID
read -rp "Microsoft Entra client ID: " ENTRA_CLIENT_ID && export ENTRA_CLIENT_ID
read -rsp "Microsoft Entra client secret value: " ENTRA_CLIENT_SECRET && export ENTRA_CLIENT_SECRET && echo
export ENTRA_DISCOVERY="https://login.microsoftonline.com/${ENTRA_TENANT_ID}/v2.0/.well-known/openid-configuration"
APISIX_SESSION_SECRET="$(openssl rand -hex 32)" && export APISIX_SESSION_SECRET

Keep the client and session secrets out of shell history, source control, logs, and screenshots. Confirm that the discovery document advertises authorization and token endpoints:

curl -sS "${ENTRA_DISCOVERY}" | jq -er '.authorization_endpoint, .token_endpoint'

Expected: two Microsoft HTTPS URLs containing the tenant ID.

What changed: the shell holds a tenant-specific confidential-client configuration and a fresh 256-bit secret for encrypting and authenticating the APISIX session cookie.

Step 3: Create the protected route

Create a route that starts browser authentication, uses PKCE, and strips credentials before proxying to httpbin.org:

curl -s "${APISIX_ADMIN_URL}/apisix/admin/routes/entra-browser" -X PUT \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}" \
  -H "Content-Type: application/json" \
  --data-binary @- <<EOF
{
  "uri": "/anything/user/*",
  "plugins": {
    "openid-connect": {
      "client_id": "${ENTRA_CLIENT_ID}",
      "client_secret": "${ENTRA_CLIENT_SECRET}",
      "discovery": "${ENTRA_DISCOVERY}",
      "redirect_uri": "${SSO_CALLBACK_URL}",
      "bearer_only": false,
      "use_pkce": true,
      "scope": "openid profile email",
      "session": {"secret": "${APISIX_SESSION_SECRET}"},
      "set_access_token_header": false,
      "set_id_token_header": false,
      "set_userinfo_header": false
    },
    "proxy-rewrite": {
      "headers": {"remove": ["Authorization", "Cookie"]}
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {"httpbin.org:80": 1}
  }
}
EOF
{ "value": { "id": "entra-browser", "uri": "/anything/user/*", "plugins": { "openid-connect": { "bearer_only": false, "use_pkce": true, … } }, … } }

What changed: /anything/user/* now requires a Microsoft Entra-backed browser session. APISIX removes the original authorization and cookie headers and does not add token or user-information headers upstream. If the upstream application needs its own cookies, narrow the Cookie removal policy before production use.

Validate

Open ${GATEWAY_URL}/anything/user/get in a private browser window. APISIX redirects the browser to Microsoft Entra ID. Sign in as the tenant test user and complete any consent step allowed by your tenant policy.

Expected: Microsoft Entra ID returns the browser to ${SSO_CALLBACK_URL}, APISIX resumes the original request, and httpbin returns a JSON response for /anything/user/get.

In the response's headers object, confirm that Authorization, Cookie, X-Access-Token, X-Id-Token, and X-Userinfo are absent. Reload the protected URL.

Expected: the reload succeeds through the APISIX session without another Microsoft sign-in prompt.

Roll back / Clean up

Delete the APISIX route before deleting its Microsoft Entra client:

curl -s -X DELETE "${APISIX_ADMIN_URL}/apisix/admin/routes/entra-browser" \
  -H "X-API-KEY: ${APISIX_ADMIN_KEY}"

Expected: the Admin API returns a successful deletion response, and the protected path no longer matches this route.

Delete APISIX Authorization Code from App registrations only if no other environment uses it. Close the private browser window and unset the secrets:

unset ENTRA_CLIENT_SECRET APISIX_SESSION_SECRET APISIX_ADMIN_KEY

Troubleshoot

SymptomCauseFix
Microsoft returns AADSTS50011.The registered reply URL and APISIX redirect_uri differ.Copy ${SSO_CALLBACK_URL} exactly into the application's Web redirect URIs and recreate the route if its value changed.
Microsoft returns AADSTS700016.The tenant or application client ID is wrong, or the application is not in that tenant.Recopy both IDs from the application overview and confirm the discovery URL names the same tenant.
APISIX cannot redeem the authorization code.The client secret value is wrong or expired, or a non-Web redirect platform was selected.Use the secret Value, create a new credential if needed, and configure the callback under the Web platform.
Sign-in completes but APISIX reports no session state.The browser did not return the APISIX session cookie, or the gateway origin changed during the flow.Use one consistent gateway origin, allow cookies for it, and restart the flow. See No Session State Found (opens in Plugin Hub docs).