online / endpoints 59 / categories 14 / rate 60/min/ip /

GET /.well-known/oauth-protected-resource

GET /.well-known/oauth-protected-resource

RFC 9728 Protected Resource Metadata for the OAuth client-server chaos quartet. Default mode joins the conflicting-discovery group with an issuer that disagrees with the OIDC and AS documents; other modes exercise resource-server-specific flaws (unreachable AS, unregistered bearer methods, mismatched resource identifier).

mode mismatched-issuer (default; authorization_servers points at identity.catastrophic.io but the sibling openid-configuration claims issuer catastrophic.io — joins the conflicting-discovery group), unreachable-as (authorization_servers references nonexistent.invalid; clients walking the chain hang fetching AS metadata), invalid-bearer-methods (bearer_methods_supported lists values not in the IANA OAuth Token Type registry), mismatched-resource-id (resource field doesn't match the URL the document is served from, violating RFC 9728 §3).

control Compare against the well-formed counterpart: not.catastrophic.io/.well-known/oauth-protected-resource side-by-side

build a request:

expect: 200 OK with Content-Type: application/json. X-Chaos-Opr-Mode reflects the selected mode; X-Chaos-Opr-Note explains the flaw. Default mode also sets X-Chaos-Conflict-Group: conflicting-discovery so a quartet-walking agent can identify the contradictory voices.

bash
curl -i 'https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer'
# OAuth Protected Resource Metadata is JSON; just parse and inspect.
# RFC 9728 §3 requires resource == the URL the document was served
# from, so verify that explicitly.
import urllib.request, json

url = "https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer"
raw = urllib.request.urlopen(url).read()
doc = json.loads(raw)
print("resource:               ", doc.get("resource"))
print("authorization_servers:  ", doc.get("authorization_servers"))
print("bearer_methods_supported:", doc.get("bearer_methods_supported"))
print("Matches served URL:     ", doc.get("resource") in url)
const res = await fetch("https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer");
const doc = await res.json();
console.log("Mode:", res.headers.get("X-Chaos-Opr-Mode"));
console.log("Note:", res.headers.get("X-Chaos-Opr-Note"));
console.log("resource:", doc.resource);
console.log("AS:      ", doc.authorization_servers);
console.log("bearer:  ", doc.bearer_methods_supported);
package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
)

func main() {
    resp, _ := http.Get("https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer")
    defer resp.Body.Close()
    raw, _ := io.ReadAll(resp.Body)
    var doc map[string]any
    json.Unmarshal(raw, &doc)
    fmt.Println("Mode:    ", resp.Header.Get("X-Chaos-Opr-Mode"))
    fmt.Println("resource:", doc["resource"])
    fmt.Println("AS:      ", doc["authorization_servers"])
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
//             serde_json = "1"
fn main() -> Result<(), Box> {
    let doc: serde_json::Value = reqwest::blocking::get("https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer")?.json()?;
    println!("resource:               {}", doc["resource"]);
    println!("authorization_servers:  {}", doc["authorization_servers"]);
    println!("bearer_methods_supported: {}", doc["bearer_methods_supported"]);
    Ok(())
}
// Requires Jackson.
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.*;

public class OauthProtectedResource {
    public static void main(String[] args) throws Exception {
        var client = HttpClient.newHttpClient();
        var req = HttpRequest.newBuilder(URI.create("https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer")).build();
        var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        var doc = new ObjectMapper().readTree(resp.body());
        System.out.println("Mode:     " + resp.headers().firstValue("X-Chaos-Opr-Mode").orElse(""));
        System.out.println("resource: " + doc.get("resource"));
        System.out.println("AS:       " + doc.get("authorization_servers"));
    }
}
using System.Text.Json;
using var client = new HttpClient();
var resp = await client.GetAsync("https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer");
var raw = await resp.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(raw);
Console.WriteLine($"Mode:     {resp.Headers.GetValues("X-Chaos-Opr-Mode").First()}");
Console.WriteLine($"resource: {doc.RootElement.GetProperty("resource")}");
Console.WriteLine($"AS:       {doc.RootElement.GetProperty("authorization_servers")}");
require "net/http"
require "json"

url  = "https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer"
resp = Net::HTTP.get_response(URI(url))
doc  = JSON.parse(resp.body)
puts "Mode:     #{resp['X-Chaos-Opr-Mode']}"
puts "resource: #{doc['resource']}"
puts "AS:       #{doc['authorization_servers']}"
puts "Matches URL? #{url.start_with?(doc['resource'])}"
$resp = Invoke-WebRequest -Uri 'https://chaos.catastrophic.io/.well-known/oauth-protected-resource?mode=mismatched-issuer' -SkipHttpErrorCheck
$resp.Headers['X-Chaos-Opr-Mode']
$doc = $resp.Content | ConvertFrom-Json
"resource: $($doc.resource)"
"AS:       $($doc.authorization_servers -join ', ')"
"bearer:   $($doc.bearer_methods_supported -join ', ')"