CSRF on GAE Endpoints with oAuth - google-app-engine

I'm looking to implement protections against CSRF attacks in my API, which I developed using GAE Endpoints with oAuth2 required for all methods.
Before implementing any specific protection I'm trying to actually break my app (CSRF looked simple at first glance). But just can't make it work.
When I reference my endpoint in another page, the browser adds the cookie information but not the Authorization header with the bearer access token. This does not seem to be enough, because my endpoints automatically return 401 with a www-authenticate:Bearer realm="https://accounts.google.com/" header.
As I said, I have no specific protection against CSRF. But does using Google Cloud Endpoints with oAuth2 under HTTPS grants me protection against this type of attack "for free"?
--edit to address comment
I tried a simple CSRF attack. I got a page up with an <img src="https://bla-bla-bla-appspot.com/_ah/api/myapi/v1/resource.getMethod">. Then I accessed this page while I had my app opened in another tab, so my browser would send my authentication information. And it does send the cookie, but not my oAuth token).
I didn't even tried doing a POST, if I "hack" a GET it would be great already.

OAUth 2.0 explicitly protects against CSRF via the use of a non-guessable state parameter which is generated by the client and validated by the server. Even if an attacker was able to trick a client into visiting a URL to authorize a malicious token, the state parameter would not match that of the client and the request would be denied.
The Google Cloud Endpoints libraries handle this bit of the OAuth spec for you, so you're in the clear.
Oauth2 requires all requests to have the bearer access token either as an HTTP header (use XMLhttpRequest from javascript to set the header and make the request) or as a URL query parameter (access_token). An attacker won't know this secret value, so would not be able to create a URL which would pass validation.

Here is (I hope) helpful java snippet from my Kuoll remote debugger for web apps.
package com.kuoll.server.filters;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CrossOriginFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.addHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
resp.setHeader("Access-Control-Allow-Headers", "origin, content-type, accept");
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
}
Replace * in resp.addHeader("Access-Control-Allow-Origin", "*"); to your Origin (if needed).
web.xml
<filter-mapping>
<filter-name>CrossOriginFilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>

Related

AngularJS SpringBoot no authentication csrf protection

My system uses AngularJS 1.6 and Spring Boot2.0. My front end is just a simple form that customers can use to buy tickets for an event. When the page loads it will GET the details of the current active event, then the users can fill the form to be POSTed. No login/registration so it's just like a google form. I've built them as separate projects so they will be running in different ports. Now I want to enable csrf protection but I can't seem to make it work.
I tried to follow this tutorial but without the authentication part: https://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii. When I did this I encountered a "CORS header ‘Access-Control-Allow-Origin’ missing" on the GET event details part so I added a CORS filter:
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("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", "Content-Type");
chain.doFilter(request, response);
After adding that, my GET works but I encounter the same CORS error when I POST the customer's details. I can already see the XSRF-TOKEN in the cookie when I use postman but somehow my backend blocks the incoming POST. From my understanding, angular automatically uses a received XSRF token so I didn't modify anything in the frontend when implementing spring security.
I've also tried this one: https://sdqali.in/blog/2016/07/20/csrf-protection-with-spring-security-and-angular-js/. And the exact same result, if I just follow the tutorial, CORS error on GET then when I add simple CORS filter, CORS error on POST. My filters seems to get mixed up on run time.
I've tried playing around the codes in these tutorials along with some answers to related questions here in stack but all of them don't have to deal with the CORS problem.
To begin, you should check my comment on how to enable CORS with spring-boot and spring-security.
As for CSRF protection, spring-boot 2 with spring-security enable that directly. Yet, to use it with AngularJS, you need to follow this guide:
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#csrf-cookie
We are also working on a project with a frontend AngularJS 1.6 and a backend spring-boot 2, but, as we didn't deploy our frontend and our backend on the same url, we got into a problem.
Example:
https://project.company.com/frontend
https://project.company.com/backend
The cookie generated by the server was set on the domain https://project.company.com with context /backend, which was unreadable from our frontend application in AngularJS.
To fix that, we forced our server to send a cookie with a context / so that it could be read from both applications.
#Configuration
#EnableWebSecurity
public class LdapAuthenticationSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
final CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
csrfTokenRepository.setCookiePath("/"); // Make sure that the cookie can be read from the backend and the frontend on the same domain.
http.csrf().csrfTokenRepository(csrfTokenRepository);
http.cors();
}
}
In the case where your applications are deployed on different domains, but with the same base like this:
https://frontend.company.com
https://backend.company.com
I would suggest to force the cookie send by the server with a domain like .company.com. But to do so, you will need to modify the way spring-security manage the cookie creation.

Windows Authentication is not working with WebAPI when called through Angular

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

clear cookies while logout in spring security or in angular

My trial code to remove cookies
.logout()
.logoutUrl("/access/logout")
.logoutSuccessHandler(new LogoutSuccessHandler() {
#Override
public void onLogoutSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication a) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
})
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
This code is of logout security with HttpSecurity.
Here I am getting issue to delete cookies
in logoutSuccessHandler in logger it shows cookies = 1 and i.e JESSESIONID
So my problem is how to delete cookie at the time of logout
or if is there any another way or any way in angular then please tell me
After going through many blogs on spring security I got answer,
.invalidateHttpSession(true) :- it is used to clear our session
and
.deleteCookies("JSESSIONID") :- it is used to delete cookies
use this after your success handler so that it will remove all cookies after logout
EDIT
And if any one want to handle cookies from frontend side then you can handle it using ng-cookies in AngularJs.

401 Unauthorised due to CORS when sending request to get user token

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.

How to configure Spring Security to send 'X-CSRF-TOKEN'?

The problem is to get the CSRF tokens working between Spring Security and Angular.
Spring Security CSRF Token Interceptor for Angular seems like something that should do the job, but there is no 'X-CSRF-TOKEN' in the HEAD response from the server.
My current tiny implementation is available in GitHub (Tag v.1.0) and I would appreciate a lot if somebody who knows the topic would have a quick look on the code, the problem should be easy to spot.
Based on the documentation, I am under the impression that CSRF should have been enabled automatically, but that seems not to be the case.
I am using Spring Boot and prefer the annotation-based configuration over XML, if something needs to be configured differently.
Any other approaches to make Spring Security work against Angular?
Angular looks for a cookie called "XSRF-TOKEN" I believe, so the easiest thing to do for the client is to send that. You can do it in a Filter for instance (example from https://github.com/spring-guides/tut-spring-security-and-angular-js/blob/master/single/src/main/java/demo/UiApplication.java#L65):
private Filter csrfHeaderFilter() {
return new 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 = new Cookie("XSRF-TOKEN", csrf.getToken());
cookie.setPath("/");
response.addCookie(cookie);
}
filterChain.doFilter(request, response);
}
};
}
Update: since spring security 4.2 the correct cookie name for angular is used by default if you use the cookie csrf repository(the link is still the best source), i.e. there is no longer any need for a custom filter. Example:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
...
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
I am answering the question myself as there was a hidden one in the original GitHub repository: Issue #1.
The solution was to add a couple of lines of Java code that adds the CSRF parameters as Http message headers.
I added a working solution to the GitHub repo with Tag v.2.0.

Resources