Skip to main content

OAuth 2.0 Authentication Flow

This guide explains the complete OAuth 2.0 authentication flow used to authorize AI agents to access patient health records through the MCP Server.

Overview

The MCP Server implements OAuth 2.0 with the following features:

  • Dynamic Client Registration
  • Authorization Code Grant with PKCE
  • Refresh Token Rotation
  • Token Revocation
  • OAuth Discovery

Step-by-Step Guide

Discover OAuth endpoints automatically instead of hardcoding URLs.

Request:

GET /.well-known/oauth-authorization-server

Response:

{
"issuer": "https://api.healthex.io",
"authorization_endpoint": "https://api.healthex.io/oauth/authorize",
"token_endpoint": "https://api.healthex.io/oauth/token",
"revocation_endpoint": "https://api.healthex.io/oauth/revoke",
"registration_endpoint": "https://api.healthex.io/oauth/register",
"scopes_supported": ["patient/*.read", "offline_access"],
"code_challenge_methods_supported": ["S256"]
}

Why Discovery?

  • Future-proof against endpoint changes
  • Automatically discover supported features
  • Standard OAuth 2.0 best practice

Save these URLs:

  • registration_endpoint - For Step 2 (client registration)
  • authorization_endpoint - For Step 3 (authorization request)
  • token_endpoint - For Step 7 (token exchange)

Step 2: Client Registration

Register your AI agent to obtain credentials.

Request:

POST /oauth/register
Content-Type: application/json

{
"client_name": "Claude AI Integration",
"redirect_uris": ["https://claude.ai/oauth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "patient/*.read offline_access"
}

Response:

{
"client_id": "550e8400-e29b-41d4-a716-446655440000",
"client_id_issued_at": 1698765432,
"client_secret_expires_at": 0,
"redirect_uris": ["https://claude.ai/oauth/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "patient/*.read offline_access"
}

Key Points:

  • token_endpoint_auth_method: "none" indicates a public client (requires PKCE)
  • offline_access scope enables refresh tokens
  • Save the client_id for subsequent requests

Step 3: Generate PKCE Parameters

PKCE (Proof Key for Code Exchange) prevents authorization code interception.

Generate Code Verifier:

// Generate 128 bytes of random data
const codeVerifier = base64URLEncode(crypto.randomBytes(128))
// Example: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

Compute Code Challenge:

// SHA256 hash of code verifier
const hash = crypto.createHash('sha256').update(codeVerifier).digest()
const codeChallenge = base64URLEncode(hash)
// Example: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

Code Challenge Method:

code_challenge_method = "S256"

Step 4: Authorization Request

Redirect the user to the authorization endpoint.

Request:

GET /oauth/authorize?
response_type=code&
client_id=550e8400-e29b-41d4-a716-446655440000&
redirect_uri=https://claude.ai/oauth/callback&
scope=patient/*.read offline_access&
state=random_state_string_xyz&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256

Parameters:

ParameterRequiredDescription
response_typeMust be code
client_idFrom registration
redirect_uriMust match registered URI
scopeSpace-separated scopes
stateRandom string for CSRF protection
code_challengeSHA256 hash of code_verifier
code_challenge_methodMust be S256

Response:

The server redirects to the HealthEx consent page:

https://app.healthex.io/#/patient-consent/{projectId}/enrollment/link?oauth_state=base64_params

The patient:

  1. Logs in or creates a HealthEx account
  2. Reviews what data the AI agent is requesting
  3. Grants or denies consent
  4. Waits while their health records are fetched and processed

During this step, the system:

  • Retrieves patient records from connected EHRs
  • Processes records for search

The authorization code is issued only after this process completes. The patient will see a loading screen during processing.


Step 6: Authorization Code Issued

After consent and data processing, the patient is redirected back to your application:

Redirect:

https://claude.ai/oauth/callback?
code=abc123def456ghi789&
state=random_state_string_xyz

Validate:

// Verify state matches the one you sent
if (receivedState !== sentState) {
throw new Error('CSRF attack detected')
}

Step 7: Token Exchange

Exchange the authorization code for access and refresh tokens.

Request:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=abc123def456ghi789&
redirect_uri=https://claude.ai/oauth/callback&
client_id=550e8400-e29b-41d4-a716-446655440000&
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Parameters:

ParameterRequiredDescription
grant_typeMust be authorization_code
codeAuthorization code from redirect
redirect_uriMust match authorization request
client_idYour client ID
code_verifierOriginal code_verifier (not hash!)

Response:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"scope": "patient/*.read offline_access"
}

Server Validation:

The server verifies:

  1. Authorization code is valid and not expired
  2. redirect_uri matches the stored value
  3. client_id matches the code owner
  4. PKCE verification: SHA256(code_verifier) === stored_code_challenge

If any check fails, an error is returned.


Step 8: Using the Access Token

Include the access token in all MCP requests.

Example:

POST /mcp
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"query": "diabetes medications"
}
}
}

Token Claims:

The JWT access token contains:

{
"sub": "patient-id",
"iss": "https://api.healthex.io",
"aud": "healthex.io",
"exp": 1698769032,
"iat": 1698765432,
"scope": "patient/*.read",
"organizationId": "org-id",
"firstName": "John",
"lastName": "Doe"
}

Step 9: Refresh Token Flow

When the access token expires, use the refresh token to get a new one.

Request:

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6&
client_id=550e8400-e29b-41d4-a716-446655440000

Response:

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4",
"scope": "patient/*.read offline_access"
}

Important:

  • A new refresh token is issued (token rotation)
  • The old refresh token is invalidated
  • Always save the new refresh token

Step 10: Token Revocation

Revoke tokens when the AI agent disconnects or the patient revokes consent.

Request:

POST /oauth/revoke
Content-Type: application/x-www-form-urlencoded

token=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6&
token_type_hint=refresh_token&
client_id=550e8400-e29b-41d4-a716-446655440000

Response:

{
"success": true
}

Error Handling

Common OAuth Errors

Invalid Client:

{
"error": "invalid_client",
"error_description": "Client authentication failed"
}

Invalid Grant:

{
"error": "invalid_grant",
"error_description": "Authorization code has expired or is invalid"
}

Invalid PKCE:

{
"error": "invalid_grant",
"error_description": "PKCE verification failed"
}

Unsupported Grant Type:

{
"error": "unsupported_grant_type",
"error_description": "Grant type not supported"
}

Next Steps