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?
The key takeaway from Harshita's link is the flexibility of the
RoutePrefixsetting 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
RoutePrefixbased 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.That code snippet checks the environment using the
ASPNETCORE_ENVIRONMENTvariable (this you can set here), a common way to differentiate between development and production environments in ASP.NET Core applications. It assumes that theProductionvalue indicates deployment on Azure.Production),RoutePrefixis set to"swagger", making the Swagger UI accessible at/swagger/index.html.Production),RoutePrefixis 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
RoutePrefixbased 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
RoutePrefixset 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.In ASP.NET Core, setting the
RoutePrefixto 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 thewwwrootdirectory or other middleware that handles requests to the root path.To make Swagger work locally with the
RoutePrefixset to an empty string, you would have to:confirm that
UseSwaggerandUseSwaggerUIare correctly ordered in your middleware pipeline. Ideally,UseSwaggerUIshould come afterUseRoutingand before anyUseEndpointscalls.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: