MCP Forge

How to add authentication to your MCP server

A practical guide. Updated 2026.

If your MCP server is reachable over the network and does not check who is calling, you have an open door. A 2026 review of around 7,000 public MCP servers found that 41 percent require no authentication at all, and only 8.5 percent use OAuth. With prompt injection a real threat, the server has to enforce identity. The model cannot be trusted to do it.

Here are the three approaches that actually work, from simplest to most robust.

Option 1: a shared bearer token

The fastest secure baseline. You generate one long random secret, give it to your client, and the server checks every request for it. The only subtlety that people get wrong: compare the token in constant time, so an attacker cannot recover it one byte at a time through timing.

import { timingSafeEqual } from "node:crypto";

function checkBearer(header, expected) {
  const m = /^Bearer\s+(.+)$/i.exec(header ?? "");
  if (!m) return false;
  const a = Buffer.from(m[1]), b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

Good for a single client or internal use. The downside is that one secret unlocks everything, and rotating it means updating every client at once.

Option 2: signed JWTs (HS256 or RS256)

When you have multiple users or want tokens that expire on their own, move to JWTs. Each token carries a signed payload with a subject and an expiry. The server verifies the signature and the expiry, then trusts the claims inside. With HS256 you share one secret. With RS256 you verify against a public key (or a JWKS endpoint from your identity provider), which is the right choice once real users are involved.

// verify an HS256 token: split, check alg, recompute the signature, check exp
const [h, p, sig] = token.split(".");
const expected = base64url(hmacSha256(secret, `${h}.${p}`));
if (!constantTimeEqual(sig, expected)) reject("bad signature");
if (payload.exp && Date.now()/1000 > payload.exp) reject("expired");

The trap here is verifying nothing and trusting the payload, or accepting an alg: none token. Always pin the algorithm you expect.

Option 3: full OAuth

For a product with many users, delegate identity to an OAuth provider (Auth0, Clerk, Cognito, your own). Your server validates the access token against the provider's JWKS, exactly like RS256 above. The call site does not change. Only the key source does.

The rule that ties it together: fail closed

The most common real-world mistake is a server that runs wide open when auth is misconfigured. Make the server refuse to start in production if no auth is set. A missing secret should be a crash on boot, never a silent "allow everyone."

Check what you have right now (free)

mcp-audit scans your MCP config and flags unauthenticated remote servers, plaintext secrets, and more. Zero dependencies, runs locally.

pipx install git+https://github.com/alih552/mcp-audit
mcp-audit
mcp-audit on GitHub

Or start from a server that already does all of this

MCP Forge Kit ships bearer and JWT auth wired up, fail-closed in production, plus SSRF-safe fetch, rate limiting, validation, tests, and CI. A secure base for your own tools.

Get MCP Forge Kit, €39

Related: The MCP Server Security Checklist