discoverFrom401 method
Resolve a 401 into concrete OAuth endpoints (RFC 9728 + RFC 8414/OIDC).
Orchestrates the MCP 2025-11-25 discovery chain:
- Parse the
WWW-Authenticatechallenge (SEP-985); readresource_metadata=when present, else fall back to the resource origin's.well-known/oauth-protected-resource(SEP-985 fallback). - Fetch the PRM document and read
authorization_servers(RFC 9728). - Discover the AS via RFC 8414 + OIDC (PR#797).
- Compute step-up scopes from the challenge
scope=(SEP-835), falling back to the PRMscopes_supported.
On success the client's cached AS metadata is set so the existing PKCE/token flow targets the discovered endpoints — reusing all existing crypto. Returns the resolved OAuthDiscoveryResult.
Implementation
Future<OAuthDiscoveryResult> discoverFrom401({
required String resourceUrl,
String? wwwAuthenticate,
}) async {
final challenge = WwwAuthenticateChallenge.parse(wwwAuthenticate);
final prmUrl = challenge?.resourceMetadata != null
? Uri.parse(challenge!.resourceMetadata!)
: wellKnownProtectedResourceUrl(resourceUrl);
final prm = await fetchProtectedResourceMetadata(prmUrl);
final authServer = prm.primaryAuthorizationServer;
if (authServer == null) {
throw OAuthError(
error: 'no_authorization_server',
errorDescription:
'Protected Resource Metadata lists no authorization_servers',
);
}
final asMetadata = await discoverAuthorizationServer(authServer);
final challengeScopes = challenge?.scopes ?? const <String>[];
final stepUpScopes = challengeScopes.isNotEmpty
? challengeScopes
: (prm.scopesSupported ?? const <String>[]);
return OAuthDiscoveryResult(
challenge: challenge,
protectedResource: prm,
authServerMetadata: asMetadata,
stepUpScopes: stepUpScopes,
);
}