Loading
0xB0Lesson 12 of 15

Authorize remote servers with OAuth

Follow how a client discovers the authorization server, gets a token for one server, and steps up scopes.

25 min 7-question quiz 2 code exercises
By the end of this lesson you can
  • Walk through the MCP authorization flow from 401 to access token
  • Explain why tokens are bound to one server (audience)
  • Request only the scopes needed, and step up when challenged

Remote MCP servers usually protect their tools with OAuth 2.1. The roles:

  • the MCP server is an OAuth resource server - it accepts and checks access tokens,
  • the MCP client is an OAuth client acting for the user,
  • an authorization server signs the user in and issues tokens (it may be run by the same company or a separate identity provider).

Authorization is optional in MCP and meant for HTTP. Local stdio servers instead get credentials from their environment.

Try it

From 401 to an access token

Step through the flow a client follows the first time it connects to a protected server. Predict the steps marked with a question.

Message 1 of 10Predicted 0/0
Browser
MCP client
MCP server
Authorization server

Least privilege and step-up

Clients should ask for the smallest set of scopes: those named in the 401’s scope parameter if present. Later, when the user tries something that needs more - say, writing a file - the server answers 403 with error="insufficient_scope" and the scopes needed. The client then re-authorizes with the union of its previous scopes and the new ones (so it doesn’t lose what it had), and retries a limited number of times.

Client registration: the preferred method is a Client ID Metadata Document - the client_id is an HTTPS URL describing the client. Dynamic Client Registration still works but is deprecated.

step_up.py
granted = {"files:read"}
challenged = {"files:write"}
print(" ".join(sorted(granted | challenged)))
Output
files:read files:write

Key takeaways

  • 401 → resource metadata → authorization server metadata → PKCE authorization with resource → token → Bearer requests.

  • Tokens are bound to one MCP server (audience); servers reject everything else and never pass tokens through.

  • Start with minimal scopes; on 403 insufficient_scope, re-authorize with the union of scopes.

Lesson quiz

7 questions · pass with 5 correct · up to 50 XP

Passing this quiz completes the lesson and keeps your streak going. Questions you miss come back in review sessions later.

Practice: write Python

Write Python in the editor and run it against sample inputs. Python runs locally in your browser using a WebAssembly runtime.

Exercise 1

Check an access token

+25 XP

The first line is this server’s canonical URI. The second line is the scope an operation needs. Each following line describes a request’s token as JSON (or the word none for no token): {"aud": ..., "scopes": [...], "expired": true/false}.

For each, print 401 missing token, 401 wrong audience, 401 expired, 403 insufficient_scope NEEDED, or 200 ok - checking in that order.

  • Every outcome
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Exercise 2

Step up scopes

+25 XP

The first line lists the scopes the client already requested. Each following line is the scope value from a 403 insufficient_scope challenge (space-separated). After each challenge, print the scope set to request next: the union so far, sorted and space-separated.

  • Two step-ups
main.py
Loading editor…

Python runs in a sandboxed browser worker with a 60 second time limit. Its runtime loads from the Pyodide CDN; your code stays in this browser.

Questions about this lesson

Stuck? Ask. Figured something out? Share it. Explaining is one of the best ways to learn.

Loading posts…

Did you like the lesson? 😆👍
Consider a donation to support our work: