AngularJS: X-Auth-Token not sent (error 401) - angularjs

I'm here because I tried everything and I don't understand what I'm doing wrong.
As a backend, I use Spring MVC, Rest and Spring Security. I'm using a custom authentication's system based on tokens (entirely stateless). I've also a CORS's filter, because my front app is not running on the same server.
So here's what I did in angularJS, in order to get authenticate when I want to access a protected resource:
I've created an interceptor that change each request to add the token found from a cookie
I add a custom header on the request
Here's what my interceptor do:
request: function(config) {
var token = $cookieStore.get("token");
if (token && token.key) {
config.headers["X-Auth-Token"]=token.key;
}
console.log(config.headers);
return config || $q.when(config);
}
But when I try to access a protected resource, even if the token is still valid, it never puts the X-Auth-Token headers on the request! It tried to send an option's request and failed with a 401 error!
But if I do the same with a url parameter (like token=....), the interceptor works as expected...(I've implemented both system : by parameter and by header)
What I don't understand is why the header is not put as expected?? and why it works perfectly with something like POSTMAN for instance?
Please help, I'm losing my hairs with this...

It's probably cleaner to do this instead of your accepted answer.
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(request, response);
}

I fixed the problem by changing my filter like this:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
LOG.info("goes on the filter");
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Accept, Content-Type, Origin, Authorization, X-Auth-Token");
response.addHeader("Access-Control-Expose-Headers", "X-Auth-Token");
if (request.getMethod().equals("OPTIONS")) {
try {
response.getWriter().print("OK");
response.getWriter().flush();
} catch (IOException e) {
LOG.error(e.getMessage());
}
} else {
chain.doFilter(request, response);
}
}

Not sure if this will solve your issue, but $cookieStore can only get/set strings, so the following:
var token = $cookieStore.get("token");
if (token && token.key) {
Will never evaluate to true.

Related

Authorization header not visible in react app though its marked allowed in exposed header in spring server

I am using spring-boot-starter-parent with version 2.3.4.RELEASE and spring-security.
I have defined cors configuration as below in my spring boot app. I am able to make calls to REST API from react app running on my local without cors issue.
As part of login am hitting /user API with user credentials. Server sends user details as response and I am generating JWT in a filter only for /user API and sending JWT in Authorization header.
I have tested with postman and curl I can see the Authorization header but my react app though it can authenticate it can't see or access Authorization header or the JWT in the response of /user API.
In cors configuration I have marked Authorization header as exposed config.setExposedHeaders(Arrays.asList("Authorization")); not sure why its not getting reflected.
complete code is as below.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(new CorsConfigurationSource() {
#Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
config.setExposedHeaders(Arrays.asList("Authorization"));
config.setMaxAge(3600L);
return config;
}
}).and()
.csrf().disable()
.addFilterBefore(new JWTTokenValidatorFilter(), BasicAuthenticationFilter.class) //JWT validation before basic authentication
.addFilterAfter(new JWTTokenGeneratorFilter(), BasicAuthenticationFilter.class) //JWT validation after basic authentication
.authorizeRequests()
.anyRequest().authenticated()//authorize all requests
.and()
.httpBasic();
}
I tried explicitly defining a cors filter but that didnt work either
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers", "Authorization, x-requested-with, x-auth-token");
response.addHeader("Access-Control-Expose-Headers","Authorization");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true");
if (!(request.getMethod().equalsIgnoreCase("OPTIONS"))) {
try {
chain.doFilter(req, res);
} catch (Exception ex) {
ex.printStackTrace();
}
} else {
System.out.println("Pre-flight");
response.setHeader("Access-Control-Allowed-Methods", "POST, GET, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Authorization, content-type,x-auth-token, " +
"access-control-request-headers, access-control-request-method, accept, origin, authorization, x-requested-with");
response.addHeader("Access-Control-Expose-Headers","Authorization");
response.setStatus(HttpServletResponse.SC_OK);
}
}
}

HTTP POST turns into OPTIONS AngularJS

I'm trying to submit a login form to a rest Api, translating my Jquery/Javascript code into AngularJS. I try to use $http service for send the request, but when I submit the form, the POST request turns into OPTIONS and no request params are passed to. This is my code:
controller.formData = {
username : $scope.formData.username,
password : $scope.formData.password,
};
$http({
method : 'POST',
url : 'http://localhost:8080/multe-web/signin',
data : controller.formData,
headers : { 'Content-Type': 'application/json' }
})
.success(function(data) {
console.log(data);
});
This is a screenshoot of browser's console
Why no parameters are passed to the HTTP POST request?
Can someone help me?
If you are trying to do a Cross Origin Request, this could be a 'preflight request':
See this post: How to disable OPTIONS request?
This request is called "preflight request". This appends when you try to access a resource that is from another domain. For more information you can search for Cross-origin resource sharing (CORS). During the preflight request, you should see headers starting with Access-Control-Request-* These request headers are asking the server for permissions to make the actual request. Your preflight response needs to acknowledge these headers in order for the actual request to work.
The OPTIONS that you see is a pre-flighted request that is initiated by the browser. This happens due to CORS that stands for "CROSS ORIGIN RESOURCE SHARING`.
Pre-flighted requests
In your case, your backend server needs to acknowledge the OPTIONS request and send a 200 back. Only then the browser fires the real POST request
Here are some links to get you started
Same Origin policy
AngularJS and CORS
Similar question on SO
You must define a CORS filter on your backend.
I don't know which language you are using but an example in spring framework (java) would be the following.
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
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", "x-requested-with, authorization, cache-control, content-type, Origin, key");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
Basically you are stating that response.setHeader("Access-Control-Allow-Origin", "*"); can come from any domain. ( in a production environment you would limit this offcourse to your known domains ).
When implementing this filter your options request will pass through without problems.
The options call is something the browser does automatically and you really don't want to disable this as some other answers are suggesting.
Kind regards

CSRF & CORS issue with angularjs and spring security

Struggling for quite some time. I am having Angular.js at front with Java/J2EE (RESTFul) at the backend with Spring security (Enabled Cors-filter). Spring security works perfectly fine except when I try to enable csrf protection. Below is my code I tried for csrf:
My CSRF filter
public class CsrfHeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
Security configuration has below code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.formLogin()
.successHandler(authSuccess)
.failureHandler(authFailure)
.and()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.and()
.addFilterBefore(corsFilter, HeaderWriterFilter.class)
.addFilterAfter(csrfHeaderFilter, CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
//.csrf().disable();
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
Had to modify CORS filter to add X-XSRF-TOKEN header name inside Access-Control-Allow-Headers as initially it was giving this error.
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, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "X-XSRF-TOKEN, x-requested-with, Content-Type");
chain.doFilter(req, res);
}
My angular js code:
$scope.login = function () {
$http.post("http://localhost:9090/cynosureserver/login", "username=" + $scope.user.name +
"&password=" + $scope.user.password, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-XSRF-TOKEN': $cookies.get('XSRF-TOKEN')
}
}).then(function (data) {
alert("login successful" + JSON.stringify(data));
}, function (err) {
alert("error logging in" + JSON.stringify(err));
});
};
I have debug points set up. After login form it goes to Csrf filter. But never hit my "CustomUserDetailsService". The problem is I always get 403 response saying:
HTTP Status 403 - Expected CSRF token not found. Has your session expired?
If I make a simple Get request it is successful and also "XSRF-TOKEN" cookie is received inside Response Header.
But for my post method I am unable to proceed.
I am seeing below:
Status Code:403 Forbidden
Remote Address:[::1]:9090
Response Headers
Access-Control-Allow-Headers:X-XSRF-TOKEN, x-requested-with, Content-Type
Access-Control-Allow-Methods:POST, GET, PUT, OPTIONS, DELETE
Access-Control-Allow-Origin:*
....
Set-Cookie:JSESSIONID=5DE6DB8B5E31DC77DC4ED28600445615; Path=/cynosureserver/; HttpOnly
X-Content-Type-Options:nosniff
X-Frame-Options:DENY
X-XSS-Protection:1; mode=block
Request Headers
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate
...
Content-Type:application/x-www-form-urlencoded
Host:localhost:9090
Origin:http://localhost:9000
Referer:http://localhost:9000/
X-XSRF-TOKEN:7c2057b5-0f1a-4dc1-bab4-c8f000a2ae2d
Form Data
username:manisha
password:123456
I tried many different combinations but nothing seems to work, Please help me - what am I missing ? I am totally clueless here. Sorry for lengthy post.

How to debug "No 'Access-Control-Allow-Origin' header is present on the requested resource"

I am getting this error shown on browser console:
XMLHttpRequest cannot load http://localhost:8080/api/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9009' is therefore not allowed access.
The environment I am using are:
Backend- Spring Boot
Front End- Angularjs
Web Server- Grunt
On server, I am already defining headers in request and response as:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Origin", "*");
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization, Content-Type");
if (httpRequest.getMethod().equals("OPTIONS")) {
httpResponse.setStatus(HttpServletResponse.SC_ACCEPTED);
return;
}
}
I already found this question on this link No 'Access-Control-Allow-Origin' header is present on the requested resource but couldn't find the appropriate solution.
Here is the browser network image:
Your server should add the proper CORS headers for preflight/actual CORS requests. I wouldn't recommend implementing your own Filter, since this is a rather complex specification.
As of 4.2, Spring Framework supports CORS with a global configuration or with a Filter - see this blog post for more information.
If you're using Spring Boot, version 1.3.0 (to be released soon) will integrate this support.
You need to enable CORS(Cross Origin Resource Sharing) on your web server. Please refer to this resource.
You need to set your response header to:
Access-Control-Allow-Origin: *
This link has all the info needed to set CORS
Enabling CORS for all domain is not a good practice, so you should restrict it when everything is up and running.
Another workaround is to have a separate API for curl request
Call a URL in your own API and access the data using your server side with cURL or something. I have a current working project with same situation (Laravel + AngularJs).
My sample code for the remote auth check with cURL request.
Code is in Laravel-PHP format, I hope you can convert to the language you are working on.
Requester function:
public function curlRequester($url,$fields)
{
// Open connection
$ch = curl_init();
// Set the url, number of POST vars, POST data
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
// Execute post
$result = curl_exec($ch);
// Close connection
curl_close($ch);
$result_json_decoded = json_decode($result, true);
return $result_json_decoded;
}
Controller function
public function login()
{
// set POST params
$fields = array(
'username' => Input::get('username'),
'password' => Input::get('password')
);
$url = 'http://www.this-is-api-url.com/login';
$result = $this->curl->curlRequester($url,$fields);
return response()->json($result);
}
Angular request function
$scope.authCheck = function(){
$http({
url:'http://www.our-project-url/login',
method:"post",
data:{"username": "rameez", "password":"rameezrami"}
})
.success(function(response) {
if(response.status==1){
$location.path("/homepage");
}else if(response.status==0){
$scope.login_error_message_box = true;
$scope.login_error_message_text =response.message;
}
});
}
Your Filter is right, You should include your filter as a component and put it where it can be scanned by spring
#Component
public class CORSFilter implements Filter{
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
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", "x-requested-with");
chain.doFilter(req, res);
}
#Override
public void destroy() {
}
}
You need to also make an update in your angular code to allow CORS via AJAX.
myApp.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
]);
If the above doesn't work, you may need to also add the following:
myApp.all('/*', function (request, response, next) {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Headers", "X-Requested-With");
response.header("Access-Control-Allow-Methods", "GET, POST", "PUT", "DELETE");
next();
});
As noted by #Marged you want to be cautious about the "*" and I would highly recommend replacing that with your domain(s).

CORS issue angularjs and spring security

I have an application that will be deployed at two places. There would be one master and one slave. I have a requirement that some of the requests should be satisfied by slave and others by master.
For this, I am sending login request to master on successful login to slave. I am able to achieve this.
My problem is, when i try to access any resource from master(request a resource from master originating from slave) after logging in, It is giving unauthorized(401) error.
Login controller:
$http.post('j_spring_security_check', payload, config).success(function(data, textStatus, jqXHR, dataType) {
if ($scope.rememberme) {
var user = {
username: $scope.username,
password: $rootScope.encryptPass($scope.password, true),
rememberme: $scope.rememberme
};
localStorage.setItem('user', JSON.stringify(user));
} else {
localStorage.removeItem('user');
}
$rootScope.pingServer();
$rootScope.retrieveNotificationCount();
$http.post('http://localhost:9090/context/j_spring_security_check',payload, config).success(function(data, textStatus, jqXHR, dataType) {
$rootScope.pingServer();
}).error(function(data, status, headers, config) {
});
});
app.js
rootapp.config(['$httpProvider', function(httpProvider) {
httpProvider.interceptors.push(['$rootScope', function($rootScope) {
return {
request: function(config) {
config.headers = config.headers||{};
config.headers['withCredentials'] = true;
config.headers['useXDomain'] = true;
return config;
}
}
}]);
My server has a filter
#Singleton
public class ResponseCorsFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletResponse instanceof HttpServletResponse) {
if (request.getMethod().equals("OPTION") || request.getMethod().equals("OPTIONS")) {
System.out.println("In Options");
if (request.getServletPath().equals("/j_spring_security_check")) {
System.out.println("request path match");
alteredResponse.setStatus(HttpServletResponse.SC_OK);
addHeadersFor200Response(alteredResponse);
return;
}
}
addHeadersFor200Response(alteredResponse);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private void addHeadersFor200Response(HttpServletResponse response) {
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Credentials", "true");
response.addHeader("Access-Control-Allow-Methods", "ACL, CANCELUPLOAD, CHECKIN, CHECKOUT, COPY, DELETE, GET, HEAD, LOCK, MKCALENDAR, MKCOL, MOVE, OPTIONS, POST, PROPFIND, PROPPATCH, PUT, REPORT, SEARCH, UNCHECKOUT, UNLOCK, UPDATE, VERSION-CONTROL");
response.addHeader("Access-Control-Allow-Headers", "useXDomain, withCredentials, Overwrite, Destination, Content-Type, Depth, User-Agent, Translate, Range, Content-Range, Timeout, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control, Location, Lock-Token, If");
response.addHeader("Access-Control-Expose-Headers", "DAV, content-length, Allow");
response.addHeader("Access-Control-Max-Age", "86400");
}
}
Am I missing anything?
You are incorrectly setting withCredentials as a request header. This is XHR option and should be present directly on the config:
config.withCredentials = true;
This option will enable cookies for your requests. Your requests will be able to include JSESSIONID, thus allowing HTTP session where Spring Security stores information about the authentication.
And as discussed in the chat (see question comments) - be aware that if the two servers are running on the same domain, they will share cookies. That might cause issues (sending incorrect JSESSIONID) so you need to access those servers under different host name.

Resources