Skip to content

OpenApiSecurityRequirement serializes as empty object when constructed programmatically with OpenApiSecuritySchemeReference #2801

@jonaslewin

Description

@jonaslewin

When building an OpenApiDocument programmatically and adding security requirements using OpenApiSecuritySchemeReference as dictionary keys in OpenApiSecurityRequirement, the serialized output produces "security": [{}] instead of "security": [{"Bearer": []}].

The root cause is in OpenApiSecurityRequirement.SerializeInternal(), which filters entries with:

this.Where(static p => p.Key?.Target is not null)

When the document is constructed in memory (not parsed from a file), Target resolves via Reference.HostDocument.ResolveReferenceTo<U>(Reference, ...), which returns null because the security scheme was added to Components.SecuritySchemes as a plain OpenApiSecurityScheme, not as an IOpenApiSecurityScheme that the resolver can match. The filter silently drops the entry with no warning.

To Reproduce

using Microsoft.OpenApi;

var doc = new OpenApiDocument
{
    Info = new OpenApiInfo { Title = "Test", Version = "1.0" },
    Components = new OpenApiComponents
    {
        SecuritySchemes = new Dictionary<string, IOpenApiSecurityScheme>(StringComparer.Ordinal)
        {
            ["Bearer"] = new OpenApiSecurityScheme
            {
                Type = SecuritySchemeType.Http,
                Scheme = "Bearer",
                BearerFormat = "JWT",
            },
        },
    },
    Security =
    [
        new OpenApiSecurityRequirement
        {
            { new OpenApiSecuritySchemeReference("Bearer", doc), [] },
        },
    ],
};

var json = await doc.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2);
Console.WriteLine(json);

Expected behavior

{
  "security": [
    {
      "Bearer": []
    }
  ]
}

Actual behavior

{
  "security": [
    {}
  ]
}

No exception or warning is produced — the entry is silently dropped.

Version

  • Microsoft.OpenApi 2.0.0-preview17 (and later, including transitive via Microsoft.OpenApi.OData 3.2.0)
  • Also affects 3.0 and 3.1 serialization — all three SerializeAsV3/V31/V32 methods share the same SerializeInternal code path.

Additional context

Suggested fix
The Where filter in SerializeInternal should fall back to Reference.ReferenceV3 (or Reference.Id) when Target is null, rather than silently skipping the entry. The callback already uses s.Reference.ReferenceV3 for the property name — the guard just needs to allow entries where the reference ID is available even if Target couldn't be resolved.

Metadata

Metadata

Assignees

Labels

priority:p1High priority but not blocking. Causes major but not critical loss of functionality SLA <=7daystype:bugA broken experience

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions