siyuan-note/siyuan · error

OAuth protected resource metadata has no authorization serve

Error message

OAuth protected resource metadata has no authorization server

What it means

Returned at oauth.go:506-507 when a protected-resource metadata document was successfully fetched but its authorization_servers array is empty. RFC 9728 requires authorization_servers to point at one or more AS issuers; SiYuan uses prm.AuthorizationServers[0] to look up AS metadata, so an empty list is fatal.

Source

Thrown at kernel/mcp/client/oauth.go:507

	*oauthex.ProtectedResourceMetadata
	MetadataURL string
}

func discoverProtectedResource(ctx context.Context, challenges []oauthex.Challenge, resource string, client *http.Client) (*discoveredProtectedResource, error) {
	metadataURL := ""
	for _, challenge := range challenges {
		if strings.EqualFold(challenge.Scheme, "bearer") && challenge.Params["resource_metadata"] != "" {
			metadataURL = challenge.Params["resource_metadata"]
			break
		}
	}
	for _, candidate := range protectedResourceURLs(metadataURL, resource) {
		prm, err := oauthex.GetProtectedResourceMetadata(ctx, candidate.URL, candidate.Resource, client)
		if err != nil {
			continue
		}
		if len(prm.AuthorizationServers) == 0 {
			return nil, fmt.Errorf("OAuth protected resource metadata has no authorization server")
		}
		return &discoveredProtectedResource{ProtectedResourceMetadata: prm, MetadataURL: candidate.URL}, nil
	}
	return nil, fmt.Errorf("OAuth protected resource metadata not found")
}

func (h *mcpOAuthHandler) validateCredentialIssuer(ctx context.Context, credential oauthCredential) (bool, error) {
	var challenges []oauthex.Challenge
	resource := h.server.URL
	if credential.ResourceMetadataURL != "" {
		challenges = []oauthex.Challenge{{Scheme: "bearer", Params: map[string]string{"resource_metadata": credential.ResourceMetadataURL}}}
		resource = credential.Resource
	}
	prm, err := discoverProtectedResource(ctx, challenges, resource, h.client)
	if err != nil {
		return false, fmt.Errorf("validate OAuth protected resource: %w", err)
	}
	if prm.Resource != credential.Resource || len(prm.AuthorizationServers) == 0 {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. On the resource server, populate authorization_servers in /.well-known/oauth-protected-resource with the AS issuer URL(s).
  2. Verify the correct PRM document is being served (curl the candidate URL from protectedResourceURLs) and that it is valid JSON with the field present.
  3. If multiple AS issuers are listed, ensure the first one (AuthorizationServers[0]) is the intended one.
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: discoverProtectedResource fetches a candidate .well-known/oauth-protected-resource document, GetProtectedResourceMetadata returns no error, but the parsed JSON has authorization_servers missing or set to [].

Common situations: Resource server publishes a PRM document but forgot to populate authorization_servers; misconfigured reverse proxy serving a partial document; PRM generated from a template with the field left empty.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/8d3bef3dea18a893. Report an issue: GitHub.