I have two Projects, one is MVC (using Angular) and other is WebAPI. Windows Authentication is working fine in MVC (thanks to this article)
However, when I am making AJAX calls from MVC site through Angular to WebAPI then I get following error:
HTTP Error 401.2 - Unauthorized You are not authorized to view this
page due to invalid authentication headers.
Most likely causes:
No authentication protocol (including anonymous)is selected in IIS.
Only integrated authentication is enabled, and a client browser was used that does not support integrated authentication.
Integrated authentication is enabled and the request was sent through a proxy that changed the authentication headers before they
reach the Web server.
The Web server is not configured for anonymous access and a required authorization header was not received.
The "configuration/system.webServer/authorization" configuration section may be explicitly denying the user access.
I read this post but it is talking about HttpClient (while I am using JQuery or Angular) to make calls.
PLEASE NOTE: If I hit the WebAPI URL through Browser then Authentication works fine. So it must be something to do with AJAX request.
This is my code in Global.asax
protected void Application_BeginRequest()
{
if (ValidateRequest())
{
//var origin = Request.Headers["Origin"];
Response.Headers.Remove("Access-Control-Allow-Origin");
Response.AddHeader("Access-Control-Allow-Origin", matchedOrigin);
Response.Headers.Remove("Access-Control-Allow-Headers");
Response.AddHeader("Access-Control-Allow-Headers", CustomConfig.HEADERS);
Response.Headers.Remove("Access-Control-Allow-Methods");
Response.AddHeader("Access-Control-Allow-Methods", CustomConfig.METHODS);
}
// This is to avoid "Method 405 Not allowed" error
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
Response.Flush();
Response.End(); //Send the Empty Response for Options (Preflight Request)
}
}
I have done enough research but couldn't find a solution. So couple of things.
How can I resolve my above issue
Secondly what's the best approach for using Windows Authentication based on my scenario (And Project setup).
If it works in the browser directly but not when JavaScript is involved it will be a CORS issue so check you have it all enabled, including handling pre-flight OPTIONS verb.
In your case you will also need to check you are passing credentials e.g.
[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*",
methods: "*", SupportsCredentials = true)]
Note with credentials I don't think you can have a wildcard origin - needs explicit list.
See this link: https://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api#credentials.
In your Angular $http request make sure you have the property withCredentials: true.
Or if you are making a JQuery ajax call then:
xhrFields: {
withCredentials: true
}
If you still have pre-flight problems try a custom message handler like this (change the origin(s)):
public class ExampleMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Headers.Contains("Origin") && request.Method.Method == "OPTIONS")
{
var response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.OK;
response.Headers.Add("Access-Control-Allow-Origin", "http://localhost:8080/");
response.Headers.Add("Access-Control-Allow-Credentials", "true");
response.Headers.Add("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization");
response.Headers.Add("Access-Control-Allow-Methods", "DELETE, POST, PUT, OPTIONS, GET");
}
return base.SendAsync(request, cancellationToken);
}
}
Then:
public static void Register(HttpConfiguration config)
{
config.MessageHandlers.Add(new ExampleMessageHandler());
Related
When i tried to send a GET request to Activiti REST URL, using POSTMAN and configuring an authorization parameter (kermit:kermit) it works like a charm.
But when i tried to do the same thing, only with Angular $http service, it returns the following error:
XMLHttpRequest cannot load http://localhost:8080/activiti-rest/service/repository/deployments. 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:8081' is therefore not allowed access. The response had HTTP status code 401.
Here is my controller:
(function() {
'use strict';
angular
.module('doktorat-app-test')
.controller('TestController', TestController);
TestController.$inject = ['$http', '$base64'];
function TestController($http, $base64) {
var tcr = this;
$http.defaults.headers.common['Authorization'] = 'Basic ' + $base64.encode('kermit:kermit');
tcr.text = 'ssdsds';
$http.get('http://localhost:8080/activiti-rest/service/repository/deployments')
.then(function(response){
tcr.text = response.data;
});
}
})();
Has anyone encountered on similar error?
Spent more then 2 days trying to resolve this issue, but without any success.
P.S. I am using NodeJS http-server to run my Angular App, which runs on port 8081.
Since you are trying to access rest api which is on http://localhost:8081/ From http://localhost:8080/, browser will check if your server implement CORS using preflight request. You can get detail about CORS on below url:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
Normally application hosted in one server will not be allowed to access the resources hosted in other sever.This restriction is implemented by all most all browser now a days.
In order to make it work, your rest api server has to tell that some other servers will also be allowed to call. For this , you need to implement CORS filer in your rest api server.
Since you didn't specify which langauge you are using in REST, i am providing an open source CORS filter library for JAVA:
http://software.dzhuvinov.com/cors-filter.html
which solves your problem.
To allow CORS for a specific domain you can use a middleware as below:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://localhost:8081");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, X-Requested-With");
next();
});
I am writing a web application using AngularJS on the front end and JAX-RS on the backend. The front end can make HTTP post requests successfully to the backend when the CORS extension in chrome has 'enable CORS' on. When it is off, it comes back with an error -
The 'Access-Control-Allow-Origin' header contains multiple values 'http://127.0.0.1:XYZXX, *', but only one is allowed. Origin 'http://127.0.0.1:XYZXX' is therefore not allowed access.
The backend has a CORSFilter.java file configured as below -
public class CORSFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
response.getHttpHeaders().add("Access-Control-Allow-Origin", "*");
response.getHttpHeaders().add("Access-Control-Allow-Headers",
"origin, content-type, accept, authorization");
response.getHttpHeaders().add("Access-Control-Allow-Credentials","true");
response.getHttpHeaders().add("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS, HEAD");
return response;
}
}
I have tried suggestions in various stackoverflow posts like deleting the X-REQUESTED-WITH and setting UseXDomain to true -
delete $http.defaults.headers.common['X-Requested-With'];
$http.defaults.useXDomain = true;
I have also tried changing the content-type to Form/URLEncoded instead of Application/JSON. None seem to enable sending the POSTs except the Chrome extension - which makes me think the solution should be on client side.
Please help! - Thanks in advance.
My code is as below
$scope.LoadSearchResults = function () {
$scope.hotels = '';
$scope.allData = '';
var mydata = '';
$http({
url: 'myurl',
Authorization : 'code',
method: 'POST',
header: { 'Content-type': 'text/json' }
})
.success(function (mydata) {
$scope.hotels = JSON.parse(JSON.stringify(mydata.SearchResponse.Hotels.Hotel));
$scope.allData = JSON.parse(JSON.stringify(mydata.SearchResponse.Hotels.Hotel));
$scope.AllLocality($scope.allData);
})
.error(function (mydata) {
$scope.hotels = "Failed";
console.log(JSON.stringify(mydata));
});
}
and getting error is
"Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://example.com/some-page. (Reason: CORS header 'Access-Control-Allow-Origin' missing)."
How could I solve this problem?
this is not an Angular problem.
the reason is you are firing a cross origin request (e.g. your app sits on a domain and the request specified by url: 'myurl' is for a different domain) and HTTP does not allow it unless permissions are specifically added by the server receiving the request by adding a CORS header to the response.
to solve your problem you please check here one of the 4 options (I am sure you can find more depending on your specific setup):
make your app fire the request to the same domain, which is normally the preferred and most secure approach (e.g. the web app hosting the angular code is also responsible of responding to the xhr you are doing via $http)
if you have control on how the server creates the response, it should be fairly easy to add such headers (I cannot add an example here as it is entirely dependent on the web server or application gateway you are using)
if you don't have control over the server response (and most importantly its headers) you can add your own server in the middle that would act as a request proxy, thus making your JS app firing the request to your server (not incurring CORS problems) and the server itself making the proxy request to the 3rd party provider
if you just need GET requests (which doesn't seem the case from your snippet) you can use angular's $http JSONP if the server supports this type of requests.
Hi I have got it same issue but now i have resolved it by using below:
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: X-API-KEY, Origin, X-Requested-With, Content-Type, Accept, Access-Control-Request-Method, Authorization");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
please pass header on controller after parent::__construct();
I've started a new Yeoman AngularJS project, hosted in Apache. I am getting data from a WCF Restful service hosted in IIS with CORS enabled. I am able to get data with no problem. However, currently I am passing the user name and password each time on the url, and overriding the user validator on the WCF side, which is extremely bad and is not secure by any means. Don't worry this only a development environment. How do I set the header values with the user credentials prior to calling the SSL service? Basically how do I setup the web request in AngularJS prior to making the service call?
I ended up creating a custom header on my AngularJS app called "Authorization", setting the header value on the app, and then reading the header values on the WCF Service.
The way I was able to accomplish this is as follows:
First, I had to change the WCF service to allow me to get the Http Context. To do that I had to first enable ASP Net Compatibility on the web.config like so:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
I had to add a Global.asax to my WebService project and modify the BeginRequest event to allow my new custom header like so:
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Authorization");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "POST, PUT, DELETE");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
HttpContext.Current.Response.End();
}
}
I then allowed ASP Net Compatibility on the service class by adding mark up to the class like so:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
At this point I was able to get Http Context headers on the WCF Service. Below is how I retrieved the header value in the service code:
HttpContext.Current.Request.Headers["Authorization"]
At this point, the WCF service is able to consume my custom header. The last step is to set the header from the AngularJS service factory code like below:
app.factory('serviceFactory', ['$http', function ($http) {
var urlBase = 'https://domainName.com/MyService.svc/';
var dataFactory = {};
var success = {};
serviceFactory.Login = function (username, password) {
return $http({
url: urlBase + "WebServiceLoginMethodName",
type: 'GET',
dataType: 'json',
headers: { 'Authorization': "Basic " + username + ":" + password }
});
};
This does not fire the User Validator on the web service, but this does let me put the username and password in the Http Headers, which I will encrypt and decrypt on the app and service as a next step.
Recently I start implementing a token based security system with angularjs and spring mvc. The idea is the following:
1. Visit /user/authenticate to get a security token and save the token to local storage
2. For each request sent by the angularJS client, use an interceptor to inject a X-Auth-Token header to the request.
In my spring back-end I have implemented an AuthenticationTokenProcessingFilter and a CustomAuthenticationEntryPoint. The first for extracting the token from the header and check if it is valid and the second to return a 401 unauthorized status when a request is not authenticated.
Please find some details about my back end code
AuthenticationController.java
#RestController
#RequestMapping(value="user")
public class AuthenticationController {
#RequestMapping(value="authenticate", method = RequestMethod.POST)
public ResponseEntity<?> login(#RequestParam("email") String email,
#RequestParam("password") String password) {
//Check if user is valid and return token
}
}
SecurityConfig.java
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UsersRepository usersRepo;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {...}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(
new AuthenticationTokenProcessingFilter(usersRepo),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(this.corsFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable().exceptionHandling()
.and()
.httpBasic()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/user/authenticate").permitAll()
.antMatchers("/**").authenticated()
.anyRequest().authenticated();
}
#Bean
public CORSFilter corsFilter() {
return new CORSFilter();
}
}
CORSFilter.java
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", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, Origin, X-Auth-Token");
response.addHeader("Access-Control-Expose-Headers", "X-Auth-Token");
chain.doFilter(req, res);
}
}
Now I am using the following angularjs code in order to query the /user/authenticate endpoint which is not behind firewall
return $http.post(baseUrl + 'user/authenticate', 'email='+username+'&password='+password,
{
headers : {
'content-type' : 'application/x-www-form-urlencoded'
}
}
);
When I use the above code everything works. However If I remove the headers parameter from the request my angularjs client sends an OPTION request (rather than a POST request - I imagine this is related to my CORS filter) and my back end sends a 401 Unauthorised response.
Could you please give me a few more details why this is happening?
Thank you in advance!
I think I had a similar (the same?) problem and I tweaked my filter chain in WebSecurityConfigurerAdapter::configure(HttpSecurity http) a little bit to solve it.
I think what happens in your case is that the browser sends an OPTIONS request to figure out whether CORS is allowed or not by your server. The OPTIONS request is handled in your filter chain and will eventually match
.antMatchers("/**").authenticated()
Now you'll probably end up in your CustomAuthenticationEntryPoint() and return a 401.
You could add:
.antMatchers(HttpMethod.OPTIONS, "/user/authenticate/").permitAll()
However, I think you still would get problems afterwards. When the client now wants to access other resources the token has to be sent in the header. Most likely this will result in another OPTIONS request which does not contain your token and so you will eventually end up with the same problem.
So what I did was to explicitly allow all OPTIONS request without checking for authentication.
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
Now you bypass your security filter and you won't return a 401. I think that this does not affect the security of the application as long as you won't handle OPTIONS requests by yourself in a controller and allow someone to access critical data.
OPTIONS request is a so called preflight request:
To protect resources against cross-origin requests that could not
originate from certain user agents before this specification existed a
preflight request is made to ensure that the resource is aware of this
specification.
Basically, it means that an OPTIONS request is issued whenever the resource (backend server) didn't supply Origin and Access-Control-* in the response (to the client). You'll need CORS activated as soon as your backend and client (webapp) are located under different domains e.g. backend is available under domainA and your client is available under domainB. Even domainA:80 and domain:8080 are treated as different domains.