I’m trying to setup a React web application using an asp.net core web API in two separate projects. I followed Microsoft’s guide for setting up the templates using two separate visual studio projects - Create an ASP.NET Core app with React - https://learn.microsoft.com/en-us/visualstudio/javascript/tutorial-asp-net-core-with-react?view=vs-2022
Out of the box, everything works and I am able to proxy my api request to the sample /weatherforecast controller’s get method. Next, I created my own controller and now I don’t seem to be able to proxy my request to this controller.
Making a request to https://localhost:3000/api/employee just returns 404. I also see a message in my console about no matching route.
I've tried various patterns for the context all with no luck. I would think that '/api' is all I need.
My setupProxy.js file looks like this.
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
['/api/weatherforecast', '/api/employee'],
createProxyMiddleware({
target: 'https://localhost:7157',
secure: false,
})
); };
My launch settings.json in my web api project
{
"profiles": {
"WebApi": {
"commandName": "Project",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7157;http://localhost:5157"
},
"IIS Express": {
"commandName": "IISExpress",
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:34955",
"sslPort": 44352
}
}
}
Partial of My Employee Controller
namespace WebApi.Controllers {
[ApiController]
[Route("api/[controller]")]
public class EmployeeController : Controller
{
private readonly ILogger<EmployeeController> logger;
private readonly Context context;
public EmployeeController(ILogger<EmployeeController> logger, TenthContext context)
{
this.logger = logger;
this.context = context;
}
// GET: EmployeeController
[HttpGet(Name="GetEmployee")]
public IEnumerable<Employee> Get()
{
var employees = context.Employees.ToList();
return employees;
}
Not sure what the deal was but all worked as expected after I rebooted.
Related
I have an old application and i have micro-frontend structure there. The application was using react-scripts#4 and this version had several vulnerabilities so i decided to upgrade it. While react-scripts#5 is not compatible with webpack#4, i needed to upgrade it too. However, I cannot use output.libraryTarget='system' or output.library.type='system' anymore. If i remove this part then app works but i cannot access micro-frontends. Here is my craco.config file;
import {
BabelOptions,
DevServerOptions,
EsLintOptions,
WebpackOptions,
} from '#craco/craco';
export default {
babel: {
plugins: [['#babel/plugin-proposal-decorators', { legacy: true }]],
} as BabelOptions,
eslint: {
enable: false,
} as EsLintOptions,
webpack: {
configure: (webpackConfig) => {
// use system
webpackConfig.output.libraryTarget = 'system';
delete webpackConfig.optimization;
// adding externals
webpackConfig.externals = [
'#test-app/mf-example',
];
return webpackConfig;
},
} as WebpackOptions,
// Adding Server
devServer: ((devServerConfig) => {
// devServerConfig.injectClient = false;
devServerConfig.proxy = {
...devServerConfig.proxy,
'/mf-example': {
target: process.env.MF_EXAMPLE,
secure: false,
changeOrigin: true,
}
};
return devServerConfig;
}) as DevServerOptions,
};
Btw, the problem is not related to other files or configs so i did not share them.
If we follow ngrx-data example and look at the Entity DataService, we can fetch the Hero data that we have in-memory (hard-coded) without any configuration. The default will work the same as if we configured:
const defaultDataServiceConfig: DefaultDataServiceConfig = {
root: 'api', // or a running server url, e.g: 'http://localhost:4000/api'
timeout: 3000, // request timeout
}
and in e.g: EntityStoreModule
#NgModule({
providers: [{ provide: DefaultDataServiceConfig, useValue: defaultDataServiceConfig }]
})
Question:
How will we configure our app to fetch data for entity "Heros" from the default source:
root: 'api'
and data for entity "Villans" from a URL:
root: 'http://localhost:4000/villans'
and data for other entities from their (other/various) respective URLs ...?
After reviewing the docs specifically:
Custom EntityDataService and
Replace the HttpUrlGenerator
I came up with this solution. Anyone feel free to comment.
Define/review your data types - entity metadata - entity names;
Create mapping to plurals for non-default plural entity names (default is: name + 's');
For entities with the non-default root URL create a mapping of entity names to specific URL;
File: ../entity-metadata.ts
// Step 1:
const entityMetadata: EntityMetadataMap = {
Hero: {},
Villan: {},
Creature: {},
DataA01: {}
// etc.
}
// Step 2:
const pluralNames = {
Hero: 'heroes',
DataA01: 'data-a01'
}
export const entityConfig = {
entityMetadata,
pluralNames
};
// Step 3:
export const rootUrls = {
// Hero: - not needed here, data comes from default root
Villan: 'http://localhost:4001',
Creature: 'http://localhost:4001',
DataA01: 'http://remoteserver.net:80/publicdata',
}
Replace the HttpUrlGenerator (doc) with your own URL generator (DynamicHttpUrlGenerator)
File: ../http-dyn-url-generator.ts
import { Injectable } from '#angular/core';
import {
DefaultHttpUrlGenerator,
HttpResourceUrls,
normalizeRoot,
Pluralizer,
DefaultPluralizer,
} from '#ngrx/data';
import { rootUrls } from '../data/ngrx-data/db01-entity-metadata';
#Injectable()
export class DynamicHttpUrlGenerator extends DefaultHttpUrlGenerator {
constructor(private aPluralizer: Pluralizer = new DefaultPluralizer(undefined)) {
super(aPluralizer);
}
protected getResourceUrls(entityName: string, root: string): HttpResourceUrls {
let resourceUrls = this.knownHttpResourceUrls[entityName];
if ( ! resourceUrls) {
// rootUrls contains
// mapping of individual ngrx data entities
// to the root URLs of their respective data sources.
// It contains only entities which do not have
// the default root URL.
if (rootUrls.hasOwnProperty(entityName)) {
root = rootUrls[entityName];
}
const nRoot = normalizeRoot(root);
const url = `${nRoot}/${this.aPluralizer.pluralize(entityName)}/`.toLowerCase();
// remove after testing
console.log('-- entityName: ' + entityName + ', URL: ' + url)
resourceUrls = {
entityResourceUrl: url,
collectionResourceUrl: url
};
this.registerHttpResourceUrls({ [entityName]: resourceUrls });
}
return resourceUrls;
}
}
For each of your data entity create a custom EntityDataService
HeroDataService
VillanDataService
CreatureDataService
DataA01DataService
etc.
(doc and code is here) - the code example is under
// store/entity/hero-data-service.ts
Register your DynamicHttpUrlGenerator and your custom EntityDataServices in your app's module, in my case:
File: ../ngrx-data-store.module.ts
(in a simple app, directly in file: app.module.ts)
#NgModule({
imports: [ ... ],
providers: [ { provide: HttpUrlGenerator, useClass: DynamicHttpUrlGenerator },
HeroDataService,
VillanDataService,
CreatureDataService,
DataA01DataService
]
})
Use your custom EntityDataServices in your components for each given entity the same way as all standard or default EntityDataServices to fetch data. The data will be pulled from the respective URLs you set in the const: rootUrls.
Don't forget to get your URLs' data server(s) configured and started.
A few important considerations:
on your server you may need to enable CORS handling. E.g: on nestjs use:
app.enableCors();
if your client app uses: Angular in-memory-web-api you need to enable access to remote server as follows:
File: ../in-mem-data.module.ts (or as you named it)
import { NgModule } from '#angular/core';
import { HttpClientModule } from '#angular/common/http';
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemDataService } from '../../services/data/in-mem-data/in-mem-data.service';
#NgModule({
imports: [
HttpClientModule,
HttpClientInMemoryWebApiModule.forRoot(InMemDataService, {
passThruUnknownUrl: true // <--- IMPORTANT for remote data access
}),
]
})
export class InMemDataModule {}
We have setup our development environment with webpack-dev-server. We use its proxy config to communicate with the backend.
We have a common login page in the server which we use in all our applications. We it is called, it sets a session cookie which expected to passed with subsequent requests. We have used the following config but the cookie is not set in the browser for some reason. I can see it in response header in the network tab of dev tool.
const config = {
devServer: {
index: "/",
proxy: {
"/rest_end_point/page": {
target: "https://middleware_server",
secure : false
},
"/": {
target: "https://middleware_server/app/login",
secure : false
},
}
The https://middleware_server/app/login endpoint returns the login page with the set-cookie header.
The proxy is used to avoid CORS errors when accessing login pages and API calls.
Upto this point no code from the application is executed. Do we have to do something in the coomon login page to get the cookie set?
the application is written with React.
Any help would be appreciated.
I have the same use case and this is what I have done.
In my case, I have multiple proxy targets so I have configured the JSON (ProxySession.json) accordingly.
Note: This approach is not dynamic. you need to get JSESSIONID manually(session ID) for the proxy the request.
login into an application where you want your application to proxy.
Get the JSESSIONID and add it in JSON file or replace directly in onProxyReq function and then run your dev server.
Example:
Webpack-dev.js
// Webpack-dev.js
const ProxySession = require("./ProxySession");
config = {
output: {..........},
plugins: [.......],
resolve: {......},
module: {
rules: [......]
},
devServer: {
port: 8088,
host: "0.0.0.0",
disableHostCheck: true,
proxy: {
"/service/**": {
target: ProxySession.proxyTarget,
changeOrigin: true,
onProxyReq: function(proxyReq) {
proxyReq.setHeader("Cookie", "JSESSIONID=" + ProxySession[buildType].JSESSIONID + ";msa=" + ProxySession[buildType].msa + ";msa_rmc=" + ProxySession[buildType].msa_rmc + ";msa_rmc_disabled=" + ProxySession[buildType].msa_rmc);
}
},
"/j_spring_security_check": {
target: ProxySession.proxyTarget,
changeOrigin: true
},
"/app_service/websock/**": {
target: ProxySession.proxyTarget,
changeOrigin: true,
onProxyReq: function(proxyReq) {
proxyReq.setHeader("Cookie", "JSESSIONID=" + ProxySession[buildType].JSESSIONID + ";msa=" + ProxySession[buildType].msa + ";msa_rmc=" + ProxySession[buildType].msa_rmc + ";msa_rmc_disabled=" + ProxySession[buildType].msa_rmc);
}
}
}
}
ProxySession.json
//ProxySession.json
{
"proxyTarget": "https://t.novare.me/",
"build-type-1": {
"JSESSIONID": "....",
"msa": "....",
"msa_rmc": ...."
},
"build-type-2": {
"JSESSIONID": ".....",
"msa": ".....",
"msa_rmc":"....."
}
}
I met the exact same issue, and fixed it by this way:
This is verified and worked, but it's not dynamic.
proxy: {
'/my-bff': {
target: 'https://my.domain.com/my-bff',
changeOrigin: true,
pathRewrite: { '^/my-bff': '' },
withCredentials: true,
headers: { Cookie: 'myToken=jx42NAQSFRwXJjyQLoax_sw7h1SdYGXog-gZL9bjFU7' },
},
},
To make it dynamic way, you should proxy to the login target, and append a onProxyRes to relay the cookies, something like: (not verified yet)
onProxyRes: (proxyRes: any, req: any, res: any) => {
Object.keys(proxyRes.headers).forEach(key => {
res.append(key, proxyRes.headers[key]);
});
},
"/api/**": {
...
cookieDomainRewrite: { "someDomain.com": "localhost" },
withCredentials: true,
...
}
You can use this plugin to securely manage auth cookies for webpack-dev-server:
A typical workflow would be:
Configure a proxy to the production service
Login on the production site, copy authenticated cookies to the local dev server
The plugin automatically saves your cookie to system keychain
https://github.com/chimurai/http-proxy-middleware#http-proxy-options
use option.cookieDomainRewrite and option.cookiePathRewrite now
cookies ??
devServer: {
https: true, < ------------ on cookies
host: "127.0.0.1",
port: 9090,
proxy: {
"/s": {
target: "https://xx < --- https
secure: false,
//pathRewrite: { "^/s": "/s" },
changeOrigin: true,
withCredentials: true
}
}
}
. . . . . . . . . . .
I'm working on single-sign-on implementation in my project with microservice architecture application, using spring-cloud-netflix. At the moment I have done with OAuth2 service and gateway service.
Here's my security config for gateway:
/**
* SSO security config.
*/
#Configuration
#EnableZuulProxy
#EnableOAuth2Sso
#EnableWebSecurity
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2ClientAuthenticationProcessingFilter ssoFilter;
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private SsoLogoutSuccessHandler logoutSuccessHandler;
/**
* SSO http config.
*
* #param http configurer
* #throws Exception exception
*/
#Override
public void configure(HttpSecurity http) throws Exception {
http
// .cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/test", "/favicon.ico", "/sockjs-node/**", "/static/**", "/*.js", "/*.jpg",
"/rest/**", "/uaa/**", "/backend/**",
"/users/**", "/files/**", "/roles/**").permitAll()
.anyRequest().authenticated().and()
// .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.logout().logoutSuccessHandler(logoutSuccessHandler)
.and()
.sessionManagement().maximumSessions(1)
.expiredUrl("/")
.maxSessionsPreventsLogin(false);
// http.addFilterAfter(ssoFilter, BasicAuthenticationFilter.class);
}
/**
* OAuth2 config.
*/
#Configuration
protected static class OAuth2Config {
#Bean
public OAuth2ClientAuthenticationProcessingFilter ssoFilter(
SsoLoginSuccessHandler ssoLoginSuccessHandler,
OAuth2ClientContext beaconOAuth2ClientContext,
RemoteTokenServices remoteTokenServices,
OAuth2ProtectedResourceDetails resourceDetails) {
OAuth2ClientAuthenticationProcessingFilter filter =
new OAuth2ClientAuthenticationProcessingFilter("/login");
filter.setRestTemplate(new OAuth2RestTemplate(resourceDetails,
beaconOAuth2ClientContext));
filter.setTokenServices(remoteTokenServices);
// filter.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler());
return filter;
}
//
// #Bean
// public CorsConfigurationSource corsConfigurationSource() {
// final CorsConfiguration configuration = new CorsConfiguration();
// configuration.setAllowedOrigins(ImmutableList.of("*"));
// configuration.setAllowedMethods(ImmutableList.of("HEAD",
// "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
// configuration.setAllowCredentials(true);
// configuration.setAllowedHeaders(
// ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
// final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// source.registerCorsConfiguration("/**", configuration);
// return source;
// }
}
}
gateway yaml config
security:
oauth2:
client:
preEstablishedRedirectUri: http://localhost:3000/login
registeredRedirectUri: http://localhost:3000/login
accessTokenUri: http://localhost:10035/uaa/oauth/token
userAuthorizationUri: http://localhost:10035/uaa/oauth/authorize
clientId: react_app
clientSecret: react_app_secret
useCurrentUri: true
resource:
tokenInfoUri: http://localhost:10035/uaa/oauth/check_token
I'm bit confused, how i should authorize react app client, if it running on different port (webpack dev server, port 3000)?
One alternative is to setup a proxy using webpack, for example, in your webpack configuration add it like this:
devServer: {
contentBase: '/static',
historyApiFallback: true,
port: 3000,
compress: false,
inline: false,
hot: true,
host: '0.0.0.0',
proxy: {
'/api': { // <---- Intercept all calls to `/api`
target: 'http://localhost:8080', // <--- Your API server in a different port
changeOrigin: true,
secure: false,
},
},
},
The previous configuration setup a proxy, so whenever there's a request to /api, it will proxy the request to your server running in the other port.
namespace MyQuotesApp
{
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseDefaultFiles();
app.UseStaticFiles();
//app.Run(async (context) =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
app.UseMvc();
}
}
}
UseDefaultFiles pick these files by default.
default.htm
default.html
index.htm
index.html
If that not worked in your case. You can specify name of your default file with DefaultFilesOptions.
DefaultFilesOptions options = new DefaultFilesOptions();
options.DefaultFileNames.Clear();
options.DefaultFileNames.Add("index.html");
app.UseDefaultFiles(options);
You can also use app.UseFileServer();, it combines the functionality of
app.UseDefaultFiles();
app.UseStaticFiles();
Note: UseDefaultFiles must be called before UseStaticFiles to serve the default file. UseDefaultFiles is a URL re-writer that doesn't actually serve the file. You must enable the static file middleware (UseStaticFiles) to serve the file.
P.S. also update your packages to most latest.
Try this.
app.UseMvc(config =>
{
config.MapRoute(
name: "Default",
template: "{controller=*YourControllerName*}/{action=Index}/{id?}"
);
});