CSRF & CORS issue with angularjs and spring security - angularjs

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.

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);
}
}
}

How to make POST request to external API using Angular2?

I'm trying to leverage CORS to make a post request. I came across several articles/answers related to CORS but somehow just couldn't get it working.
As I understand, the access-control-allow-origin: * is to be set on the server side to get this working but what I have here is a angular-cli project.
My project is purely Angular 2.1 based and no backend server is involved. Any suggestions as to how to set it up properly will be highly appreciated.
The exact error that I'm getting is this:
"NetworkError: 404 Not Found - https://flowxo.com/hooks/a/rbpja7r2/?usertype=User"
and this warning in console:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the
remote resource at https://flowxo.com/hooks/a/rbpja7r2/?usertype=User.
(Reason: CORS header 'Access-Control-Allow-Origin' missing).
Update:
Here's how I'm trying to make a POST request:
let headers = new Headers();
headers.append('Access-Control-Allow-Headers', 'Content-Type');
headers.append('Content-Type', 'application/json');
headers.append('Access-Control-Allow-Methods', 'POST, OPTIONS');
headers.append('Access-Control-Allow-Origin', '*');
return this.http.post(
this.flowxoUrl,
JSON.stringify(formData),
{headers: headers}
)
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server error')); //...errors if any
This is server side issue, not Angular.
Your server should respond with Access-Control-Allow-Origin header.
Java (Spring) example:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
response.setHeader("Access-Control-Allow-Origin", "*"); //you can specify domains here * - is a wildcard, it will allow all origins to request
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, content-type");
if("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
#Override
public void destroy() {}
}

Spring-boot-oauth2-angularjs, error with CORS after session expiry and redirect to oauth2 server

I have a front-end application that uses the Spring-boot, Spring security oauth2 on server side and AngularJs on client side. I also use a third-party oauth2 server. My problem is that after the expiry of the application session, sping-security redirects all request to '/login' (and that's exactly how it should be) and I got 302 status code with location to redirect on auth-server page in response header. But the redirect does not happen because I get error:
XMLHttpRequest cannot load ****://bla-bla/oauth/authorize?client_id=...andSomeAuthStuff. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '****://myipadress:8084' is therefore not allowed access.
The question is why the first entry into an application or refresh a page or logout and new login does not involve such an error and all goes well but ONLY when I get a session timeout (For example I make ajax request from dead view), CORS error occurs :-(
I reproduce the steps:
On the "dead" page I make ajax request to my API (Spring-boot 1.3.3 WAR running on provided Tomcat 8)
Spring intercepts the request and responds:
General:
Request URL:***//myipadress:8084/appname/login
Request Method:GET
Status Code:302 Found
Remote Address:myipadress:8084
Response headers:
Access-Control-Allow-Headers:Authorization, Content-Type, Accept, x-requested-with, Cache-Control
Access-Control-Allow-Methods:POST, GET, OPTIONS, DELETE, PUT
Access-Control-Allow-Origin:*
Access-Control-Max-Age:3600
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Location:*//authserver/oauth/authorize?client_id=******&redirect_uri=*://myipadress:8084/appname/login&response_type=code&state=BIQ68y
Pragma:no-cache
Server:Apache-Coyote/1.1
Set-Cookie:JSESSIONID=05D39263EEF7EC9E24AEE8E1E6693748; Path=/appname/; HttpOnly
X-Content-Type-Options:nosniff
X-Frame-Options:DENY
X-XSS-Protection:1; mode=block
CORS Filter:
public class SimpleCORSFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
res.setHeader("Access-Control-Max-Age", "3600");
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, x-requested-with, Cache-Control");
chain.doFilter(request, res);
}
#Override
public void destroy() {
}
}
Security configuration:
#EnableOAuth2Sso
#Configuration
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.logout()
.and().antMatcher("/**")
.authorizeRequests()
.anyRequest().authenticated()
.and().csrf().disable()
.addFilterBefore(new SimpleCORSFilter(), ChannelProcessingFilter.class);
}
}
Log after ajax request:
2016-04-04 14:10:42.613 DEBUG 5428 --- [o-8084-exec-144] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/login'; against '/login'
2016-04-04 14:10:42.613 DEBUG 5428 --- [o-8084-exec-144] uth2ClientAuthenticationProcessingFilter : Request is to process authentication
2016-04-04 14:10:42.615 DEBUG 5428 --- [o-8084-exec-144] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2016-04-04 14:10:42.657 DEBUG 5428 --- [o-8084-exec-144] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2016-04-04 14:10:42.658 DEBUG 5428 --- [o-8084-exec-144] o.s.s.web.DefaultRedirectStrategy : Redirecting to '****://authserver/oauth/authorize?client_id=*****&redirect_uri=***://myipadress:8084/appname/login&response_type=code&state=iNdnBk'
I believe you need to add the #EnableWebSecurity annotation to your CustomWebSecurityConfigurerAdapter.
Also, I tried to go the other way and put the filter in such a way:
#Override
public void configure(HttpSecurity http) throws Exception {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
source.registerCorsConfiguration("/**", config);
CorsFilter filter = new CorsFilter(source);
http
.logout()
.and().antMatcher("/**")
.authorizeRequests()
.anyRequest().authenticated()
.and().csrf().disable()
.addFilterBefore(filter, ChannelProcessingFilter.class);
}
But it also not helped and CORS headers in response all dissapeared!
I think the reason is not in the headers, but in the status code 302 in response. How do I come to 401 instead of 302?

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).

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

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.

Resources