I have a blazor wasm application running on a subpath Apps/Genesis/
The problem is some DLL's are not being found:
The request is:
https://localhost:5001/Apps/Genesis/Apps/Genesis/_framework/Blazored.LocalStorage.dll
The request should be:
https://localhost:5001/Apps/Genesis/_framework/Blazored.LocalStorage.dll
app.MapWhen(
context =>
{
return context.Request.Path.StartsWithSegments("/Apps/Genesis");
},
genesis =>
{
// Define blazor files
genesis.UseBlazorFrameworkFiles("/Apps/Genesis");
// Access static files (Ex.: images, dll's) - Used twice because it's on the microsoft docs example
genesis.UseStaticFiles();
genesis.UseStaticFiles("/Apps/Genesis");
// Apply routing for UseEndpoints
genesis.UseRouting();
// Especify Routes
genesis.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToFile("Apps/Genesis/{{*path:nonfile}}", "Apps/Genesis/index.html");
});
});
Related
I am pulling my hair out on this issue and I wonder if anyone here could help me out. If not, does anyone know who can?
My situation is that I have a BFF (Backend-for-frontend) that serves my Angular SPA. I use Azure AD Auth.
I use .NET Core 5 and the up to date Azure AD nuget libs
The BFF is useful because it prevents my Auth tokens from being saved in the browser; the BFF saves it in a http-only secure cookie so the SPA has no knowledge of the user; it has to ask the API about the user.
The BFF also ensures that you can only access the SPA if you are logged in
The BFF also reverse proxies calls to /api to the actual API; which is hosted inside our k8s cluster so you can't reach it from the internet
The diagram basically looks like this. See image.
My problem is that I need to use AuthorizeForScopes for Azure AD Auth but this requires stupid workarounds. As far as I can find online I must put this on a Controller/Action like so:
[HttpGet]
[Authorize]
[AuthorizeForScopes(ScopeKeySection = "Scopes")]
public IActionResult Index()
{
return PhysicalFile($"{_webHostEnvironment.ContentRootPath}/ClientApp/dist/index.html", "text/html");
}
This means that if you go to /, you will hit this endpoint and get served the index.html. This isn't a super neat setup because I would rather just let the UseSpa() in Startup.cs handle this. But I believe this is necessary because I can't just use AuthorizeForScopes in my middleware.
Another problem is that this doesn't work during development because there the UseProxyToSpaDevelopmentServer() in Startup.cs handles all this. If you run this during development you get an error cuz it can't find the index.html
The current solution I have on prod is that the code I posted above instead redirects to /home which the SPA handles, so that way if you go to / you get redirected to /home and the BFF pipeline then redirects you to the SPA and boom, it all works. But this means I can't run my SPA on /
I currently have the following Startup.cs setup for my pipeline. I removed unnecessary code.
app.UseStaticFiles();
app.UseStatusCodePagesWithReExecute("/errorstatus/{0}");
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
// Proxy calls to the API through the BFF so the API can only be reached from within the cluster. This is more secure
app.Map("/api", true, config => RunApiProxy(/* Stuff here */));
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapHealthChecks("/health");
});
if (!Environment.IsDevelopment())
{
// During development we serve files from the dev-server, not from the location of the spa static files
app.UseSpaStaticFiles();
}
// Redirect the user to authenticate if the user isnt at this moment
// All static frontend related files are served using the UseSpaStaticFiles middleware
// What's left is the index.html
app.Use(async (context, next) =>
{
if (context.User?.Identity?.IsAuthenticated != true)
{
await context.ChallengeAsync(WellKnownAuthenticationSchemes.OpenIdConnect);
}
else
{
await next();
}
});
if (Environment.IsDevelopment())
{
// Forward devserver socket calls during development
app.MapWhen(p => p.Request.Path.StartsWithSegments("/sockjs-node"), config =>
{
config.UseSpa(spa =>
{
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
});
});
}
// Serve the angular app
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (Environment.IsDevelopment())
{
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
}
});
How do I implement AuthorizeForScopes without that Action method posted above so I can serve my SPA on / only if the user is [Authorized] and [AuthorizeForScopes]?
How do I implement AuthorizeForScopes without that Action method posted above so I can serve my SPA on / only if the user is [Authorized] and [AuthorizeForScopes]
There are two ways of doing this in the startup.cs > ConfigureServices(IServiceCollection services) method.
Option 1:
services.AddControllers(o =>
{
o.Filters.Add(new RequiredScopeAttribute(new[] { "access_as_user" }));
});
This will automatically add the Scope attribute to all controllers, but this will not work if you have endpoints with [AllowAnonymous].
To get around this the other way is to add your own authorization policy in the same ConfigureServices method for scope:
services.AddAuthorization(o =>
{
var authPolicy = new AuthorizationPolicyBuilder(new[] { JwtBearerDefaults.AuthenticationScheme })
.RequireAuthenticatedUser()
.RequireAssertion(ctx =>
{
// Attempt with Scp claim
Claim scopeClaim = ctx.User.FindFirst(ClaimConstants.Scp);
// Fallback to Scope claim name
if (scopeClaim == null)
{
scopeClaim = ctx.User.FindFirst(ClaimConstants.Scope);
}
return scopeClaim != null && scopeClaim.Value.Split(' ').Intersect(new[] { "access_as_user" }).Any();
})
.Build();
o.DefaultPolicy = authPolicy;
});
The second option sets this auth policy as default for all routes via o.DefaultPolcy = authPolcy but unlike the first option this can be overridden by using [AllowAnonymous] on your method/controller.
You can also use o.AddPolicy("authMeUpScotty", authPolicy) instead of o.DefaultPolicy and use [Authorize(Policy = "authMeUpScotty")] if it was only for particular endpoints.
Hope this helps!
Having the below code to configure in Startup.cs
Variables.
private const string SwaggerDocumentVersionName = "v1";
private static string SwaggerDocumentServiceName => $"Users API({SwaggerDocumentVersionName})";
ConfigureServices method.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(
SwaggerDocumentVersionName,
new OpenApiInfo
{
Title = SwaggerDocumentServiceName,
Version = $"{SwaggerDocumentVersionName}"
});
});
Configure method.
app.UseSwagger(c =>
{
c.RouteTemplate = "/swagger/{documentName}/swagger.json";
});
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "swagger/ui";
c.SwaggerEndpoint($"/swagger/{SwaggerDocumentVersionName}/swagger.json", SwaggerDocumentServiceName);
});
When running locally (https://localhost:5001/swagger/ui resolved to https://localhost:5001/swagger/ui/index.html) definition is loaded correctly and everything seems fine.
Deploying the service to AWS Lambda as ASP.NET Core REST API and navigating to the URL (https://DNS_URL/API_PREFIX/swagger/ui resolved to https://DNS_URL/API_PREFIX/swagger/ui/index.html)it shows the below error loading the JSON definition.
The interesting part is that if you navigate to the JSON definition route (https://DNS_URL/API_PREFIX/swagger/v1/swagger.json) it shows the definition.
The main URL for the API you have released on lambda is https://DNS_URL/API_PREFIX/
Swagger UI needs to fetch the swagger.json file in order for it to work, and for your localhost it is working correctly since https://localhost:5001/swagger/v1/swagger.json is a valid endpoint
(*you have no prefix here)
And the version deployed to lambda is trying to fetch this swagger.json file under
https://DNS_URL/swagger/v1/swagger.json - without your API_PREFIX, thus it's returning 404, not found and swagger ui is displaying the Error message.
Quick fix, which you might apply, that I think would work:
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "swagger/ui";
c.SwaggerEndpoint($"{env.IsDevelopment() ? "" : API_PREFIX}/swagger/{SwaggerDocumentVersionName}/swagger.json", SwaggerDocumentServiceName);
});
Where the API_PREFIX is a string starting with '/'
i'm using swagger ui in my asp.net mvc
Swashbuckle v 5.6
asp.net v 4.8
on local machine everythink working successfully but unfortunately after deploy website to cloud, swagger not load static content, but wen i navigate to these resources using chrome it's loadded successfully
i config swagger using:
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.Schemes(new[] { "http", "https" });
c.IgnoreObsoleteActions();
c.SingleApiVersion("v1", "Console - API");
c.ApiKey("auth")
.Description("API Key Authentication")
.Name("auth")
.In("header");
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
c.IncludeXmlComments(String.Format(#"{0}\bin\docs.xml", System.AppDomain.CurrentDomain.BaseDirectory));
c.UseFullTypeNameInSchemaIds();
})
.EnableSwaggerUi(c =>
{
c.DocumentTitle("Console - API");
c.EnableApiKeySupport("auth", "header");
c.InjectJavaScript(thisAssembly, "Console.API.Assets.Swagger.SwashbuckleCustomAuth.js");
c.InjectStylesheet(thisAssembly, "Console.API.Assets.Swagger.SwashbuckleCustomStyle.css");
});
}
Do i miss any thing? dose https effects swagger load static files?
pss: i don't think this issue related to iis because i have placed a static file in root folder of the website and i was able to load it
The IIS was block simultaneous requests, and the solution was by increment maximum concurrent connections in site advance settings in IIS
I created an angular project using the .NET core 2.2 and the spa template.
I changed startup.cs to use a proxy development server when serving angular, so I could start my server and my client code independently.
I am using .net core 2.2 documentation and this blog post as a reference:
https://www.codingflow.net/building-single-page-applications-on-asp-net-core-2-2/
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
//spa.UseAngularCliServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
}
});
I then ran the following commands in two terminals
dotnet run
_
cd ./ClientApp\
npm start
When I navigate in my browser (chrome) to localhost:4200, webpack will serve my app. However, when I navigate to the fetch-data page from the template, the api call to
http://localhost:4200/api/SampleData/WeatherForecasts
Made in the fetch-data component, part of the standard template
import { Component, Inject } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-fetch-data',
templateUrl: './fetch-data.component.html'
})
export class FetchDataComponent {
public forecasts: WeatherForecast[];
constructor(http: HttpClient, #Inject('BASE_URL') baseUrl: string) {
http.get<WeatherForecast[]>(baseUrl + 'api/SampleData/WeatherForecasts').subscribe(result => {
this.forecasts = result;
}, error => console.error(error));
}
}
interface WeatherForecast {
dateFormatted: string;
temperatureC: number;
temperatureF: number;
summary: string;
}
Returns a 404
GET http://localhost:4200/api/SampleData/WeatherForecasts 404 (Not Found)
HttpErrorResponse {headers: HttpHeaders, status: 404, statusText: "Not Found", url: "http://localhost:4200/api/SampleData/WeatherForecasts", ok: false, …}
Of course, this error is not thrown if I run IIS Express in debug mode from visual studio. This will launch a browser window on a different port (not 4200, 44333 usually) where all of the API requests seem to route fine.
I would like to be able to manage my client code independently of my server code. How can I do this? What is wrong with my current setup?
While spliting the SPA project into Front-End and Back-End, you need to change the baseUrl in the angular.
For dotnet run, it will run the .net core project under https://localhost:5001 and http://localhost:5000.
For npm start, it will run the angular project under http://localhost:4200.
For #Inject('BASE_URL') baseUrl: string, it will return the angular url instead of core api url.
So, you need to change the request base url and you could try like:
export class FetchDataComponent {
public forecasts: WeatherForecast[];
private apiUrl: string = "https://localhost:5001/";
constructor(http: HttpClient, #Inject('BASE_URL') baseUrl: string) {
http.get<WeatherForecast[]>(this.apiUrl + 'api/SampleData/WeatherForecasts').subscribe(result => {
this.forecasts = result;
}, error => console.error(error));
}
}
Since Angualr and Core project run under different ports, you need to enable Cors in the Core project like
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//rest code
app.UseCors(builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
//spa.UseAngularCliServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://localhost:4200");
}
});
}
Thank you Tao Zhou for your answer. For my project, allowing for request from any origin was not an option, so I had to seek out a different answer.
Angular launches by default on port 4200. I created a proxy.conf.json file, and added it to the same directory as my package.json
{
"/api/*": {
"target": "https://localhost:5001",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
I then edit my npm start scrip in package.json to be the following
"scripts": {
"ng": "ng",
"start": "npm run ng -- serve --proxy-config proxy.conf.json",
This instructs webpack to proxy all routes beginning with /api to port 5001. This is the convention .NET core for all routes that don't serve a view, and is ideal for my project.
This also allows us to keep stricture controls on header and origin for security reasons.
I'm using ASP.NET Core and NSwag to host and describe a new web service hosted in IIS with Windows Authentication.
Locally I run the web service using https, but when I deploy to a test environment the web service sits behind a load balancer with SSL-offloading. This means that even though the site appears to run under SSL in the browser, the actual binding in IIS is http. So my Swagger UI page (and swagger.json definition) describes the schemes supported as http.
I'd like the Schemes element in the Swagger.json that I use to read "https" instead of "http". Would anyone be able to help me find the property I need to set in my code to set the scheme manually?
{
x-generator: "NSwag v11.19.1.0 (NJsonSchema v9.10.72.0 (Newtonsoft.Json v11.0.0.0))",
swagger: "2.0",
info: {
title: "My API title",
description: "Provides access to data.",
version: "1.0.0"
},
host: "myhostname.net",
schemes: [
"http"
],
etc...
}
Boom. Got it!
Finally found an answer on Github and the following code did the trick:
app.UseSwaggerWithApiExplorer(config =>
{
//...other code omitted...
config.PostProcess = settings =>
{
settings.Schemes.Clear();
settings.Schemes.Add(NSwag.SwaggerSchema.Https);
};
});
EDIT:
for NSwag v12 use:
app.UseSwagger(configure => configure.PostProcess = (document, _) => document.Schemes = new[] { SwaggerSchema.Https });
My project was using NSwag v13 and the below worked for me.
app.UseOpenApi(a => {
a.PostProcess = (document, _) => {
document.Schemes = new[] { OpenApiSchema.Https, OpenApiSchema.Http };
};
});
Source:
https://github.com/RicoSuter/NSwag/issues/1545