Skip to main content

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)

1

Build and run the server

./mvnw spring-boot:run
2

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
3

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-registry>/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

Build docs developers (and LLMs) love