What does Cookie CsrfTokenRepository.withHttpOnlyFalse () do and when to use it? - java

I am trying to learn Spring Security right now and I have seen many different examples using this. I know what CSRF is and that Spring Security enables it by default.
The thing that I am curious about to know is this kind of customization.
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests(request -> {
request
.antMatchers("/login").permitAll()
.anyRequest()
....more code
What kind of customization does .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) this line and when it is appropriate to use it.
I would appreciate it if anyone can come with a simple explanation.

CSRF stands for Cross Site Request Forgery
It is one kind of token that is sent with the request to prevent the attacks. In order to use the Spring Security CSRF protection, we'll first need to make sure we use the proper HTTP methods for anything that modifies the state (PATCH, POST, PUT, and DELETE – not GET).
CSRF protection with Spring CookieCsrfTokenRepository works as follows:
The client makes a GET request to Server (Spring Boot Backend), e.g. request for the main page
Spring sends the response for GET request along with Set-cookie header which contains securely generated XSRF Token
The browser sets the cookie with XSRF Token
While sending a state-changing request (e.g. POST) the client (might be angular) copies the cookie value to the HTTP request header
The request is sent with both header and cookie (browser attaches the cookie automatically)
Spring compares the header and the cookie values, if they are the same the request is accepted, otherwise, 403 is returned to the client
The method withHttpOnlyFalse allows angular to read XSRF cookie. Make sure that Angular makes XHR request with withCreddentials flag set to true.
Code from CookieCsrfTokenRepository
#Override
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken(this.headerName, this.parameterName,
createNewToken());
}
#Override
public void saveToken(CsrfToken token, HttpServletRequest request,
HttpServletResponse response) {
String tokenValue = token == null ? "" : token.getToken();
Cookie cookie = new Cookie(this.cookieName, tokenValue);
cookie.setSecure(request.isSecure());
if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
cookie.setPath(this.cookiePath);
} else {
cookie.setPath(this.getRequestContext(request));
}
if (token == null) {
cookie.setMaxAge(0);
}
else {
cookie.setMaxAge(-1);
}
cookie.setHttpOnly(cookieHttpOnly);
if (this.cookieDomain != null && !this.cookieDomain.isEmpty()) {
cookie.setDomain(this.cookieDomain);
}
response.addCookie(cookie);
}
#Override
public CsrfToken loadToken(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, this.cookieName);
if (cookie == null) {
return null;
}
String token = cookie.getValue();
if (!StringUtils.hasLength(token)) {
return null;
}
return new DefaultCsrfToken(this.headerName, this.parameterName, token);
}
public static CookieCsrfTokenRepository withHttpOnlyFalse() {
CookieCsrfTokenRepository result = new CookieCsrfTokenRepository();
result.setCookieHttpOnly(false);
return result;
}
You may explore the methods here

You may find additional information about the httpOnly attribute of cookies here: https://www.cookiepro.com/knowledge/httponly-cookie/

Related

CSRF token is generated again, doesn't use the one it already has

We have a Java based application with the front-end in Vuejs.
The application has a CsrfRequestMatcher that bypasses GET reqests.
The problem we're facing is with SSO. It is our custom Login/SSO we are not using any third party APIs for it.
This is the use case that breaks our application:
The user successfully SSOs in.
The user can move around the application doing POST requests.
The user doesn't logout, but closes the tab.
The user SSOs in again.
Lands fine in the application, but as soon as they do a POST
request, the application says invalid CSRF token and throws them on the login page.
All this takes place in the org.springframework.security.web.csrf.CsrfFilter class.
Here's the code snippet:
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for "
+ UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
}
else {
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
Upon debugging I see that the code in CsrfFilter still has the csrfToken it generated the last time on the first call / SSO.
Then the user is redirected to a different page (Still a GET request), and the CsrfFilter is called again.
This time CsrfToken csrfToken = this.tokenRepository.loadToken(request); is null. This reads it from the session so don't understand why is that null?
So it generates a new CsrfToken, and the UI has an old CsrfToken obviously, so the next POST call generates the Invalid CSRF token error, because of token mismatch.
We are using an older version of Spring (1.4), so please take this in to account when you answer.
Any help is appreciated to resolve this CSRF token issue.

How to delete a cookie from backend upon an error

Currently setting up http only cookie in a Spring boot project via configurations as follows.
This cookie is getting set correctly when ever I call following endpoint.
#Bean
public CookieSerializer defaultCookieSerializer() {
DefaultCookieSerializer cookie = new DefaultCookieSerializer();
cookie.setDomainNamePattern(".*");
cookie.setCookieName("my_cookie");
cookie.setUseSecureCookie(true);
cookie.setUseHttpOnlyCookie(true);
cookie.setCookieMaxAge(1200);
return cookie;
}
As can see, the cookie called my_cookie is being set for 2 mins.
In my controller within same project, I have the following controller method.
In the event I enter the error block, I wish to delete the cookie called my_cookie. How can I do that?
This is the closest question I found for this but is not the same case considering I set it via configurations.
https://stackoverflow.com/questions/9821919/delete-cookie-from-a-servlet-response
#PostMapping(value = "/endpoint")
public List CustomResponse(
#RequestBody Request request,
) throws Exception {
CustomResponse response = null;
if (otherCookie != null) {
CustomResponse response = // perform some other rest request and get value from there
}
if (response == null) {
// I want to delete the cookie named `my_cookie` at this stage.
throw new CustomException('name');
}
return response;
}
To delete a cookie, set the Max-Age directive to 0 and unset its value. You must also pass the same other cookie properties you used to set it. Don't set the Max-Age directive value to -1. Otherwise, it will be treated as a session cookie by the browser.
// create a cookie
Cookie cookie = new Cookie("username", null);
cookie.setMaxAge(0);
cookie.setSecure(true);
cookie.setHttpOnly(true);
cookie.setPath("/");
//add cookie to response
response.addCookie(cookie);
For more, refer to the post by Dzone:
https://dzone.com/articles/how-to-use-cookies-in-spring-boot

Ask the server api before any request in Angular

I store my session in database, I have columns with user id, UUID and expiration date. Before any request from Angular I would like to send request to the method in api which check expiration date and let angular send another request if is valid or logout user and remove local storage with token with message about expiration date of my session. I'm looking for similar solution to HTTPInterceptor which add headers automatically to every request instead of add headers to any method with request before.
I'm using Angular 10 and Spring Boot 2.3.1.
EDIT.
I found the solution for catching errors in any request in my interceptor on the Angular side.
#Injectable()
export class HttpInterceptorService implements HttpInterceptor {
constructor() {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
if (localStorage.getItem('username') && localStorage.getItem('basicauth')) {
req = req.clone({
setHeaders: {
Authorization: localStorage.getItem('basicauth')
}
})
}
return next.handle(req).pipe(
catchError(response => {
console.log(response.status)
// do something, example clear LocalStorage...
return throwError(response);
})
)
}
}
EDIT 2.
I made Interceptor with preHandle() method in Spring-Boot to check session expiration date before request and if session is expired I set unique response status to 495 which tell Angular to logout the user and clear LocalStorage.
preHandle() method in Spring-Boot:
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//if (request.getRequestURL().toString().endsWith("/api/basicauth"))
String GUID = request.getHeader("GUID") == null? "1" : request.getHeader("GUID");
if (sessionService.getSessionByGuid(GUID).isPresent()) {
Session session = sessionService.getSessionByGuid(GUID).get();
if(session.getExpirationDate().isBefore(LocalDateTime.now())) {
sessionService.deleteSession(GUID);
response.setStatus(495);
return false;
} else {
sessionService.renewSession(session);
return true;
}
}
return true;
}
Interceptor method in Angular:
intercept(req: HttpRequest<any>, next: HttpHandler) {
if (localStorage.getItem('username') && localStorage.getItem('basicauth')) {
req = req.clone({
setHeaders: {
Authorization: localStorage.getItem('basicauth'),
GUID: localStorage.getItem('GUID')
}
})
}
return next.handle(req).pipe(
catchError(response => {
if (response.status == 495) {
this.auth.removeSessionAndStorage();
this.openSnackBar("Twoja sesja wygasłą!",);
this.router.navigate(['login', 'session-expired']);
}
return throwError(response);
})
);
}
I think your design is a little flawed. It really sounds like you are wanting to do JWT authentication but you're getting confused with how the token is validated before actually following through and executing your request.
In your interceptor, as long as you are adding Authorization: Bearer <token> with every request, you just need to create some auth middleware in your backend. This middleware would check the header of every request, grab the JWT and validate it. If it's valid, it then follows through and executes the request. If it fails, it returns an Unauthorized/Forbid result (up to you on what you want to return).
There's a couple of different ways to set this up, but here's a nice medium article that describes how to do it with Node.js. Some other frameworks, like .NET, make it a lot easier. So, you may want to evaluate the options before going with one.
Example code from article:
let jwt = require('jsonwebtoken');
const config = require('./config.js');
let checkToken = (req, res, next) => {
let token = req.headers['x-access-token'] || req.headers['authorization']; // Express headers are auto converted to lowercase
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length);
}
if (token) {
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.json({
success: false,
message: 'Token is not valid'
});
} else {
req.decoded = decoded;
next();
}
});
} else {
return res.json({
success: false,
message: 'Auth token is not supplied'
});
}
};
module.exports = {
checkToken: checkToken
}
So, essentially, the process you should be going for is made up of a couple of things:
The token is sent as a header with every single request
A middleware exists on your backend to validate the token before any API method execution
https://medium.com/dev-bits/a-guide-for-adding-jwt-token-based-authentication-to-your-single-page-nodejs-applications-c403f7cf04f4
If you stick with Spring Boot, it looks like there is some sample code on how to set this up here

Java JSP filter, set cookie if not exist

My question is: How can i set a cookie that does not already exist through a filter?
As i understand how filters work i can catch an incoming request before it reaches a given servlet, work on the request and pass it to the servlet. When the servlet than has generated the response i can catch the outgoing response and work with it again.
What i have done, is on the request determined that the specific cookie isin't present so i set a boolean value representing this. Than when the response returns from the servlet i add the specific cookie.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
ServletContext sc = filterConfig.getServletContext();
//Run when receiving the request from the client
boolean cookie = false;
System.out.println("Searching for the cookie (request)");//debug
Cookie[] cookies = httpRequest.getCookies();
if(cookies != null) {
for(int a = 0; a < cookies.length; a++) {
if(cookies[a].getName().equals("PositionCookie")) {
cookie = true;
}
}
}
chain.doFilter(httpRequest, httpResponse);
//Run when sending the response to the client
System.out.println("Determining to create cookie (response)");//Debug
if(!cookie) {
System.out.println("Creating 'PositionCookie' (response)");//debug
Cookie c = new Cookie("PositionCookie", "/test/data/string");
c.setPath("/");
c.setMaxAge(-1);
httpResponse.addCookie(c);
}
}
Since i just want this to be a session cookie i have given it a MaxAge of -1.
All the debug lines are activated and written to catalina.out so i know that new Cookie statement has been reached but a new cookie is not added to the browsers saved cookies. I dont have any browser settings that deny new cookies and i get the JSESSIONID cookie without problems.
Cookies are set in HTTP headers. The headers are the first thing that are written as part of the response. The Servlet container only buffers so much of the response before writing it (headers first) to the client. Once the headers have been written the response is considered to be committed. (The application can also force the commit of the response.) Once the response has been committed cookies cannot be added (since the HTTP headers have already been sent to the client).
I strongly suspect the response is committed by the time you try and add the cookie so the call is simply ignored.
The simple solution is to move the call to addCookie() to before the call to chain.doFilter(httpRequest, httpResponse);

Spring boot security oauth2 get access_token from cookie

I'm currently implementing the authentication between several Spring Boot applications. At the moment, the jwt access token is sent in the authorization header and it is picked up by the resource server. However I would like to use HttpOnly cookies to send the tokens and was wondering how do you configure Spring Boot to get the token from cookies instead of the headers.
I should mention that I'm using the spring-security-oauth2 and spring-security-jwt libraries.
Thank you!
Managed to get the token from the cookies by creating my custom TokenExtractor and passing that in configuration class (the one with #EnableResourceServer) like the following:
public void configure(ResourceServerSecurityConfigurer resources) {
resources.tokenExtractor(new CustomTokenExtractor());
}
The CustomExtractor from the accepted answer might look like this:
private class CustomExtractor implements TokenExtractor {
private static final String TOKEN_KEY_JWT = "token";
#Override
public Authentication extract(HttpServletRequest request) {
return new PreAuthenticatedAuthenticationToken(getTokenFromRequest(request), "");
}
private String getTokenFromRequest(HttpServletRequest request) {
final Cookie[] cookies = request.getCookies();
if (cookies == null) {
return null;
}
return Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(TOKEN_KEY_JWT))
.findFirst()
.map(Cookie::getValue).orElse(null);
}
}

Categories