Skip to content

Using 3rd-party identity providers is simple ... until you need auth for Swagger😱 or need to write a client #47461

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

Closed
mkane91301 opened this issue Mar 28, 2023 · 3 comments
Labels
area-identity Includes: Identity and providers enhancement This issue represents an ask for new feature or an enhancement to an existing one
Milestone

Comments

@mkane91301
Copy link

mkane91301 commented Mar 28, 2023

          @mkane91301 I think it makes sense to speak about some specific code. Once you have it, open up an issue on the okta .NET SDK with specific feedback on what you think the 3-line experience should look like. The APIs will only get better *real* customer feedback.

Originally posted by @davidfowl in #42158 (comment)

The main story of simply adding auth isn't my complaint. The actual real-world story of adding auth with all the extras is where I don't think I should have to write so much code and it should be easy for identity vendors to expose what's needed to identity consumers, such as Swagger, that we don't need to glue them together.

Here's what I need to glue Okta and Swagger together:

internal static class OktaExtensions
{
    internal static void AddOkta(this WebApplicationBuilder builder, string sectionName = "Okta")
        => builder.Services.AddOkta(builder.Configuration.GetSection(sectionName));

    private static void AddOkta(this IServiceCollection services, IConfiguration configSection)
    {
        services.AddAuthentication(ConfigureAuthentication)
                .AddOktaWebApi(configSection.Get<OktaWebApiOptions>());

        services.Configure<OktaWebApiOptions>(configSection)
                .AddOptions<SwaggerGenOptions>()
                .Configure<IOptions<OktaWebApiOptions>>(ConfigureSwagger);

        services.Configure<OktaSwaggerOptions>(configSection)
                .AddOptions<SwaggerUIOptions>()
                .Configure<IOptions<OktaSwaggerOptions>>(ConfigureSwaggerUi);

        services.AddAuthorization();
    }

    private static void ConfigureAuthentication(AuthenticationOptions options)
    {
        options.DefaultAuthenticateScheme = OktaDefaults.ApiAuthenticationScheme;
        options.DefaultChallengeScheme = OktaDefaults.ApiAuthenticationScheme;
        options.DefaultSignInScheme = OktaDefaults.ApiAuthenticationScheme;
    }

    private static void ConfigureSwagger(SwaggerGenOptions swaggerOpts, IOptions<OktaWebApiOptions> oktaOpts)
    {
        var okta = oktaOpts.Value;

        swaggerOpts.AddSecurityDefinition("Okta", new OpenApiSecurityScheme
                                                  {
                                                      Type = SecuritySchemeType.OAuth2,
                                                      Flows = new OpenApiOAuthFlows
                                                              {
                                                                  Implicit = new OpenApiOAuthFlow
                                                                             {
                                                                                 AuthorizationUrl = new Uri($"{okta.OktaDomain}/oauth2/{okta.AuthorizationServerId}/v1/authorize"),
                                                                                 Scopes = new Dictionary<string, string>
                                                                                          {
                                                                                              {"openid", "openid"},
                                                                                              {"profile", "profile"},
                                                                                              {"email", "email"}
                                                                                          },
                                                                                 TokenUrl = new Uri($"{okta.OktaDomain}/oauth2/{okta.AuthorizationServerId}/v1/token")
                                                                             }
                                                              }
                                                  });

        swaggerOpts.AddSecurityRequirement(new OpenApiSecurityRequirement
                                           {
                                               {
                                                   new OpenApiSecurityScheme
                                                   {
                                                       Reference = new OpenApiReference
                                                                   {
                                                                       Type = ReferenceType.SecurityScheme,
                                                                       Id = "Okta"
                                                                   }
                                                   },
                                                   new[] { "openIdConnect" }
                                               }
                                           });
    }

    private static void ConfigureSwaggerUi(SwaggerUIOptions swaggerOpts, IOptions<OktaSwaggerOptions> oktaOpts)
    {
        var okta = oktaOpts.Value;

        swaggerOpts.OAuthClientId(okta.ClientId);
        swaggerOpts.OAuth2RedirectUrl($"{okta.RedirectHost}/swagger/oauth2-redirect.html");
        swaggerOpts.OAuthAppName(Assembly.GetEntryAssembly()!.GetName().Name);
        swaggerOpts.OAuthScopeSeparator(" ");
        swaggerOpts.OAuthAdditionalQueryStringParams(new Dictionary<string, string>
                                                     {
                                                         { "nonce", $"{Guid.NewGuid() :N}" }
                                                     });
    }

    private class OktaSwaggerOptions
    {
        public string ClientId { get; set; } = "";
        public string RedirectHost { get; set; } = "";
    }
}

I would like it that when I add an identity vendor's auth implementation, it would either automatically provide the OpenApiSecurityScheme and OpenApiSecurityRequirement or make it a simple option to ask it to provide them. And then Swagger would automatically know where to find them.

Similarly on the client side, there is no standard way to take an IHttpClientBuilder and add an OAuth bearer token to its calls from such-and-such a vendor's identity server according to some configuration. I wrote much more code than I can snip here to create a standard way for our shop. I'd open source it if my company allowed, but this should already be part of Microsoft.Extensions anyway.

@davidfowl
Copy link
Member

davidfowl commented Mar 28, 2023

The actual real-world story of adding auth with all the extras is where I don't think I should have to write so much code and it should be easy for identity vendors to expose what's needed to identity consumers, such as Swagger, that we don't need to glue them together.

It turns out we have another issue about inferring OpenAPI information for auth schemes.

Similarly on the client side, there is no standard way to take an IHttpClientBuilder and add an OAuth bearer token to its calls from such-and-such a vendor's identity server according to some configuration. I wrote much more code than I can snip here to create a standard way for our shop. I'd open source it if my company allowed, but this should already be part of Microsoft.Extensions anyway.

I'd file another issue for this, with the specifics as well. There's more detail needed here. It's unclear to me if you're trying to set tokens in the request or do something more complex, (like an oauth2 auth flow, that supports getting the token, storing the refresh token and renewing it etc).

@mkArtakMSFT mkArtakMSFT added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label Mar 28, 2023
@mkArtakMSFT mkArtakMSFT added this to the .NET 8 Planning milestone Mar 28, 2023
@ghost
Copy link

ghost commented Mar 28, 2023

Thanks for contacting us.

We're moving this issue to the .NET 8 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.

@mkArtakMSFT
Copy link
Member

Closing in favor of the issue that @davidfowl linked above.

@mkArtakMSFT mkArtakMSFT closed this as not planned Won't fix, can't repro, duplicate, stale Dec 1, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Feb 7, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-identity Includes: Identity and providers enhancement This issue represents an ask for new feature or an enhancement to an existing one
Projects
None yet
Development

No branches or pull requests

4 participants
@davidfowl @mkane91301 @mkArtakMSFT and others