Overview
OAuth2 is the industry-standard protocol for authorization, enabling secure access to resources without sharing credentials. In MCP implementations, OAuth2 provides a robust way to authenticate and authorize clients (such as AI agents) accessing MCP servers and their tools.
This lesson demonstrates how to implement OAuth2 using Spring Boot, acting as both an Authorization Server and a Resource Server.
Project overview
This project is a minimal Spring Boot application that:
Acts as a Spring Authorization Server — issuing JWT access tokens via the client_credentials flow
Acts as a Resource Server — protecting its own /hello endpoint
Client credentials flow Machine-to-machine OAuth2 — no user login required
JWT tokens Stateless, verifiable access tokens with configurable scopes
Azure Container Apps Production-ready deployment with automatic TLS
APIM integration Validate JWTs at the API gateway layer
Quick start (local)
Obtain an access token
curl -u mcp-client:secret \
-d grant_type=client_credentials \
http://localhost:8081/oauth2/token | jq -r .access_token > token.txt
Call the protected endpoint
curl -H "Authorization: Bearer $( cat token.txt)" http://localhost:8081/hello
A successful response returns: Hello from MCP OAuth2 Demo!
Testing the OAuth2 configuration
1. Verify the server is running and secured
# This should return 401 Unauthorized
curl -v http://localhost:8081/
2. Get an access token using client credentials
# Full token response
curl -v -X POST http://localhost:8081/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic bWNwLWNsaWVudDpzZWNyZXQ=" \
-d "grant_type=client_credentials&scope=mcp.access"
# Extract just the token (requires jq)
curl -s -X POST http://localhost:8081/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Authorization: Basic bWNwLWNsaWVudDpzZWNyZXQ=" \
-d "grant_type=client_credentials&scope=mcp.access" \
| jq -r .access_token > token.txt
The Basic Authentication header bWNwLWNsaWVudDpzZWNyZXQ= is the Base64 encoding of mcp-client:secret.
3. Access the protected endpoint
# Using the saved token
curl -H "Authorization: Bearer $( cat token.txt)" http://localhost:8081/hello
# Or inline with the token value
curl -H "Authorization: Bearer eyJra...token_value...xyz" http://localhost:8081/hello
Container deployment
docker build -t mcp-oauth2-demo .
docker run -p 8081:8081 mcp-oauth2-demo
Deploy to Azure Container Apps
az containerapp up -n mcp-oauth2 \
-g demo-rg -l westeurope \
--image < your-registr y > /mcp-oauth2-demo:latest \
--ingress external --target-port 8081
The ingress FQDN becomes your issuer (https://<fqdn>). Azure provides a trusted TLS certificate automatically for *.azurecontainerapps.io.
Wire into Azure API Management
Add this inbound policy to your API to validate JWTs at the gateway:
< inbound >
< validate-jwt header-name = "Authorization" >
< openid-config url = "https://<fqdn>/.well-known/openid-configuration" />
< audiences >
< audience > mcp-client </ audience >
</ audiences >
</ validate-jwt >
< base />
</ inbound >
APIM fetches the JWKS automatically from the OpenID configuration URL and validates every inbound request — no code changes required on the MCP server.
How the flow works
MCP Client
│
├── POST /oauth2/token (client_credentials)
│ Authorization: Basic mcp-client:secret
│
▼
Spring Authorization Server
│
├── Issues JWT access token (signed, scoped to mcp.access)
│
▼
MCP Client
│
├── GET /hello
│ Authorization: Bearer <jwt>
│
▼
Spring Resource Server
│
└── Validates JWT → returns response
Security considerations
Never commit client secrets to source control. Use environment variables or Azure Key Vault for storing the secret value in production.
Use HTTPS everywhere Always use HTTPS in production to protect tokens in transit
Short token lifetimes Configure short-lived access tokens and use refresh tokens appropriately
Scope validation Validate the scope claim in the JWT on every resource server request
Rotate secrets Regularly rotate client secrets and use managed identities where possible