discoverFrom401 method

Future<OAuthDiscoveryResult> discoverFrom401({
  1. required String resourceUrl,
  2. String? wwwAuthenticate,
})

Resolve a 401 into concrete OAuth endpoints (RFC 9728 + RFC 8414/OIDC).

Orchestrates the MCP 2025-11-25 discovery chain:

  1. Parse the WWW-Authenticate challenge (SEP-985); read resource_metadata= when present, else fall back to the resource origin's .well-known/oauth-protected-resource (SEP-985 fallback).
  2. Fetch the PRM document and read authorization_servers (RFC 9728).
  3. Discover the AS via RFC 8414 + OIDC (PR#797).
  4. Compute step-up scopes from the challenge scope= (SEP-835), falling back to the PRM scopes_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,
  );
}