Table of Contents

Authentication Guide

Secure your PoshMcp server with either Azure Entra ID (OAuth 2.1) or API key authentication.

Use this page to choose the right option for your deployment and configure it with the current Authentication schema.

Choose an Authentication Mode

  • Use Entra ID (OAuth 2.1) for enterprise identity, centralized access control, token issuance, and browser/client compatibility. Start with Entra ID (OAuth 2.1).
  • Use API key authentication for internal services, automation, and simple server-to-server access where key distribution is acceptable. Start with API Key Authentication.
  • In Azure-hosted production, a common pattern is Entra ID for inbound client authentication plus Managed Identity for outbound Azure resource access.

Quick Comparison

Aspect App Registration Managed Identity Best For
Defines OAuth scopes? ✓ Yes (required) ✗ No OAuth API definition
Provides server credentials? ✓ Yes (client secret) ✓ Yes (automatic) Server calling Azure APIs
Setup complexity Medium (portal steps) Low (enable on resource) Ease of deployment
Secret rotation burden? High (manual) None (automatic) Reduced ops overhead
Works on-premises? ✓ Yes ✗ No (requires Azure compute) Non-Azure deployments
Works in Azure Container Apps/AKS? ✓ Yes ✓ Yes Azure-native deployments

Decision Matrix

  • On-premises or hybrid: Use App Registration only
  • Azure Container Apps, AKS, or App Service: Use Managed Identity + App Registration (recommended)
  • Simple testing: Use App Registration only
  • Enterprise with compliance: Use both for maximum flexibility

Entra ID (OAuth 2.1)

Use Entra ID when clients should present bearer tokens and you need centralized identity lifecycle management.

App Registration

Use App Registration to define OAuth scopes and control client access. Works anywhere.

Setup in Azure Portal

  1. Go to Azure PortalAzure Active DirectoryApp registrationsNew registration
  2. Name: PoshMcp Server
  3. Click Register

Save these values:

  • Application (client) ID
  • Directory (tenant) ID

Expose API Scopes

Before you can add scopes, you must first set the Application ID URI. This is the root identifier for your API and scopes.

  1. Go to Expose an API
  2. If Application ID URI is not set, click Set and accept the default (api://{client-id}) or enter a custom URI like api://poshmcp-prod
  3. Click Add a scope
  4. Fill in the scope details:
    • Scope name (required): user_impersonation — becomes part of the full scope URI: api://poshmcp-prod/user_impersonation
    • Who can consent?: Select "Admins only" for server-to-server (M2M) scenarios; "Admins and users" for delegated user consent
    • Admin consent display name (required): e.g., Access PoshMcp Server — shown on admin consent screens
    • Admin consent description (required): e.g., Allows the app to execute PowerShell commands via PoshMcp — shown on admin consent screens
    • User consent display name (optional): e.g., Access PoshMcp Server
    • User consent description (optional): e.g., Allows you to execute PowerShell commands via PoshMcp
    • State: Ensure Enabled is selected
  5. Click Add scope

After creating the scope, if you selected "Admins only" for a machine-to-machine scenario:

  • Go to API permissions on the client app registration that will use this scope
  • Add the newly created scope as an application permission
  • Click Grant admin consent (only admins can do this) to pre-authorize the client

Full scope URI: The scope you configured becomes {Application ID URI}/{scope name} — use this in your RequiredScopes and ScopesSupported settings.

Configure PoshMcp

Edit appsettings.json:

{
  "Authentication": {
    "Enabled": true,
    "DefaultScheme": "Bearer",
    "DefaultPolicy": {
      "RequireAuthentication": true,
      "RequiredScopes": ["user_impersonation"]
    },
    "Schemes": {
      "Bearer": {
        "Type": "JwtBearer",
        "Authority": "https://login.microsoftonline.com/{tenant-id}",
        "Audience": "api://poshmcp-prod",
        "RequireHttpsMetadata": true,
        "ValidIssuers": ["https://login.microsoftonline.com/{tenant-id}/v2.0"]
      }
    },
    "ProtectedResource": {
      "Resource": "api://poshmcp-prod",
      "ResourceName": "PoshMcp Server",
      "AuthorizationServers": ["https://login.microsoftonline.com/{tenant-id}"],
      "ScopesSupported": ["api://poshmcp-prod/user_impersonation"],
      "BearerMethodsSupported": ["header"]
    }
  },
  "PowerShellConfiguration": {
    "CommandNames": ["Get-Process", "Get-Service"]
  }
}

Replace {tenant-id} with your Directory (tenant) ID from the app registration.

Managed Identity (Azure-Hosted Deployments)

Use managed identity when PoshMcp runs on Azure compute (Container Apps, AKS, App Service). Eliminates credential management on the server.

Enable Managed Identity

Azure Container Apps:

az containerapp create \
  --name poshmcp \
  --resource-group MyResourceGroup \
  --image myregistry.azurecr.io/poshmcp:latest \
  --system-assigned

Azure Kubernetes Service (AKS):

Use workload identity for pods:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: poshmcp
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: poshmcp
spec:
  template:
    spec:
      serviceAccountName: poshmcp
      containers:
      - name: poshmcp
        image: myregistry.azurecr.io/poshmcp:latest

Grant Permissions

Get the managed identity's principal ID and grant access to services:

principalId=$(az containerapp identity show --name poshmcp --resource-group MyResourceGroup --query principalId -o tsv)

# Grant Key Vault access
az keyvault set-policy \
  --name mykeyvault \
  --object-id $principalId \
  --secret-permissions get list

# Or grant storage access
az role assignment create \
  --assignee $principalId \
  --role "Storage Blob Data Reader" \
  --scope /subscriptions/{subscription}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/mystorageacct

Configuration

Use the same appsettings.json format as App Registration. The difference is credentials come from the Azure platform automatically.

API Key Authentication

Use API key authentication when you need lightweight authentication for trusted clients, internal automation, or bootstrap scenarios.

Edit appsettings.json:

{
  "Authentication": {
    "Enabled": true,
    "DefaultScheme": "ApiKey",
    "DefaultPolicy": {
      "RequireAuthentication": true,
      "RequiredScopes": [],
      "RequiredRoles": ["reader"]
    },
    "Schemes": {
      "ApiKey": {
        "Type": "ApiKey",
        "HeaderName": "X-API-Key",
        "Keys": {
          "key-reader": {
            "Scopes": [],
            "Roles": ["reader"]
          },
          "key-ops": {
            "Scopes": [],
            "Roles": ["ops", "reader"]
          }
        }
      }
    }
  }
}

Make an authenticated request:

curl -H "X-API-Key: key-reader" https://poshmcp.example.com/tools

For per-tool authorization precedence with PowerShellConfiguration.CommandOverrides, see Configuration Guide.

Testing Authentication

Check Protected Resource Metadata

curl https://poshmcp.example.com/.well-known/oauth-protected-resource

Expected response:

{
  "resource": "api://poshmcp-prod",
  "resource_name": "PoshMcp Server",
  "authorization_servers": ["https://login.microsoftonline.com/{tenant-id}"],
  "scopes_supported": ["api://poshmcp-prod/user_impersonation"],
  "bearer_methods_supported": ["header"]
}

Acquire a Token

curl -X POST \
  https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token \
  -d "client_id={client-id}" \
  -d "client_secret={client-secret}" \
  -d "scope=api://poshmcp-prod/.default" \
  -d "grant_type=client_credentials"

Make Authenticated Request

curl -H "Authorization: Bearer {token}" \
  https://poshmcp.example.com/tools

Security Best Practices

1. Use HTTPS

Always use HTTPS for production. Entra ID requires it.

2. Design Scopes for Least Privilege

Create granular scopes instead of "admin" or "full access":

{
  "scopes_supported": [
    "api://poshmcp-prod/read.diagnostics",
    "api://poshmcp-prod/execute.storage",
    "api://poshmcp-prod/execute.compute"
  ]
}

3. Combine with Command Allowlists

{
  "PowerShellConfiguration": {
    "CommandNames": [
      "Get-Process",
      "Get-Service"
    ],
    "ExcludePatterns": [
      "Remove-*",
      "*-Credential",
      "Invoke-Expression"
    ]
  }
}

4. Store Secrets in Key Vault

Never embed secrets in appsettings.json:

az keyvault secret set --vault-name MyKeyVault --name poshmcp-client-secret --value "..."

Reference in Container Apps:

az containerapp secret set --name poshmcp --secrets client-secret=keyvaultref:MyKeyVault/poshmcp-client-secret

5. Enable Audit Logging

PoshMcp logs user identity and command execution:

export POSHMCP_LOG_LEVEL=Information

Logs include:

  • User principal name (from token)
  • Command executed
  • Correlation ID (trace requests across systems)

Troubleshooting

Use the Doctor Command for Diagnostics

Run the poshmcp doctor command to validate your authentication configuration:

poshmcp doctor

The output includes:

  • Authentication section: Shows enabled state, configured schemes (JwtBearer/ApiKey), authority, audience, key count, default policy, and protected resources — no secrets are exposed
  • Identity section (HTTP transport only): Shows authenticated caller's principal name, assigned scopes, and roles

401 Unauthorized on every request

Check:

  1. Is authentication enabled in appsettings.json?
  2. Are you sending Authorization: Bearer {token} header?
  3. Is the token expired? Check the exp claim.

Invalid token signature

Check:

  1. Did you acquire the token from the correct Entra ID tenant?
  2. Is the server using HTTPS? (Entra ID requires it for production)
  3. Do the token's iss, aud, and scp claims match your configuration?

Decode the token at jwt.io to inspect claims.

Insufficient permissions / scope mismatch

Error: The claim 'scp' does not contain any of the required values

Fix:

  1. Verify the token includes the required scope (decode and check scp claim)
  2. In Entra ID, verify the scope is defined in "Expose an API"
  3. Verify the client requested the correct scope when acquiring the token
  4. Check DefaultPolicy.RequiredScopes in appsettings.json

Managed Identity: IMDS endpoint not found

Symptoms: Can't access Azure services from PoshMcp despite managed identity being enabled.

Fix:

  1. Verify managed identity is enabled on your compute resource
  2. Check for network policies or proxies blocking access to 169.254.169.254
  3. Allow IMDS (Instance Metadata Service) access:
    # Inside container, test IMDS
    curl -H "Metadata:true" http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-12-01&resource=https://management.azure.com
    

Managed Identity: Access denied to Azure service

Fix: Grant the managed identity the appropriate role:

principalId=$(az containerapp identity show --name poshmcp --resource-group MyResourceGroup --query principalId -o tsv)

az role assignment create \
  --assignee $principalId \
  --role "Key Vault Secrets User" \
  --scope /subscriptions/{subscription}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/mykeyvault

Next: Security Best Practices | Configuration Guide | Docker Deployment