Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically infer OpenApiSecuritySchemes from authentication configuration #39761

Open
captainsafia opened this issue Jan 25, 2022 · 10 comments
Assignees
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-openapi Priority:1 Work that is critical for the release, but we could probably ship without

Comments

@captainsafia
Copy link
Member

captainsafia commented Jan 25, 2022

At the moment, when users enable authentication in their ASP.NET apps, they typically have to manually the describe the OpenApiSecuritySchemes in their application and the top level and configure OpenApiSecurityRequirements for each route that requires authentication and authorization.

We should infer as much of these definitions as possible so users don't need to configure auth twice, once for their application and another time for OpenAPI.

Provide metadata support for parts of the specification documented in https://swagger.io/docs/specification/authentication/.

@captainsafia captainsafia added enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-openapi old-area-web-frameworks-do-not-use *DEPRECATED* This label is deprecated in favor of the area-mvc and area-minimal labels labels Jan 25, 2022
@captainsafia captainsafia added this to the 7.0-preview2 milestone Jan 25, 2022
@rafikiassumani-msft rafikiassumani-msft changed the title Security schemes cannot be generated for individual endpoints/actions Generate security schemes for individual endpoints/actions Feb 2, 2022
@rafikiassumani-msft rafikiassumani-msft added the Priority:1 Work that is critical for the release, but we could probably ship without label Feb 10, 2022
@ghost
Copy link

ghost commented Mar 14, 2022

Thanks for contacting us.

We're moving this issue to the .NET 7 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@captainsafia
Copy link
Member Author

Who knew auth could be so hard? 🤪

I started experimenting with some of this support in the new Microsoft.AspNetCore.OpenAPI package. The problem is generally easy to solve for cookie and JWT bearer-based authentication types, but OAuth authentication types are a lot trickier for us to derive automatic annotations for because:

  • We don't store any information about what type of grant flow an OAuth implementation uses
  • We don't make it easy for authentication scheme providers to describe what authentication type they are

We've had conversations about how to make the authentication system more self-describing for annotation purposes. There's more design scenarios to reason about here but for .NET 7 we're scoping it down to auto-generating annotations for JWT-bearer based types to align with the work we've done with dotnet user-jwts and the options-from-config changes.

@julealgon
Copy link

...but for .NET 7 we're scoping it down to auto-generating annotations for JWT-bearer based types...

@captainsafia wouldn't it be possible to request the information from the user for the things that are hard to imply? Not having any scaffolding for AAD integration feels really unprofessional right now (in the sense that it is a solution that just doesn't work at all out of the box) and it would be really nice to have something in place for .NET7.

Also, a lot of parameters used to build the auth schema for swagger can leverage the config section created by Azure AD itself: that's what I did for a few APIs on my side by creating a generic extension method that introduces a IConfigureOptions implementation for SwaggerGenOptions and SwaggerUIOptions that rely on the AzureAD config options to read some of the values to build things like the authorize/token URLs. The only "inputs" needed for a default scafolding would be what grant types the user expects to support (it could default to "Implicit" by default for example which is the simplest one). Even scopes are added in the config by default so even those could be inferred.

Then if the user wants to move away from some of that stuff (like handling scopes directly without the config) they can edit the initial template code to accomodate.

@julealgon
Copy link

On another note, I wish Azure AD's OpenIdConnect endpoint was more useful for this... if it returned app-specific scopes and URLs, it could be used directly with Swagger to automatically setup a lot of different details that have to be manually added today when using the OAuth2 SecuritySchemeType.

As it stands today, AzureAD's OpenIdConnect discovery endpoint is completely useless IMHO.

Related:

@captainsafia
Copy link
Member Author

@captainsafia wouldn't it be possible to request the information from the user for the things that are hard to imply?

Perhaps within reason. Although the goal of this design is to automatically generate as much of the OpenApiSecurityRequirement and OpenApiSecurityScheme definitions in the document as possible.

Also, a lot of parameters used to build the auth schema for swagger can leverage the config section created by Azure AD itself:

We had discussed this option. It dovetails nicely in some ways with the work we're doing to support loading more authentication options from config (see #42170). However, configuration isn't the source of truth for how authentication is set up in application. You'll get something that partly works for generating some schemas but not all the time. Not to mention, the approach doesn't help at all for applying the security requirements on operations.

Not having any scaffolding for AAD integration feels really unprofessional right now

We hadn't really explored doing this as part of the template/scaffolding infrastructure. At the moment, the plan is to do this work in the framework so that it can play well with all authentication strategies.

@captainsafia captainsafia changed the title Generate security schemes for individual endpoints/actions Automatically infer OpenApiSecuritySchemes from authentication configuration Jul 18, 2022
@captainsafia
Copy link
Member Author

I spent some time prototyping what this would look like and landed on a pretty solid strategy that uses PostConfigureOptions on a custom AuthenticationBuilder that would work well for this.

With that in mind, here's some of the challenges:

  • The PostConfigureOptions needs to be added to DI as part of the AddAuthentication call. In my demo, I added this behavior to a custom WebApplicationAuthenticationBuilder that was part of WebApplication. Similar to the model that we tried out in .NET 7 with builder.Authentication. It's not totally necessary but not having it would mean that this behavior will be enabled for all authentication scenarios in .NET 8.
  • The PostConfigureOptions would need to emit OpenApiSecuritySchemes given AuthenticationSchemeOptions but cannot take a dependency on the type since it is not in the shared framework. We could consider packaging it in the shared framework or build a set of intermediary definitions.
  • The prototype relies on a global document service existing (see Support configuring document-level parts of OpenAPI schema #44192) so that the security schemes under components can be populated.

@captainsafia
Copy link
Member Author

I only got CookieOptions and GoogleOptions working for the sake of my demo but we probably want to support the full spectrum of authentication options in framework. Starting a draft of the options in the table below here:

Authentication option Generated security requirement
JwtBearerOptions { type: 'http', scheme: 'Bearer' }

The PostConfigureOptions would need to emit OpenApiSecuritySchemes given AuthenticationSchemeOptions but cannot take a dependency on the type since it is not in the shared framework. We could consider packaging it in the shared framework or build a set of intermediary definitions.

I think building a set of intermediary types is the best choice here. It'll allow us to provide plugins for MVC + NSwag/Swashbuckle and will allow community-provided authentication providers to present their own definitions for the auth strategies they register. Although it increases the API surface area of the feature it is the most flexible.

@diegosasw
Copy link

diegosasw commented Oct 26, 2024

@captainsafia thanks for the great changes.

May I ask what's the current situation for .NET 9 with this? Haven't found any doc or guidance.

I'm using Microsoft.AspNetCore.OpenApi version 9.0.0-rc.2.24474.3.
The OpenApi document generates well. I actually tried generating it at compile time with Microsoft.Extensions.ApiDescription.Server version 9.0.0-rc.2.24474.3 and the following OpenApi options in the .csproj and it generates the OpenApi document properly, finding minimal endpoints and infering request/response schemas. All good there

    <PropertyGroup>
        <TargetFramework>net9.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <IsPackable>false</IsPackable>
        <!--Open Api Generation-->
        <OpenApiDocumentsDirectory>$(MSBuildProjectDirectory)</OpenApiDocumentsDirectory>
        <OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
        <OpenApiGenerateDocumentsOnBuild>true</OpenApiGenerateDocumentsOnBuild>
        <!--End of Open Api Generation-->
    </PropertyGroup>

Now I can plug Scalar or Swashbuckle UI and read from the OpenApi json to render a UI

app .UseSwaggerUI(options =>
{
      options.SwaggerEndpoint("/openapi/v1.json", "MyProject.Api");
});

But what's the situation with Authentication?

My endpoints are secured with Authentication Bearer schema, but the generated OpenApi document doesn't seem to infer this.

As I understood, Security Schemas like Bearer authentication are standard in OpenApi v3

I suppose this is not yet available in the NuGet or .NET 9.

Is there any workaround?

If I was using Swashbuckle Swagger also for OpenApi document generation, I could instruct it to generate security schema like this

services.AddSwaggerGen(
options =>
{
	options.AddSecurityDefinition(
		"Bearer",
		new OpenApiSecurityScheme
		{
			Name = "Authorization",
			Type = SecuritySchemeType.Http,
			Scheme = "Bearer",
			In = ParameterLocation.Header,
			Description = "JWT Authorization header"
		});

	options.AddSecurityRequirement(
		new OpenApiSecurityRequirement
		{
			{
				new OpenApiSecurityScheme
				{
					Reference = new OpenApiReference
					{
						Type = ReferenceType.SecurityScheme,
						Id = "Bearer"
					}
				},
				Array.Empty<string>()
			}
		});
});

Is there any "manual" way to do this in the Microsoft package? I don't see any security schema option

services.AddOpenApi(options =>
{
     options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_0;
});

Maybe there is some IOpenApiSchemaTransformer implementation somewhere? Can't find anything

services.AddOpenApi(options =>
{
    options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_0;
    options.AddSchemaTransformer<MyOpenApiSchemaTransformer>();
});

@diegosasw
Copy link

diegosasw commented Oct 26, 2024

I'll answer myself in case somebody find this useful.

After playing a bit with the options I managed to add open api security scheme by adding a document transformer IOpenApiDocumentTransformer, which is later picked by Swashbuckle Swagger UI properly.

I would like to find more examples if anyone knows, but for now this works fine.

internal static class OpenApiRegistrations
{
    internal static IServiceCollection AddMicrosoftOpenApi(
        this IServiceCollection services)
    {
        services.AddOpenApi(options =>
        {
            options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_0;
            options.AddDocumentTransformer<OpenApiSecuritySchemeTransformer>();
        });
        
        return services;
    }
}

public class OpenApiSecuritySchemeTransformer
    : IOpenApiDocumentTransformer
{
    public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context,
        CancellationToken cancellationToken)
    {
        var securitySchema =
            new OpenApiSecurityScheme
            {
                Type = SecuritySchemeType.Http,
                Scheme = "bearer",
                BearerFormat = "JWT",
                Description = "JWT Authorization header using the Bearer scheme."
            };

        var securityRequirement =
            new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Id = "bearerAuth",
                            Type = ReferenceType.SecurityScheme
                        }
                    },
                    []
                }
            };
        
        document.SecurityRequirements.Add(securityRequirement);
        document.Components = new OpenApiComponents()
        {
            SecuritySchemes = new Dictionary<string, OpenApiSecurityScheme>()
            {
                { "bearerAuth", securitySchema }
            }
        };
        return Task.CompletedTask;
    }
}

@martincostello
Copy link
Member

An inline document transformer is also how I went about it: https://github.com/martincostello/aspnetcore-openapi/blob/d87b42a236762ac32d833e6b482500b4d97f118c/src/TodoApp/OpenApi/AspNetCore/AspNetCoreOpenApiEndpoints.cs#L35-L53

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-openapi Priority:1 Work that is critical for the release, but we could probably ship without
Projects
No open projects
Status: No status
Development

No branches or pull requests

5 participants