Swagger won't authorize in Azure when served from root, "/" although working from path, "/swagger", "/boink" etc

71 views Asked by At

I have a working setup with Swagger (roughly setup like in this answer) sending me over to Azure where I authorize and then obtain a functioning token. Everything is as supposed to and it's not about changing from Web to SPA.

A colleague suggested that we should serve Swaggy directly in the root of the backend instead of the path /swagger, so I introduced the following changes in the code (accompanied with an extra redirect URL in Azure (

//app.UseSwagger(options => options.RouteTemplate = "swagger/{documentName}/docs.json");
app.UseSwagger(options => options.RouteTemplate = "swaggy/{documentName}/docs.json");
app.UseSwaggerUI(options =>
{
  ...
  //options.RoutePrefix = "swagger";
  options.RoutePrefix = "swaggy";
});

https://localhost:7001/swagger/oauth2-redirect.html
https://localhost:7001/swaggy/oauth2-redirect.html

This still works and I see no issues altering the swagger location. However, once I drop it entirely and serve it from the root (as agreed with the team) and make the change as shown below, it stops working!

//app.UseSwagger(options => options.RouteTemplate = "swagger/{documentName}/docs.json");
app.UseSwagger(options => options.RouteTemplate = "{documentName}/docs.json");
app.UseSwaggerUI(options =>
{
  ...
  //options.RoutePrefix = "swagger";
  options.RoutePrefix = "";
});

https://localhost:7001/swagger/oauth2-redirect.html
https://localhost:7001/oauth2-redirect.html

I'm suddenly faced with the following error message.

Auth ErrorError: response status is 400, error: invalid_request, description: AADSTS9002326: Cross-origin token redemption is permitted only for the 'Single-Page Application' client-type. Request origin: 'https://localhost:7001'.

I've been googling it the whole day and see nothing about it. All the hits refer to changing the platform to Azure to SPA and/or checking the redirect URLs. None of that is relevant here.

What is it about and how can I kill it?

1

There are 1 answers

4
VonC On BEST ANSWER

The key takeaway from Harshita's link is the flexibility of the RoutePrefix setting in Swagger UI options, which allows you to tailor Swagger's accessibility based on the environment (local vs. Azure). See "Customize the HTTP endpoint".

So you should adapt the Swagger and Swagger UI configuration in your ASP.NET Core application to dynamically set the RoutePrefix based on whether the application is running locally or in Azure. That would make sure the Swagger UI works correctly in both environments without manual adjustments for each deployment.

// Determine the running environment
var isAzure = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production";

// Configure Swagger
app.UseSwagger(options => options.RouteTemplate = "swagger/v1/swagger.json");

// Configure Swagger UI
app.UseSwaggerUI(x =>
{
    x.SwaggerEndpoint("/swagger/v1/swagger.json", "Web API V1");

    // Set the RoutePrefix based on the environment
    x.RoutePrefix = isAzure ? "" : "swagger";
});

That code snippet checks the environment using the ASPNETCORE_ENVIRONMENT variable (this you can set here), a common way to differentiate between development and production environments in ASP.NET Core applications. It assumes that the Production value indicates deployment on Azure.

  • When running locally (not in Production), RoutePrefix is set to "swagger", making the Swagger UI accessible at /swagger/index.html.
  • When deployed to Azure (Production), RoutePrefix is set to an empty string, making the Swagger UI accessible directly at the root (/index.html).

That should also address the concern of making Swagger accessible at the desired path without causing cross-origin token redemption issues related to the OAuth2 flow with Azure AD (AADSTS9002326), which comes from Azure AD's handling of OAuth2 redirects, particularly in scenarios where the redirect seems to originate from a different origin than expected.

When serving Swagger from the root (/) on Azure, the redirect URI used for OAuth2 authentication changes, affecting how Azure AD processes these requests. If Azure AD perceives these requests as cross-origin from an unauthorized origin, it will block them due to security policies designed to prevent unauthorized token redemption.

By dynamically setting the RoutePrefix based on the deployment environment (local development versus Azure), you make sure the OAuth2 redirect URIs remain consistent with the expectations of Azure AD, whether the application is accessed locally or hosted on Azure.

  • Local development: The Swagger UI is accessed at /swagger/index.html, and OAuth2 redirects use paths consistent with local development settings. Since there is no cross-origin concern locally, authentication flows work as expected.

  • Azure deployment: With RoutePrefix set to an empty string, Swagger UI is served from the root (/index.html), and OAuth2 redirects are expected to originate from the root. That setup would match Azure's security policies for OAuth2 redirects, avoiding the cross-origin issue since the redirects no longer appear to come from a different origin.


What I failed to understand is the case of the prefix for the local run to be set to empty string. How can I achieve that? And if I can't, what's the reason?

The question isn't "how to make it work locally?" but rather "how to make it work locally with the empty string as prefix?".

In ASP.NET Core, setting the RoutePrefix to an empty string ("") means you want to serve Swagger UI directly from the application's base URL (e.g., https://localhost:5001/). That setup is straightforward when deploying to Azure or any environment where you have control over the base URL and can make sure no conflicts with other routes. However, doing this locally is more complex, because of how the application routes are resolved and potential conflicts with other endpoints in your application:

  • Your application might have other routes that could conflict with Swagger's assets when served from the root. For example, if you have a controller action mapped to the root, it might take precedence or conflict with the Swagger UI route, leading to the Swagger UI not being accessible.

  • ASP.NET Core processes middleware in the order they are added to the pipeline in Startup.Configure. If Swagger middleware (UseSwaggerUI) is configured before static files middleware (UseStaticFiles), there might be issues serving the Swagger UI assets from the root because the request might be intercepted by the static files middleware first, especially if there are files in the wwwroot directory or other middleware that handles requests to the root path.

To make Swagger work locally with the RoutePrefix set to an empty string, you would have to:

  • confirm that UseSwagger and UseSwaggerUI are correctly ordered in your middleware pipeline. Ideally, UseSwaggerUI should come after UseRouting and before any UseEndpoints calls.

  • make sure no other routes or controllers that handle requests to the root (/). That might require adjusting your application's routing to accommodate Swagger UI at the root.

For instance:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // Other middleware (e.g., error handling) can go here

    app.UseRouting();

    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
        c.RoutePrefix = string.Empty;
    });

    // More configurations, like UseAuthorization(), UseEndpoints(), etc.
}