Rubric
Contents — domains, guide and mocks

Finding authentication and authorization gaps

CCAR-P 3.214 min read · checked 21 September 2026

Task statementAnalyze authentication and authorization requirements to identify security gaps

Four hops, four different credentials

  1. User → your appSSO session identifies the person
  2. App → Claude APIAPI key held server-side only
  3. Agent → MCP serveruser’s token, issued for this server
  4. Server → backendits own token — never forwarded
Each arrow is a separate trust decision. A gap at any one of them — a shared account, a forwarded token, a key the agent can read — undoes the controls at the others.

Authentication, authorization, and “on whose behalf?”

Authentication answers who is making a request; authorization answers what that party may do. In ordinary web apps the two are close together. In an agent system they drift apart, because the thing actually calling the database is neither the user nor your code — it is a tool invoked because a model decided to. So the architect’s core question at every hop is: on whose behalf is this call made, and does the system receiving it know that for certain?

Three facts shape the answer. First, the model can be influenced by the content it reads — Anthropic’s secure-deployment guide calls this prompt injection and recommends defence in depth rather than trusting the model to refuse. Second, a tool’s arguments are written by the model, so anything security-relevant in them (a customer ID, an account number, a file path) is untrusted input. Third, whatever credential a tool runs with is the ceiling of what an injected instruction can achieve. Authorization therefore has to be enforced by systems outside the model, using identity the model cannot forge.

HopWho must be identifiedCredentialTypical gap
User → applicationThe personSSO / sessionAgent endpoint left unauthenticated for a demo
Application → Claude APIYour organisationAPI keyKey shipped in a browser or mobile app
Agent → MCP server or toolThe user the agent acts forOAuth access token for that serverOne shared service account for every user
Tool server → backend APIThe tool serverThe server’s own upstream tokenForwarding the token it received (token passthrough)

How MCP does it: OAuth 2.1, bound to one server

For remote MCP servers over HTTP, the MCP specification builds authorization on OAuth 2.1. The MCP server is an OAuth resource server; the MCP client (Claude Code, a Claude app, or your own agent) is an OAuth client acting for a user; a separate authorization server signs the user in and issues tokens. Authorization is optional in MCP, and for local servers on the STDIO transport the specification says not to use this flow but to take credentials from the environment instead.

An MCP client connecting to a protected server

MCP client
MCP server
Auth server
User
Step 1: MCP client to MCP server: Request with no token
Step 2: MCP server to MCP client: 401 + resource_metadata URL
Step 3: MCP client to MCP server: Fetch protected-resource metadata
Step 4: MCP client to Auth server: Fetch auth-server metadata
Step 5: MCP client to User: Sign-in link: PKCE + resource
Step 6: User to Auth server: Signs in and consents
Step 7: Auth server to MCP client: Token, audience = this server
Step 8: MCP client to MCP server: Request + Authorization: Bearer
Step 9: MCP server : Validate audience and scopes
The two details auditors look for: PKCE and a resource parameter on the way in, and an audience check on the server before it does anything.

The specification’s hard requirements are the checklist for any review. Clients must use PKCE, must send the resource parameter naming the MCP server in both the authorization and token requests, and must send the token in the Authorization header — never in the URL query string. Servers must publish Protected Resource Metadata so clients can find the authorization server, must validate that each token was issued for them, must answer invalid or expired tokens with 401, and must not accept or pass on tokens issued for anything else. Missing permissions get a 403 with error="insufficient_scope" and the scopes needed, which lets a client step up to wider access only when an operation needs it.

The gaps that recur in agent designs

Most review findings fall into a handful of patterns. Use them as a checklist against any architecture diagram you are given.

Reviewing a hospital’s appointment assistant

  • Passes: Patients sign in via the portal’s SSOthe person is authenticated
  • Fails: Tools call the scheduling API as one service accountevery patient gets every patient’s access
  • Fails: get_appointments takes patient_id from the modelan injected ID reaches other records
  • Check: System prompt says “only discuss the user’s own bookings”useful, but not a control
  • Fails: Service account holds records:* scopefar wider than scheduling needs
  • Missing: Per-user audit trail of tool callscannot show who saw what
The brief: patients chat to book, move or cancel their own appointments. Three findings let one patient reach another’s records; two widen the damage if anything leaks.
GapWhy it mattersClose it by
Shared service account behind the agentThe agent can do anything any user could; users inherit each other’s reachDelegated, per-user tokens; the backend applies that user’s permissions
Identity taken from tool argumentsAn injected instruction can name another user or accountBind identity from the authenticated session on the server side
Token passthroughDownstream checks bypassed; audit trail wrong; replay across servicesThe tool server obtains its own upstream token
Omnibus scopes (*, full-access)A leaked token opens everything; revocation breaks every workflowMinimal starting scopes; step up with insufficient_scope challenges
Secrets inside the agent’s environmentAnything the agent can read, an injection can try to exfiltrateInject credentials at a proxy outside the agent’s boundary
Prompt text as the access policyProbabilistic, and overridable by content the model readsEnforce in code and permissions; keep the prompt as a second layer
Session ID treated as proof of identityA guessed or stolen ID impersonates the userVerify every request; bind sessions to the user ID from the token
Identity comes from the session, never from the modelpython
def get_appointments(tool_input: dict, session: Session) -> dict:
    # tool_input is written by the model: treat it as untrusted.
    patient = session.patient            # set by SSO middleware, not by Claude
    if "appointments:read" not in session.scopes:
        return {"is_error": True, "content": "Missing scope appointments:read"}

    appts = scheduling_api.list(
        patient_id=patient.id,           # ignore any patient_id Claude supplies
        token=session.scheduling_token,  # this user's token, for this API only
        after=tool_input.get("after"),
    )
    return {"content": [{"date": a.date, "clinic": a.clinic} for a in appts]}

Choosing the right credential pattern

Which credential pattern fits this call?

Who is the agent acting for here?
  • A signed-in user
    Delegated OAuth tokenper user, audience-bound
  • No user — a scheduled job
    Service identitynarrow scopes, own audit trail
  • A local STDIO tool
    Credentials from envsandboxed, least privilege
  • Agent must not see secret
    Proxy injects itoutside the agent’s boundary

The proxy pattern deserves a closer look because it closes a gap the others do not. Anthropic’s secure-deployment guide recommends running a proxy outside the agent’s security boundary that adds credentials to outgoing requests: the agent never sees the key, the proxy can enforce an allowlist of destinations, and every request is logged in one place. It also warns that even a read-only mount of a code directory can expose .env files, cloud credentials and private keys, so those should be excluded before the agent sees the directory.

Where the API does the MCP call for you, the responsibilities move. With the Claude API’s MCP connector, your application runs the OAuth flow, obtains and refreshes the token, and passes it in the authorization_token field; Anthropic’s servers then call the MCP server. The documentation also notes the connector is not covered by zero-data-retention arrangements — a data-handling question to raise in the same review (compliance itself is 5.4).

Closing gaps in Claude Code and the Agent SDK

When the agent is Claude Code or built on the Agent SDK, the same thinking applies to its own permissions. Claude Code evaluates permission rules in a fixed order — deny, then ask, then allow — and the first match wins, so an allow rule cannot carve an exception out of a deny rule. Organisations can put rules in managed settings that users cannot override, and can disable the bypass-permissions mode there. Remote MCP servers authenticate through OAuth with /mcp, and project-scoped servers from a checked-in .mcp.json need approval before first use.

Managed settings: deny first, and keep bypass mode offjson
{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)",
      "Bash(curl *)",
      "mcp__prod-db"
    ],
    "disableBypassPermissionsMode": "disable"
  }
}

Traps the wrong answers are built from

Tempting but wrongDo this instead
Relying on the system prompt to keep users to their own dataEnforce access in the tool server or backend using the authenticated user’s identity.
One shared service account behind an agent that serves many usersUse delegated per-user OAuth tokens so existing permissions apply.
Taking customer_id or user_id from the model’s tool argumentsBind identity from the session server-side; treat tool input as untrusted.
An MCP server forwarding the client’s token to a downstream APIValidate the token’s audience, then call downstream with the server’s own credential.
Requesting every scope up front “to avoid prompts later”Start with minimal scopes and step up on an insufficient_scope challenge.

You should now be able to

  • Trace identity and credentials across each hop of a Claude integration and name the gap at each.
  • Explain the MCP authorization model: resource server, client, authorization server, PKCE and audience-bound tokens.
  • Identify token passthrough, confused-deputy and session-hijack risks in an MCP server design.
  • Choose between delegated user tokens, service identities, environment credentials and proxy injection.
  • Recognise when a control is only in the prompt and move it into code, permissions or infrastructure.
  • Apply Claude Code permission precedence and managed settings to close gaps for a team.

Practice questions

Original questions written for this lesson, in the exam’s style. Answer first, then open the reasoning — every option is explained, including why the wrong ones are tempting.

  1. Question 1

    A telecom support agent has a get_account tool that takes a customer_id argument and queries billing with a privileged service account. In testing, a message pasted into the chat convinces the agent to look up a different customer’s account.

    Which change closes the gap most reliably?

    1. AAdd a system-prompt rule forbidding lookups for other customers.
    2. BRun an output classifier that blocks replies containing other names.
    3. CDerive the customer from the signed-in session inside the tool server.
    4. DSwitch to a larger model that resists prompt injection better.
    Show answer and reasoning
    1. AIncorrect. It may reduce the rate of failure, but an instruction is not an access control and can be overridden by injected text.
    2. BIncorrect. A useful extra layer, but the data was already fetched; the tool still has access it should not.
    3. CCorrect. Identity the model cannot write is the fix: the tool ignores the argument and uses the authenticated user, so no prompt can widen access.
    4. DIncorrect. Better resistance lowers the odds, but the privileged account and model-supplied ID remain; defence belongs outside the model.
  2. Question 2

    You are reviewing the design of a remote MCP server that fronts a company’s HR system. The design notes list its authorization behaviour.

    Which two behaviours are security gaps? (Select 2.)

    1. AIt forwards the bearer token it receives on to the HR system’s API.
    2. BIt accepts any valid token from the corporate identity provider.
    3. CIt returns 403 with insufficient_scope when a write needs more scope.
    4. DIt requires the access token in the Authorization header.
    5. EIts clients use PKCE and send the resource parameter.
    Show answer and reasoning
    1. ACorrect. This is token passthrough, which the MCP specification forbids; the server must use its own upstream credential.
    2. BCorrect. Without an audience check it accepts tokens issued for other services; servers must validate that tokens were issued for them.
    3. CIncorrect. That is the specified step-up behaviour, and it supports least privilege.
    4. DIncorrect. Correct per the specification; tokens must not be sent in the query string.
    5. EIncorrect. Both are client requirements in the specification, not gaps.
  3. Question 3

    A platform team runs the Agent SDK in CI to triage pull requests from external contributors. The container has a GitHub token and a cloud API key in environment variables so the agent can comment and query build logs.

    What is the most important change?

    1. AHave a proxy outside the container inject credentials into the agent’s calls.
    2. BRotate both credentials weekly so any leaked copy expires quickly.
    3. CInstruct the agent in its prompt never to print environment variables.
    4. DMove both secrets out of the environment into a read-only .env file.
    Show answer and reasoning
    1. ACorrect. The agent processes untrusted content; keeping secrets outside its boundary means an injection cannot read or exfiltrate them.
    2. BIncorrect. Rotation limits how long a leak lasts but leaves the secrets readable by the agent on every run.
    3. CIncorrect. A prompt rule is not a control, and exfiltration need not involve printing anything.
    4. DIncorrect. A read-only file is still readable; the secret stays inside the agent’s boundary.
  4. Question 4

    An organisation’s managed Claude Code settings include the deny rule Bash(aws *). A project’s own settings add the allow rule Bash(aws s3 ls) so developers can list buckets.

    What happens when Claude runs aws s3 ls?

    1. AIt runs, because the more specific allow rule takes precedence.
    2. BIt runs with a prompt, because project settings downgrade deny to ask.
    3. CIt is blocked, because deny rules are evaluated first and win.
    4. DIt is blocked only in auto mode; in manual mode it prompts.
    Show answer and reasoning
    1. AIncorrect. Specificity does not change the order; deny is evaluated first.
    2. BIncorrect. Project settings cannot weaken a managed deny rule.
    3. CCorrect. Rules are checked deny, then ask, then allow, and an allow rule cannot carve an exception out of a deny.
    4. DIncorrect. The deny rule applies regardless of permission mode.

Sources

Drafted with AI assistance and checked against the sources above; expert review is in progress. Spotted an error? Tell us and it gets fixed, dated and listed on how this is written.