Phase 2: OAuth / JWT Authentication Setup¶
Authenticate MCP clients using OAuth 2.1 with an external Identity Provider (EntraID, Cognito, Okta, Keycloak). ARC-1 validates JWT Bearer tokens and extracts user identity.
When to Use¶
- Enterprise environments with existing IdP
- When you need to know which user is making requests
- Audit trail requirements
- When combined with Phase 3 for per-user SAP auth
Architecture¶
┌─────────────────────┐
│ Identity Provider │
│ (EntraID / Cognito) │
└──────┬──────────────┘
│ OIDC tokens
┌──────────────────┐ JWT Bearer │ ┌──────────────────┐ Basic Auth ┌────────────┐
│ MCP Client │ ──────────────────►├────►│ arc1 Server │ ──────────────────► │ SAP ABAP │
│ (IDE / Copilot) │ Authorization │ │ validates JWT │ service account │ System │
└──────────────────┘ │ └──────────────────┘ └────────────┘
│
┌─────────────┘
│ JWKS keys
│ (cached 1h)
Identity Provider Setup¶
Microsoft Entra ID (Azure AD)¶
- Create App Registration:
- Azure Portal → Microsoft Entra ID → App registrations → New registration
- Name:
ARC-1 SAP MCP Server - Supported account types: Single tenant (
Accounts in this organizational directory only) -
Redirect URI: leave blank (will be set after Copilot Studio connector creation)
-
Expose an API:
- App registration → Expose an API → Set Application ID URI (accept default
api://{client-id}) -
Add a scope:
access_as_user— Type:Admins and users, Display name:Access ARC-1 -
Set Token Version to v2.0:
- App registration → Manifest → set
"requestedAccessTokenVersion": 2 - Or via Azure CLI:
# Get the object ID of the service principal's associated app az ad app show --id {client-id} --query id -o tsv # Patch the API application to use v2.0 tokens az rest --method PATCH \ --url "https://graph.microsoft.com/v1.0/applications/{object-id}" \ --body '{"api":{"requestedAccessTokenVersion":2}}' -
Why: v2.0 tokens use the raw client ID as
audclaim, while v1.0 usesapi://...URI. The OIDC validator needs a consistent audience value. -
Add Microsoft Graph User.Read permission:
- App registration → API permissions → Add a permission → Microsoft Graph → Delegated →
User.Read - Click Grant admin consent for your organization
- Or via Azure CLI:
-
Why: Power Platform requires this permission for OAuth connectors.
-
Create a Client Secret:
- App registration → Certificates & secrets → New client secret
- Copy the secret value immediately (it won't be shown again)
-
Or via Azure CLI:
-
Note the values:
- Application (client) ID — used as both Client ID and audience
- Directory (tenant) ID — e.g.,
9ef3a122-4319-496a-a394-a7318c2d0a7e - Client secret — from step 5
- Issuer URL:
https://login.microsoftonline.com/{tenant-id}/v2.0
AWS Cognito¶
- Create User Pool
- Create App Client
- Configure domain
- Issuer URL:
https://cognito-idp.{region}.amazonaws.com/{pool-id}
Keycloak¶
- Create Realm
- Create Client (confidential)
- Issuer URL:
https://keycloak.company.com/realms/{realm}
Server Setup¶
Start arc1 with OIDC Validation¶
arc1 --url https://sap.example.com:44300 \
--user SAP_SERVICE_USER \
--password 'ServicePassword123' \
--transport http-streamable \
--http-addr 0.0.0.0:8080 \
--oidc-issuer 'https://login.microsoftonline.com/{tenant-id}/v2.0' \
--oidc-audience 'api://arc1-sap-connector'
Environment Variables¶
export SAP_URL=https://sap.example.com:44300
export SAP_USER=SAP_SERVICE_USER
export SAP_PASSWORD=ServicePassword123
export SAP_TRANSPORT=http-streamable
export SAP_HTTP_ADDR=0.0.0.0:8080
export SAP_OIDC_ISSUER='https://login.microsoftonline.com/{tenant-id}/v2.0'
export SAP_OIDC_AUDIENCE='api://arc1-sap-connector'
export SAP_OIDC_USERNAME_CLAIM='preferred_username' # default
Username Mapping (Optional)¶
If OIDC usernames don't match SAP usernames, create a mapping file:
Client Configuration¶
VS Code (with OAuth)¶
VS Code supports MCP OAuth natively. Configure in .vscode/mcp.json:
VS Code will:
1. Discover the Protected Resource Metadata at /.well-known/oauth-protected-resource
2. Find the Authorization Server (your IdP)
3. Open browser for OAuth login
4. Send Bearer tokens automatically
Microsoft Copilot Studio / Power Automate¶
Copilot Studio uses Power Automate custom connectors to connect to MCP servers. The connector handles OAuth token acquisition automatically.
Step 1: Create Custom Connector¶
- Go to Power Automate → Custom connectors → New custom connector → Create from blank
- General tab:
- Connector name:
ARC-1 SAP MCP - Host:
your-arc1-server.cfapps.us10-001.hana.ondemand.com(or your server hostname) - Base URL:
/
Step 2: Configure Security Tab¶
- Security tab → Authentication type: OAuth 2.0
- Identity Provider: Azure Active Directory
- Enable Dienstprinzipal-Unterstützung (Service Principal support)
- Client ID:
{client-id}(from Entra ID app registration) - Client secret:
{client-secret}(from Entra ID app registration) - Authorization URL:
https://login.microsoftonline.com - Tenant ID:
{tenant-id}(your actual tenant ID — NOTcommon) - Resource URL:
{client-id}(the raw GUID, NOTapi://...) - Scope:
api://{client-id}/access_as_user offline_access
⚠️ Critical: The Tenant ID must be your actual tenant GUID, not
common. Usingcommonfails for single-tenant apps.⚠️ Critical: The Resource URL must be the raw client ID GUID (e.g.,
aa34a3d1-...), not theapi://URI. When an app requests a token for itself, Entra ID requires the GUID format.
Step 3: Create Definition¶
- Definition tab → Create an action:
- Summary:
InvokeServer - Operation ID:
InvokeServer - Verb: POST
- URL:
https://your-arc1-server.example.com/mcp - Click Connector aktualisieren (Update Connector)
Step 4: Add Redirect URI to Entra ID¶
- After creating the connector, copy the Umleitungs-URL (Redirect URL) shown at the bottom of the Security tab
- It looks like:
https://global.consent.azure-apim.net/redirect/crc25-5farc-2d1-20... - Go to Azure Portal → App registration → Authentication → Add platform → Web
- Add the redirect URI from step 6
- Also add the base:
https://global.consent.azure-apim.net/redirect
Or via Azure CLI:
az ad app update --id {client-id} \
--web-redirect-uris \
"https://global.consent.azure-apim.net/redirect" \
"https://global.consent.azure-apim.net/redirect/your-connector-specific-uri"
Step 5: Create Connection¶
- Click Verbindung erstellen (Create Connection)
- A Microsoft login popup will appear — sign in with your organization account
- Grant the requested permissions (Sign in and read user profile)
Troubleshooting Copilot Studio¶
| Error | Cause | Fix |
|---|---|---|
AADSTS50011 (Reply address mismatch) |
Redirect URI not registered | Add the connector's specific redirect URI to the app registration |
AADSTS90009 (Requesting token for itself) |
Resource URL uses api:// format |
Change Resource URL to raw client ID GUID |
AADSTS90008 (Not consented, must require Graph) |
Missing User.Read permission |
Add Microsoft Graph User.Read and grant admin consent |
AADSTS65001 (Consent not granted) |
App not authorized | Run az ad app permission admin-consent --id {client-id} |
Anmelden nicht möglich (Login not possible) |
Tenant ID is common or Resource URL empty |
Set Tenant ID to actual GUID; set Resource URL to client ID |
| OAuth popup opens/closes immediately | Multiple issues possible | Check Tenant ID, Resource URL, and redirect URI registration |
Manual Token Testing¶
# Get a token from your IdP (example with Azure CLI)
# First, authorize Azure CLI for your app:
az ad app update --id {client-id} --set "api.preAuthorizedApplications=[{\"appId\":\"04b07795-8ddb-461a-bbee-02f9e1bf7b46\",\"delegatedPermissionIds\":[\"your-scope-id\"]}]"
# Login with the scope
az login --scope "api://{client-id}/access_as_user"
# Get a token
TOKEN=$(az account get-access-token --scope "api://{client-id}/access_as_user" --query accessToken -o tsv)
# Test against arc1
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' \
https://your-arc1-server.example.com/mcp
How It Works¶
- MCP client sends request without token
- ARC-1 returns
401withWWW-Authenticate: Bearer resource_metadata="..." - Client fetches Protected Resource Metadata
- Client discovers IdP authorization server
- Client performs OAuth 2.1 Authorization Code + PKCE flow
- Client sends
Authorization: Bearer <jwt>on every request - ARC-1 validates JWT signature via JWKS (cached 1 hour)
- arc1 checks issuer, audience, expiry
- ARC-1 extracts username from configured claim
- Request proceeds (SAP auth still via service account)
Security Notes¶
- JWT signatures are cryptographically verified via JWKS
- JWKS keys are cached for 1 hour (auto-refresh)
- Tokens must have correct issuer AND audience
- ARC-1 never sees user passwords (IdP handles login)
- SAP still uses a shared service account (for per-user SAP auth, add Phase 3)
References¶
- MCP Specification - Authorization — OAuth 2.1 auth for MCP servers
- RFC 9728 - OAuth Protected Resource Metadata — Auto-discovery of authorization servers
- Microsoft Entra ID - App Registrations — Azure AD app setup
- AWS Cognito User Pools — AWS IdP setup
- Keycloak - Creating a Realm — Open-source IdP setup
Next Steps¶
→ Phase 3: Principal Propagation — Per-user SAP authentication