# How to Configure OAuth Authentication with Dynamic Client Registration in MCP Memory Service

> Configure OAuth authentication with dynamic client registration in MCP Memory Service. Use the POST /oauth/register endpoint for client registration and automatic credential generation.

- Repository: [Henry/mcp-memory-service](https://github.com/doobidoo/mcp-memory-service)
- Tags: how-to-guide
- Published: 2026-02-28

---

**Enable OAuth 2.1 in MCP Memory Service by setting `MCP_OAUTH_ENABLED=true`, configure your storage backend via environment variables, and use the RFC 7591-compliant `POST /oauth/register` endpoint to dynamically register clients with automatic credential generation.**

The MCP Memory Service repository (`doobidoo/mcp-memory-service`) ships with a complete, production-ready **OAuth 2.1** implementation that supports **dynamic client registration** according to RFC 7591. This optional security layer activates only when explicitly enabled through environment configuration, providing automated client provisioning, JWT token issuance, and Bearer token validation without requiring manual client credential management.

## Prerequisites and Environment Configuration

All OAuth behavior originates in [`src/mcp_memory_service/config.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/src/mcp_memory_service/config.py), which reads environment variables at startup to construct the issuer URL, generate cryptographic keys, and select storage backends.

Set these required variables to activate the system:

```bash
export MCP_OAUTH_ENABLED=true
export MCP_OAUTH_ISSUER=https://api.example.com
export MCP_OAUTH_STORAGE_BACKEND=sqlite
export MCP_OAUTH_SQLITE_PATH=./data/oauth.db

```

The service automatically generates an RSA key-pair on startup if `MCP_OAUTH_PRIVATE_KEY` and `MCP_OAUTH_PUBLIC_KEY` are omitted (see lines 49-84 in [`config.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/config.py)). For symmetric signing only, omit the RSA variables and the system will fall back to `HS256` using a generated symmetric secret.

## OAuth 2.1 Architecture Overview

The implementation spans seven core components that handle discovery, registration, storage, and request validation:

- **[`oauth/discovery.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/discovery.py)**: Exposes `.well-known` endpoints (`/oauth-authorization-server/mcp` and `/openid-configuration/mcp`) returning `OAuthServerMetadata` containing the registration URL
- **[`oauth/registration.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/registration.py)**: Implements `POST /oauth/register` for dynamic client registration, validating `ClientRegistrationRequest` payloads and returning `ClientRegistrationResponse` with generated credentials
- **[`oauth/models.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/models.py)**: Defines Pydantic schemas for registration requests, server metadata, and the internal `RegisteredClient` representation used by storage layers
- **[`oauth/storage/factory.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/storage/factory.py)**: Creates storage backends based on `MCP_OAUTH_STORAGE_BACKEND` (accepts `"memory"` or `"sqlite"`)
- **[`oauth/storage/memory.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/storage/memory.py)**: In-memory dev/testing backend with volatile storage
- **[`oauth/storage/sqlite.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/storage/sqlite.py)**: Persistent production backend for registered clients and tokens
- **[`oauth/middleware.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/middleware.py)**: FastAPI dependency that validates `Authorization: Bearer` headers, supporting both JWT and stored access tokens with fallback to API-key authentication

These components are wired into the FastAPI application in [`src/mcp_memory_service/web/app.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/src/mcp_memory_service/web/app.py), which mounts the `/oauth/*` routers and integrates the authentication middleware into the request pipeline.

## Step-by-Step Configuration

### Enable OAuth via Environment Variables

The [`config.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/config.py) module exposes helper functions including `get_jwt_algorithm()`, `get_jwt_signing_key()`, and `get_jwt_verification_key()` that depend on your environment configuration. When `MCP_OAUTH_ENABLED` is `true`, the system derives the `OAUTH_ISSUER` from the server host/port unless overridden by `MCP_OAUTH_ISSUER` (lines 121-131).

For reverse-proxy deployments, explicitly set the issuer to your external URL:

```bash
export MCP_OAUTH_ISSUER=https://api.example.com
export MCP_OAUTH_PRIVATE_KEY="$(cat private.pem)"
export MCP_OAUTH_PUBLIC_KEY="$(cat public.pem)"

```

### Configure Storage Backends

The storage factory in [`oauth/storage/factory.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/storage/factory.py) (lines 31-70) instantiates the appropriate backend based on the `MCP_OAUTH_STORAGE_BACKEND` variable:

- **`memory`**: Non-persistent storage suitable for development and testing
- **`sqlite`**: File-based persistence for production environments

The SQLite backend stores `RegisteredClient` objects including `client_id`, `client_secret`, redirect URIs, and grant types, while the memory backend maintains identical behavior but loses data on process termination.

### Set Up Discovery Endpoints

Before registering clients, verify the discovery endpoints are accessible. The [`discovery.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/discovery.py) module (lines 31-56) exposes metadata at:

```

GET /.well-known/oauth-authorization-server/mcp
GET /.well-known/openid-configuration/mcp

```

These return JSON containing the `issuer`, `authorization_endpoint`, `token_endpoint`, and `registration_endpoint` URLs required by OAuth 2.1 clients.

## Dynamic Client Registration Workflow

### The Registration Endpoint

The dynamic registration implementation lives in [`oauth/registration.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/registration.py) (lines 1-55, with validation logic at lines 45-99). Clients submit a `ClientRegistrationRequest` to `POST /oauth/register` with their metadata:

```bash
curl -X POST https://api.example.com/oauth/register \
     -H "Content-Type: application/json" \
     -d '{
           "redirect_uris": ["https://myapp.example.com/callback"],
           "grant_types": ["authorization_code"],
           "response_types": ["code"],
           "client_name": "My Application",
           "token_endpoint_auth_method": "client_secret_basic"
         }'

```

### Validating Client Metadata

The registration endpoint validates **redirect URIs** via `validate_redirect_uris`, checks supported **grant types** and **response types**, and ensures the requested authentication method is supported. Upon validation, it calls `get_oauth_storage().generate_client_id()` and `generate_client_secret()` to create unique credentials.

### Storage and Persistence

The storage backend persists a `RegisteredClient` model (defined in [`models.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/models.py) lines 70-83) containing the generated credentials and client metadata. For SQLite backends, this creates a durable record in `oauth.db`; for memory backends, the client exists only for the process lifetime.

The endpoint returns a `ClientRegistrationResponse` JSON object:

```json
{
  "client_id": "c2a1f9e7-5c3b-4a12-9f7b-2c1e4d5f6a78",
  "client_secret": "a1b2c3d4e5f6...",
  "redirect_uris": ["https://myapp.example.com/callback"],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "client_secret_basic",
  "client_name": "My Application"
}

```

## Authenticating Requests

### Token Validation Middleware

Once registered, clients obtain access tokens (handled in [`authorization.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/authorization.py) and [`token.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/token.py) elsewhere in the codebase) and present them via the `Authorization: Bearer <token>` header.

The [`oauth/middleware.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/middleware.py) dependency (lines 1-45, with validation at lines 83-132) processes each request:

1. Extracts the Bearer token from the header
2. Attempts JWT validation using `validate_jwt_token` with issuer, audience, and claim verification
3. Falls back to storage lookup for stored access tokens (e.g., refresh token flows)
4. Returns an `AuthenticationResult` object with `client_id`, `scope`, and authentication method

If validation fails, the middleware returns a `401` error with an RFC 6750-compatible error document. If `ALLOW_ANONYMOUS_ACCESS` is configured, unauthenticated requests may proceed with limited privileges.

## Practical Implementation Examples

### Complete Environment Setup

Configure a production-ready instance with SQLite persistence and external issuer:

```bash

# .env file

MCP_OAUTH_ENABLED=true
MCP_OAUTH_STORAGE_BACKEND=sqlite
MCP_OAUTH_SQLITE_PATH=./data/oauth.db
MCP_OAUTH_ISSUER=https://api.example.com
MCP_OAUTH_PRIVATE_KEY="$(cat /secrets/oauth-private.pem)"
MCP_OAUTH_PUBLIC_KEY="$(cat /secrets/oauth-public.pem)"

```

### Dynamic Client Registration

Register a new client and capture the credentials:

```bash
RESPONSE=$(curl -s -X POST https://api.example.com/oauth/register \
  -H "Content-Type: application/json" \
  -d '{
    "redirect_uris": ["https://app.example.com/oauth/callback"],
    "grant_types": ["authorization_code", "refresh_token"],
    "response_types": ["code"],
    "client_name": "Production App",
    "scope": "memory:read memory:write"
  }')

CLIENT_ID=$(echo $RESPONSE | jq -r '.client_id')
CLIENT_SECRET=$(echo $RESPONSE | jq -r '.client_secret')

```

### Accessing Protected Endpoints

Use the registered credentials to obtain a token (via the authorization flow or token endpoint), then access MCP endpoints:

```bash
curl https://api.example.com/memory/v1/items \
     -H "Authorization: Bearer $ACCESS_TOKEN" \
     -H "Content-Type: application/json"

```

### Direct Storage Backend Access

For debugging or administrative operations, interact with the storage layer directly:

```python
from mcp_memory_service.web.oauth.storage.factory import create_oauth_storage
import asyncio

async def audit_clients():
    storage = create_oauth_storage(
        backend_type="sqlite", 
        db_path="./data/oauth.db"
    )
    clients = await storage.list_clients()
    for client in clients:
        print(f"Client: {client.client_id} - {client.client_name}")

asyncio.run(audit_clients())

```

This imports the factory function from [`oauth/storage/factory.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/storage/factory.py) and creates a storage instance matching your configuration.

## Summary

- **Enable OAuth** by setting `MCP_OAUTH_ENABLED=true` and configuring `MCP_OAUTH_ISSUER` in your environment
- **Choose storage** via `MCP_OAUTH_STORAGE_BACKEND` (`sqlite` for production, `memory` for testing) as implemented in [`storage/factory.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/storage/factory.py)
- **Register clients dynamically** using `POST /oauth/register` which validates `ClientRegistrationRequest` and returns `client_id`/`client_secret` via the [`registration.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/registration.py) endpoint
- **Authenticate requests** using Bearer tokens validated by the middleware in [`oauth/middleware.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/middleware.py), which supports JWT (RS256/HS256) and stored token lookup
- **Configure keys** manually via environment variables or allow automatic RSA key generation in [`config.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/config.py) lines 49-84

## Frequently Asked Questions

### What OAuth 2.1 flows does MCP Memory Service support?

According to the [`oauth/registration.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/registration.py) source code, the service validates **authorization_code** and **refresh_token** grant types through the `ClientRegistrationRequest` model. The token endpoint (implemented separately in [`authorization.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/authorization.py) and [`token.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/token.py)) issues JWT access tokens signed with RS256 (when RSA keys are configured) or HS256 (fallback symmetric signing), supporting the standard authorization code flow with PKCE and refresh token rotation.

### How does dynamic client registration differ from static client configuration?

**Dynamic client registration** (RFC 7591) allows OAuth clients to self-register at runtime via `POST /oauth/register` without manual intervention. The [`registration.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/registration.py) endpoint automatically generates `client_id` and `client_secret` through the storage backend's `generate_client_id()` and `generate_client_secret()` methods, persists the `RegisteredClient` model, and returns credentials immediately. Static configuration would require pre-provisioning these values in a database or configuration file before deployment.

### Can I use different storage backends for OAuth clients and memory data?

Yes. The OAuth storage system is decoupled from the main memory service storage. Set `MCP_OAUTH_STORAGE_BACKEND` independently to choose between `memory` (volatile, dev-only) or `sqlite` (persistent). The factory in [`oauth/storage/factory.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/storage/factory.py) creates isolated instances, so you can run OAuth with SQLite persistence while keeping memory data in a different backend. For production, always use `sqlite` to prevent losing registered client credentials on restart.

### How do I troubleshoot authentication failures in the middleware?

Check the [`oauth/middleware.py`](https://github.com/doobidoo/mcp-memory-service/blob/main/oauth/middleware.py) validation logic (lines 83-132). The middleware first attempts `validate_jwt_token` which verifies the JWT signature against `get_jwt_verification_key()`, checks the issuer matches `OAUTH_ISSUER`, and validates required claims. If JWT validation fails, it falls back to looking up the token as a stored access token in your configured backend. Enable detailed logging to see whether failures occur during signature verification (indicating key mismatches) or during storage lookup (indicating expired or unknown tokens).