I am using the tutorials on https://spring.io/guides/tutorials/spring-security-and-angular-js to connect AngularJS app to Spring Security backend for authentication.
When I try to log in (the username and password already exist in the backend), I get the following error in browser consoles (am testing my app in Chrome, Firefox and IE, all of them give me the same error).
The error is:
XMLHttpRequest cannot load http://SERVER_URL:4444/test/user.
Response to preflight request doesn't pass access control
check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:3000' is therefore not allowed access.
The response had HTTP status code 401.
Code:
var auth = angular.module("Admin");
authService.service("AuthService", function($http, $rootScope){
this.login = function(credentials){
var headers = credentials ? {authorization: "Basic " + btoa(credentials.username + ":" + credentials.password)} : {};
$http.get("http://SERVER_URL:4444/test/user",{headers:headers})
.then(function(response){
console.log("success");
$rootScope.authenticated = true;
}, function(){
console.log("failed");
$rootScope.authenticated = false;
});
};
});
When I access the same link in the browser, I get authenticated box and I type in the username and password and I can see the result. But I don't understand why can't I access the same link via $http() successfully.
What is the correct way to do it? I have read numerous posts and Q&As but none solves the problem. I just don't understand what is going on.
You need to register a CORS filter in your Spring Security configuration on your backend to allow the origin of your Angular app access to the backend, since it is making cross-origin requests (it's not coming from the same host and port):
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
}
Alternatively, if you are using an older version of Spring Security that does not support CorsConfiguration you can accomplish the same thing by implementing a custom filter and injecting it into your filter chain:
#Component
public class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "*");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
By default, the browsers have implemented the security policy, not to execute the scripts loaded from different domains. Even if it's from the same host and different port, it will not allow as it's violates the cross domain policy.
There are many ways to overcome this issue.
Best way is to enable the CORS at the server, so that from the first response onward, the server will instruct the browsers to allow to cross the domain. Refer the link to get more http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server
Related
This question already has answers here:
"Cross origin requests are only supported for HTTP." error when loading a local file
(30 answers)
Closed 2 years ago.
No matter what I try I continue to get error:
"Access to XMLHttpRequest at
'file:///C:/api/v1/backtest?{%22strategy_name%22:%22sma"}' from origin
'null' has been blocked by CORS policy: Cross origin requests are only
supported for protocol schemes: http, data, chrome, chrome-extension,
chrome-untrusted, https."
Here is the ajax call:
var inputs = {};
inputs.strategy_name = strategy_name;
inputs.start_date = document.getElementById("start_date").value;
inputs.end_date = document.getElementById("end_date").value;
inputs.formula = document.getElementById("formula").value;
inputs.symbol = document.getElementById("backtest_symbol").value
inputs.benchmark_symbol = document.getElementById("benchmark_symbol").value
let jsonStrategyInputs = JSON.stringify(inputs);
console.log("jsonStrategyInputs=",jsonStrategyInputs);
$.ajax({
type : "GET",
contentType : "application/json",
datatype : "json",
url : "/api/v1/backtest",
data : jsonStrategyInputs,
success : function(data){
//do a bunch of business logic
},
error: function(e) {
alert('Error: '+e);
}
});
Here is the Spring Boot controller code:
#CrossOrigin(origins = "*")
#RestController
#RequestMapping(path="/api/v1")
public class StrategyController {
#Autowired
private StrategyService strategyService;
#GetMapping(value = "/backtest")
#ResponseBody
public JsonResponse backtestStrategy(#RequestBody BacktestInputs inputs, BindingResult result) throws Exception {
bla bla
}
This gives noted exception. So I added this class to the project:
#Component
public class SimpleCORSFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
response.setHeader("Access-Control-Expose-Headers", "Location");
chain.doFilter(req, res);
}
#Override
public void init(FilterConfig filterConfig) {}
#Override
public void destroy() {}
}
Same error. I have tried countless combinations of similar post answers but still no luck. Back to the experts. Any suggestions?
Of note, calling a similar controller with a jquery method works fine, like this:
function loadWatchlist() {
$.getJSON("http://localhost:8080/api/v1/watchlist?name=My%20Portfolio", function (data) {
data.forEach(function (item) {
console.log(item.watchlistId.symbol);
});
}
EDIT
Interestingly, if I click the link in the browser console, it says "Invalid character found in the request target [/api/v1/backtest?{%22strategy_name%22:%22sma-cross-and-extreme-hi-lo%22,%22start_date%22:%221928-12-30%22,%22end_date%22:%222020-06-19%22,%22formula%22:%22LET%20InitialBuyStop=StopLoss(InitialBuy,H)\nLET%20TrailingBuyStop=StopLoss(TrailingBuy,5%)\nLET%20InitialSellStop=StopLoss(InitialSell,L);\nLET%20TrailingSellStop=StopLoss(TrailingSell,5%)\nLET%20CrossAboveSMA200=C(1)%3CSMA(200,1)%20AND%20C%3ESMA(200)\nLET%20CrossBelowSMA200=C(1)%3ESMA(200,1)%20AND%20C%3CSMA(200)\nLET%20AboveUpperEnvCh=C%3EEnvCh(200,20).Upper\nLET%20BelowLowerEnvCh=C%3CEnvCh(200,20).Lower\nLET%20YesterdayWasOut=Position(1)==0\nLET%20YesterdayWasIn=Position(1)==1\nLET%20HitBuyStop=C%3EBuyStop\nLET%20HitSellStop=C%3CSellStop\nIF(CrossAboveSMA200)%20THEN%20BUY\nIF(CrossBelowSMA200)%20THEN%20SELL\nIF(AboveUpperEnvCh%20AND%20DnBar%20AND%20YesterdayWasIn)%20THEN%20SellAndSetBuyStop\nIF(AboveUpperEnvCh%20AND%20HitBuyStop%20AND%20YesterdayWasOut)%20THEN%20BuyAndResetStops\nIF(BelowLowerEnvCh%20AND%20YesterdayWasOut%20AND%20UpBar)%20THEN%20BuyAndSetSellStop\nIF(BelowLowerEnvCh%20AND%20YesterdayWasIn%20AND%20HitSellStop)%20THEN%20SellAndResetStops\nIF(TrailingBuyStop%3CBuyStop)%20THEN%20BuyStop=TrailingBuyStop\nIF(TrailingSellStop%3ESellStop)%20THEN%20SellStop=TrailingSellStop%22,%22symbol%22:%22SPP-500%22,%22benchmark_symbol%22:%22SP-500%22]"
It seems it is only url encoding the spaces and not {}, (), '=', '-' or the '%' in the 5%. May be I should not use JSON.stringify(inputs)? I also added more relevant info in this post above the ajax call.
the error you get is caused by your Chrome browser, so #CorssOrigin in the backend is no use.
this is cased since your ajax code is trying to access your own local resource.
to avoid this, you need to add some options when you execute Chrome browser.
here's a sample for executing Chrome on Windows
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir="C:\Users\ユーザ名\Local\Google\Chrome\User Data"
I have a RESTfull Backend implemented with Spring security running on localhost:8080, a Login filter responds to login requests with a token placed in the headers, I didn’t even implemented an Endpoint method for that, It is all done by Spring Security magic by the following line of code:
protected void configure(HttpSecurity http) throws Exception {
// disable caching
http.headers().cacheControl();
http.csrf().disable() // disable csrf for our requests.
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
…
The frontend is a Angularjs Nodejs NPM Bower project running a static http server on localhost:8000. On frontend I make a simple POST request as follows:
$scope.login = function () {
var data = {
"username":$scope.username,
"password":$scope.password
}
$http({
url: baseUrl,
method: "POST",
data: data
}).then(function (response) {
console.log("###### SUCCESS #####");
var headers = response.headers();
console.log("headers: "+JSON.stringify(headers));
var token = headers.authorization.split(" ")[1];
console.log("token: "+token);
$http.defaults.headers.common['Authorization'] = token;
$location.path("/view1");
}, function (responseData) {
// called asynchronously if an error occurs
// or server returns responseData with an error status.
console.log("###### FAIL #####");
console.log("Response: "+JSON.stringify(responseData));
$window.alert("Failed to Login");
});
This works like a charm in IE (also with curl, wget and python requests) but it miserably failing on Chrome and Safary.
I know that those Browsers are blocking CORS POSTs, making the request empty as soon as the reach the backend, in fact I don’t see any data when I log out the request from backend. I tried every possible combination of:
Frontend side:
1) $http(method: POST)
2) $http.post(
3) Adding flags: Access-Control-Allow-Origin, Access-Control-Expose, etc.
4) Adding all possible header combination: ‘Content–Type’:’application/
Browser side:
1) Start chrome with flag: --disable-web-security
2) Installing Chrome extension CORS
Backend side:
1) Spring Security Disable csfr
2) Spring Security Permit all
3) Spring Security HttpMethod.OPTION
Nothing, NHOTING worked for me!
is there something I’m missing?
Is there another way to send POST requests?
EDIT
As discussed, I modified the classes as follow:
WebSecurityConfig:
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new CORSFilter(), BasicAuthenticationFilter.class)
and implemented the CORSFilter as suggestet.
I also add the WebConfig class as suggested:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8000")
.allowedMethods("PUT", "POST");
}
}
Because of the empty string the login filter throws:
com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input
this will be chatched by spring security which denied the access.
I also triyed to move the frontend server on other ports then 8000 (4200, 7000, etc.) with no success.
You need to enable Cors support in spring. In your WebConfig you can override addCorsMappings
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:4200"); //url of where angular is running.
}
}
This enables cors for the whole application. You can also be more specific with your mappings allowing specific header, and methods.
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3");
}
You can also allow user #CrossOrgin at the class and method level
#CrossOrigin(origin = "http://domain2.com",
maxAge = 3600)
public class ApiController {
}
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html
https://spring.io/guides/gs/rest-service-cors/
I have used a CORS filter before, and it worked well:
public class CORSFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", "*");
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
response.addHeader("Access-Control-Max-Age", "1");
}
filterChain.doFilter(request, response);
}
}
and then add this to your config:
.addFilterBefore(new CORSFilter()), BasicAuthenticationFilter.class)
Yes, I know what you are thinking - yet another CORS question, but this time I'm stumped.
So to start off, the actual error message:
XMLHttpRequest cannot load http://localhost/Foo.API/token. The
value of the 'Access-Control-Allow-Origin' header in the response must
not be the wildcard '*' when the request's credentials mode is
'include'. Origin 'http://localhost:5000' is therefore not allowed
access. The credentials mode of requests initiated by the
XMLHttpRequest is controlled by the withCredentials attribute.
I'm not sure what is meant by credentials mode is 'include'?
So when I perform the request in postman, I experience no such error:
But when I access the same request through my angularjs web app, I am stumped by this error. Here is my angualrjs request/response. As you'll see the response is OK 200, but I still receive the CORS error:
Fiddler Request and Response:
The following image demonstrates the request and response from web front-end to API
So based on all the other posts I've read online, it seems like I'm doing the right thing, that's why I cannot understand the error. Lastly, here is the code I use within angualrjs (login factory):
CORS Implementation in API - Reference purposes:
Method 1 used:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
EnableCrossSiteRequests(config);
}
private static void EnableCrossSiteRequests(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("*", "*", "*")
{
SupportsCredentials = true
};
config.EnableCors(cors);
}
}
Method 2 used:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
The issue stems from your Angular code:
When withCredentials is set to true, it is trying to send credentials or cookies along with the request. As that means another origin is potentially trying to do authenticated requests, the wildcard ("*") is not permitted as the "Access-Control-Allow-Origin" header.
You would have to explicitly respond with the origin that made the request in the "Access-Control-Allow-Origin" header to make this work.
I would recommend to explicitly whitelist the origins that you want to allow to make authenticated requests, because simply responding with the origin from the request means that any given website can make authenticated calls to your backend if the user happens to have a valid session.
I explain this stuff in this article I wrote a while back.
So you can either set withCredentials to false or implement an origin whitelist and respond to CORS requests with a valid origin whenever credentials are involved
If you are using CORS middleware and you want to send withCredentials boolean true, you can configure CORS like this:
var cors = require('cors');
app.use(cors({credentials: true, origin: 'http://localhost:5000'}));
Customizing CORS for Angular 5 and Spring Security (Cookie base solution)
On the Angular side required adding option flag withCredentials: true for Cookie transport:
constructor(public http: HttpClient) {
}
public get(url: string = ''): Observable<any> {
return this.http.get(url, { withCredentials: true });
}
On Java server-side required adding CorsConfigurationSource for configuration CORS policy:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// This Origin header you can see that in Network tab
configuration.setAllowedOrigins(Arrays.asList("http:/url_1", "http:/url_2"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
configuration.setAllowedHeaders(Arrays.asList("content-type"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()...
}
}
Method configure(HttpSecurity http) by default will use corsConfigurationSource for http.cors()
If you're using .NET Core, you will have to .AllowCredentials() when configuring CORS in Startup.CS.
Inside of ConfigureServices
services.AddCors(o => {
o.AddPolicy("AllowSetOrigins", options =>
{
options.WithOrigins("https://localhost:xxxx");
options.AllowAnyHeader();
options.AllowAnyMethod();
options.AllowCredentials();
});
});
services.AddMvc();
Then inside of Configure:
app.UseCors("AllowSetOrigins");
app.UseMvc(routes =>
{
// Routing code here
});
For me, it was specifically just missing options.AllowCredentials() that caused the error you mentioned. As a side note in general for others having CORS issues as well, the order matters and AddCors() must be registered before AddMVC() inside of your Startup class.
If it helps, I was using centrifuge with my reactjs app,
and, after checking some comments below, I looked at the centrifuge.js library file, which in my version, had the following code snippet:
if ('withCredentials' in xhr) {
xhr.withCredentials = true;
}
After I removed these three lines, the app worked fine, as expected.
Hope it helps!
I Am not able to fetch data from Rest Server.Following Error is coming:
"Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access."
Response is coming as "0", This is not hitting the Rest Method also.
Rest APT:
#POST
#Timed
#Path("updateScore")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response updateScore(Player player)
{
StringBuilder returnStr = new StringBuilder("Sucess");
return Response.ok().
header("Access-Control-Allow-Origin", "http://localhost:8080").
header("Access-Control-Allow-Methods", "GET, POST, PATCH, PUT, DELETE, OPTIONS").
header("Access-Control-Allow-Headers", "Origin, Content-Type, X-Auth-Token").
allow("OPTIONS").
status(200).
entity("Hello").build();
}
JavaScript Call
var url = "http://192.168.0.101:9090/api/FGame/updateScore/";
var client = new XMLHttpRequest();
client.open('POST', url, true);
client.setRequestHeader('Content-Type', 'application/json');
client.send('{"Name" : 12}');
client.onreadystatechange = function() {
alert(client.status);
alert(client.data)
};
But if I am changing to JavaScript call as following then working fine.
JavaScript Call
var url = "http://192.168.0.101:9090/api/FGame/updateScore/";
var client = new XMLHttpRequest();
client.open('POST', url, true);
client.send(null);
client.onreadystatechange = function() {
alert(client.status);
alert(client.data)
};
In my scenario I am using an angular front-end Api to collect the data from a form object and a Java rest Api to write the data to a MySql database. But I was always having the "Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource." error in the browser console. None of the suggested header modifications in the frontend like;
const headers: {
'Access-Control-Allow-Origin' : '*',
};
worked for me.
After hours of research in the internet one of my friends showed me the answer in not in the frontend but in the backend. Adding a WebConfig class to my java code fixed my problem.
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000/", "http://localhost:4200/")
.allowedMethods("*")
.allowedHeaders("*");
}
}
Hope this finds those who is looking for an answer to the same error.
I have read many questions and answers regarding Angular $http data service calling into another application Web Api. Yes I seen that some people say the reason "Postman" is allowed in is because its like 3rd party external app etc..
In the past I did have control of the Web Api in which i installed CORS etc..
However, I am thinking that something has to be POSSIBLE as several stackoverflow question and answers did indeed have answers that were giving me HOPE
Here is the error
XMLHttpRequest cannot load http://localhost:60127/api/Product/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:60128' is therefore not allowed access.
Project with Web Api code
public class ProductController : ApiController
{
// GET api/<controller>
public IHttpActionResult Get() {
IHttpActionResult ret = null;
// ....
}
URL of Web api (works with Postman)
http://localhost:60127/api/Product/
Angular code
function productList() {
//http://localhost:60127/api/Product/
//dataService.get("/api/Product")
dataService.get("http://localhost:60127/api/Product/")
.then(function (result) {
vm.products = result.data; // result is an http status
debugger; // stop to see the code
},
function (error) {
handleException(Error);
});
}
In WebAPiConfig Class you need to enable Cors while registering WebAPiConfig
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.EnableCors(new System.Web.Http.Cors.EnableCorsAttribute("*", "*", "*"));
}
}
have u enabled CORS? and add something like:
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class ProductController : ApiController
{
.....
}
If you have different origin for server and client , you need to set Access-Control-Allow-Origin header to true on server side,
response.getHttpHeaders().add("Access-Control-Allow-Origin", "*"); // will alow reuest from all API
So if you are using filter you can do like this
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}