Not able to run Swagger UI - asp.net-web-api

I have a .NetCore Web API project. And I am trying to use Swagger on it.
All the configuration looks good but when I run my project I got an 404 error, page not found.
Here is my code:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_config);
services.AddTransient<IRestHelper, RestHelper>();
// Add framework services.
services.AddApplicationInsightsTelemetry(_config);
services.AddMvc();
services.AddSwaggerGen(config =>
{
config.SwaggerDoc("v1", new Info { Title = "Slack Relay API", Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(_config.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
app.UseMvc();
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((swagger, httpReq) => swagger.Host = httpReq.Host.Value);
});
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs");
});
}
My web API base url is: http://localhost:88/
So when I try to open this URL I get a 404: http://localhost:88/swagger/ui
I am using NetCore 1.0.1 and In the project.json I have this line to import Swagger:
"Swashbuckle.AspNetCore": "1.0.0"
Any help please?
thanks

You are entering the wrong URL in the browser. With Swagger 1.0.0 RTM the default url changed from http://localhost:88/swagger/ui to http://localhost:88/swagger

You need to remember that order of middlewares is important: MVC midleware should be the last one, as it tries to process all requests. As you do not have controller's action mapped to /swagger/ui you receive 404 error. Modify Configure method to:
app.UseSwagger(c =>
{
c.PreSerializeFilters.Add((swagger, httpReq) => swagger.Host = httpReq.Host.Value);
});
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "V1 Docs");
});
app.UseMvc();
Moreover, it is better to replace app.UseMvc() with something like the following, so MVC middleware will not try to process any requests related to swagger (requests to URL started with /swagger):
app.MapWhen(x => !x.Request.Path.Value.StartsWith("/swagger"), builder =>
{
builder.UseMvc();
});

Related

Configure HealthChecksUI for AspNetCore WebApi

I am setting up the HealthCheckUI for the ASPNETCore web api (.Net Core 3.1). However http://localhost:1234/healthchecks-ui keeps calling the default api http://localhost:1234/healthchecks-api instead of http://localhost:1234/health as configured.
The /health returns JSON data as expected and /healthchecks-api returns [].
Any idea please?
Setup:
public void ConfigureServices(IServiceCollection services){
...
services.AddHealthChecksUI(setupSettings: setup=>
{
setup.SetEvaluationTimeInSeconds(15); //time in seconds between check
setup.MaximumHistoryEntriesPerEndpoint(60); //maximum history of checks
setup.SetApiMaxActiveRequests(1); //api requests concurrency
setup.AddHealthCheckEndpoint("api", "/health"); //map health check api
})
.AddInMemoryStorage();
}
and
public void Configure(IApplicationBuilder app){
...
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health", new HealthCheckOptions { ResponseWriter = HealthCheckResponseWriter.WriteAsync });
endpoints.MapHealthChecksUI();
});
}
My HealthCheck JSON is not good format. Working fine with the default UIResponseWriter:
endpoints.MapHealthChecks("/health", new HealthCheckOptions { ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse });

SignOut does not redirect to site home page

I'm trying to setup an ASP.net Core 3 MVC app that uses OIDC to connect to my company's SSO portal (OpenAM).
I used Visual Studio 2019 project generator to create a basic app with no authentication and then I added the OIDC client capabilities following the steps at http://docs.identityserver.io/en/latest/quickstarts/2_interactive_aspnetcore.html#creating-an-mvc-client .
Logging in works great with minimal changes to the Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
// Setup Identity Server client
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://mycompany.com/ssoservice/oauth2";
options.RequireHttpsMetadata = false;
options.ClientId = "openIdClient";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.ProtocolValidator.RequireNonce = false;
options.SaveTokens = true;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
// endpoints.MapDefaultControllerRoute();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
I also set up a Logout controller action:
[Authorize]
public IActionResult Logout()
{
return SignOut("Cookies", "oidc");
}
The action actually works, i.e. when activated the cookie is deleted and the user is logged out from the SSO portal, but when the browser redirects to the /signout-callback-oidc endpoint it receives an HTTP 200 response without any content. I would have expected to have it automatically redirect to the site home page "/", which is the default value of the OpenIdConnectOptions.SignedOutRedirectUri property.
What am I missing?
Ok, after fiddling some more time, I found out this is the result of a missing draft implementation in the latest community OpenAM release (and also in the current paid ForgeRock AM, but they are working on it: https://bugster.forgerock.org/jira/browse/OPENAM-13831). Basically, the .net core handler for /signout-callback-oidc relies on having the state parameter available in order to redirect, like Ruard van Elburg mentioned in the comments:
https://github.com/aspnet/AspNetCore/blob/4fa5a228cfeb52926b30a2741b99112a64454b36/src/Security/Authentication/OpenIdConnect/src/OpenIdConnectHandler.cs#L312-L315
OpenAM does not send back the state parameter, as reported in my logs. Therefore, we need to perform the redirect ourselves - the most straightforward way seems to be using the OnSignedOutCallbackRedirect event:
Startup.cs
services.AddAuthentication(...)
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
...
options.Events.OnSignedOutCallbackRedirect += context =>
{
context.Response.Redirect(context.Options.SignedOutRedirectUri);
context.HandleResponse();
return Task.CompletedTask;
};
...
});
Thanks to all the users that replied to the discussion, your contributions allowed me to find the clues to the correct solution.
you return SignOut,
instead, SignOut user and return RedirectToAction("Home","Index")

How can SignalR Cors be configured for an ASPNETCore 2.1 application?

I am trying to configure CORS for my aspnetcore 2.1 application. I have followed the documentation closely. My Startup file has been configured as follows.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options => options
.AddPolicy("CorsPolicy", builder =>
{
builder.WithOrigins("http://sitetracker")
.AllowAnyHeader();
}));
services.AddMvc();
services.AddSignalR(o => o.EnableDetailedErrors = true);
services.AddDbContext<BiometricContext>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("CorsPolicy");
app.UseSignalR(routes =>
{
routes.MapHub<BiometricsHub<User>>("/biometricshub");
});
app.UseMvc();
}
I configured and angular project to start a hub connection and wait on a the server to push some information to the client.
var port = 58422;
this.connection = new HubConnectionBuilder()
.withUrl(`http://localhost:${port}/BiometricsHub`)
.configureLogging(LogLevel.Information)
.build();
this.connection.on("ReceiveActiveIdentities", (identities) => {
debugger // Waiting here to receive data
});
this.connection.start().catch(err => {
debugger // Error is "Error" with and error code of 0
console.error(err.toString());
});
An error is caused when the connection.start is called.
Since I couldn't figure out why the cors error was happening I wanted to know if the code was broken in some other way. I disabled the cors security in a browser session by executing the following line at the run menu.
chrome.exe --user-data-dir="C:/Chrome dev session" --disable-web-security
Once CORS was disabled, the code would return the data in my client callback as expected. This leads me to believe everything outside of my CORS configuration is correct.
What is missing in my configuration? I can still not get this to work.
I thought I had tried this before in my many configurations. However, this seems not to be the case. Maybe at the time app.UseCors() followed app.UseMvc which apparently is a no-no.
builder.WithOrigins("http://sitetracker")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
The three following it allows need to be in place or more restricted versions which allow your particular actions or methods to be executed as needed.

Asp.Net Core 2.1 WebApi returns 400 when sending request from Angular 6 WebApp to get access_token

I've got an application that is represented by asp.net core 2.1 web api on the server side and angular 6 on the client side. OpenIddict is used on the server side to support token authentication. The main problem is that when a request is sent from angular app to the server to generate or refresh access_token for a client, the server responds with the 400 (Bad Request), though when it is send from Postman everything works just fine. The Cors policy is added to allow corss-origin requests as client and server sides are placed on different ports so simple requests from angular to the server passes fine.
Here is the Startup class:
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
Configuration = configuration;
hostingEnvironment = env;
}
public IConfiguration Configuration { get; }
private IHostingEnvironment hostingEnvironment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<HospitalContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.UseOpenIddict();
});
services.AddCors(options => options.AddPolicy("AllowLocalhost4200", builder =>
{
builder
.WithOrigins("http://localhost:4200")
.WithHeaders("Authorization", "Content-type")
.WithMethods("Get", "Post", "Put", "Delete");
}));
services.AddCustomIdentity();
services.AddCustomOpenIddict(hostingEnvironment);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseCors("AllowLocalhost4200");
app.UseAuthentication();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
app.InitilizeDb();
}
}
and AddCustomOpenIddict method which is in ConfigureServices method if someone needs to see the configuration:
public static IServiceCollection AddCustomOpenIddict(this IServiceCollection services,
IHostingEnvironment env)
{
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<HospitalContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/connect/token");
options.EnableAuthorizationEndpoint("/connect/authorize");
options.AllowRefreshTokenFlow()
.AllowImplicitFlow();
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(30));
options.SetIdentityTokenLifetime(TimeSpan.FromMinutes(30));
options.SetRefreshTokenLifetime(TimeSpan.FromMinutes(60));
if (env.IsDevelopment())
{
options.DisableHttpsRequirement();
}
options.AddEphemeralSigningKey();
});
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddOAuthValidation();
return services;
}
The Angular method that sends a request is:
public authorize(model: ILoginModel): Observable<Response> {
return this.http.post(`http://localhost:58300/connect/token`,
this.authService.authFormBody(model),
{headers: this.authService.authHeaders()});
}
with this.authService.authFormBody and this.authService.authHeaders:
authHeaders(): Headers {
const headers = new Headers(
{
'Content-Type': 'application/x-www-form-urlencoded'
});
return headers;
}
authFormBody(model: ILoginModel): string {
let body = '';
body += 'grant_type=password&';
body += 'username=' + model.email + '&';
body += 'password=' + model.password + '&';
body += 'scope=OpenId profile OfflineAccess Roles';
return body;
}
I'm actually new to token based authetication, so maybe there is a problem in configurations or something. Would appreciate any offers to solve a problem.
I found an error, it was just really that I removed AddPasswordFlow from my configs and left AllowRefreshTokenFlow() and AllowImplicitFlow() and was sending grant_type=password to the server that was not cofigured to accept such a grant, it was my mistake there. It is supposed to be:
services.AddOpenIddict(options =>
{
//some configs
options.AllowPasswordFlow()
.AllowRefreshTokenFlow()
.AllowImplicitFlow();
//some configs
});
For the begining, try allowing all headers:
https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.1#set-the-allowed-request-headers
Later, to be precise, look at all headers your angular app is sending and allow all of them in your cors policy. To start with, allow application-x-www-form-urlencoded
There seems to be a typo in your authFormBody method:
body += 'grant_type=password$';
This should be written as:
body += 'grant_type=password&';

When using an API route, return Http Response 401 instead of redirect to login page when not authorised

I'm building an ASP.NET Core 2.0 website using MVC and WebAPI to provide access to a series of microservices. Where a WebAPI controller requires a user to be authenticated and authorised (using the Authorize attribute), any unauthorised or not-logged in user gets the response back as the entire HTML for the MVC login page.
When unauthorised users access the API, I would like to return the HTTP status code 401 and its associated error message in the response, instead of an entire HTML page.
I've looked at a few existing questions and noticed that they either refer to ASP.NET MVC (such as SuppressDefaultHostAuthentication in WebApi.Owin also suppressing authentication outside webapi) which is no good for ASP.NET Core 2.0. Or they are using a hackaround for Core 1.x, which just doesn't seem right (ASP.Net core MVC6 Redirect to Login when not authorised).
Has a proper solution been implemented in Core 2.0 that anyone is aware of? If not, any ideas how it could be implemented properly?
For reference, there's part of a controller as an example:
[Authorize]
[ApiVersion("1.0")]
[Produces("application/json")]
[Route("api/V{ver:apiVersion}/Organisation")]
public class OrganisationController : Controller
{
...
[HttpGet]
public async Task<IEnumerable<string>> Get()
{
return await _organisationService.GetAllSubdomains();
}
...
}
And the configurations within Statup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
// Add API version control
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
options.ErrorResponses = new DefaultErrorResponseProvider();
});
// Add and configure MVC services.
services.AddMvc()
.AddJsonOptions(setupAction =>
{
// Configure the contract resolver that is used when serializing .NET objects to JSON and vice versa.
setupAction.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseStatusCodePagesWithRedirects("/error/index?errorCode={0}");
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
...
}
There is an easy way to suppress redirect to Login page for unathorized requests. Just add following call of ConfigureApplicationCookie extension method in your ConfigureServices:
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.CompletedTask;
};
});
Or if you need custom error message in response body:
services.ConfigureApplicationCookie(options =>
{
options.Events.OnRedirectToLogin = async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await context.Response.WriteAsync("Some custom error message if required");
};
});
As far as you're using redirects to custom error pages for error codes (UseStatusCodePagesWithRedirects() call in Configure method), you should add filter for 401 error. To achieve this, remove call to UseStatusCodePagesWithRedirects and use UseStatusCodePages extension method with skip of redirect for Unauthorized code:
//app.UseStatusCodePagesWithRedirects("/error/index?errorCode={0}");
app.UseStatusCodePages(context =>
{
if (context.HttpContext.Response.StatusCode != (int)HttpStatusCode.Unauthorized)
{
var location = string.Format(CultureInfo.InvariantCulture, "/error/index?errorCode={0}", context.HttpContext.Response.StatusCode);
context.HttpContext.Response.Redirect(location);
}
return Task.CompletedTask;
});
If you're using JWT for authentication with an ASP.NET Core 2 API; you can configure the unauthorized response when you're configuring the services for Authentication & JWT:
services.AddAuthentication( JwtBearerDefaults.AuthenticationScheme )
.AddJwtBearer(options => options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = c =>
{
c.NoResult();
c.Response.StatusCode = 401;
c.Response.ContentType = "text/plain";
return c.Response.WriteAsync("There was an issue authorizing you.");
}
});

Resources