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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions