Move to remote HTTP safely
Authenticate and authorize requests
Protect a remote server by validating who sent a token, who it is for, when it expires, and what it permits.
Our HTTP endpoint currently answers anyone who finds the URL. For practice data that’s acceptable. For anything real, it’s the first thing to fix.
Two words get mixed up here, so let’s pin them down. Authentication establishes who the caller is. Authorization decides what that caller may do. You need both, in that order.
The MCP server is a resource server
For remote HTTP, an MCP server acts as an OAuth resource server. It doesn’t log users in. It receives a bearer token that some authorization server issued, verifies it, and decides whether to serve the request.
That check has to happen at the HTTP layer, before MCP dispatch. createMcpHandler() doesn’t read the Authorization header and doesn’t derive a trusted identity for you. If you skip the gate, every request is anonymous.
The SDK gate
The v2 SDK ships a web-standard gate for exactly this:
import { requireBearerAuth } from '@modelcontextprotocol/server'
const gate = requireBearerAuth({
verifier,
requiredScopes: ['notes:read'],
resourceMetadataUrl
})
async function fetchMcp(request: Request) {
const auth = await gate(request)
if (auth instanceof Response) return auth
return handler.fetch(request, { authInfo: auth })
}
Read the flow. gate(request) either returns an error Response or an auth object. If it’s a Response, we return it right away and the SDK never sees the request. Otherwise we pass the verified authInfo into handler.fetch(), where handlers can read it later for per-caller authorization.
The verifier is yours to write, and it’s the important part. It must validate the token’s signature (or introspection result), its issuer, its audience, its expiry, and whether it has been revoked. The SDK gate handles the bearer shape, the expiry, and the required scopes around your verifier. It can’t know which issuer you trust.
Audience is the one people forget
A token can be perfectly valid and still be for a different service. Your verifier must reject a token whose audience isn’t this MCP server.
And the reverse: if your server calls a downstream API, get a separate token for that API. Don’t forward the caller’s token. Passing it through creates a confused deputy, a server that acts with authority it was never meant to have.
Status codes and discovery
Return 401 when the token is missing or invalid. Return 403 when the identity is fine but the scope isn’t. Those two numbers tell a client whether to get a token or to ask for more permission.
Also advertise protected resource metadata at the URL you passed as resourceMetadataUrl. Compatible clients use it to discover which authorization server to talk to.
Hygiene
Use HTTPS, always. Keep tokens out of URLs, source code, analytics, tool results, and logs. A token in a log file is a token someone else can use.
The practice dataset in this course is public only because every note is fake. The moment you swap in private notes, authorization stops being a follow-up task and becomes a release blocker.
Lesson completed