I'm using an angular frontend, and a spring backend with spring security. I have secured my API with the following code
http
.cors().and()
// .csrf().disable()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.authorizeRequests()
// .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/xrtool/*").permitAll()
.antMatchers("/advanced_search").hasRole("USER")
.antMatchers("/creation").hasRole("USER")
.antMatchers("/edit").hasRole("USER")
.antMatchers("/basicAuth").permitAll()
.antMatchers("/**").permitAll()
.anyRequest().authenticated().and()
.httpBasic();
I have no problems reaching the APIs with no protection (permitAll) but those which require a certain role give me a Status 401 unauthorized. I believe that is due to my client not automatically setting the cookies after the initial authentication request.
The code for my authorization request:
authenticate(username : string, password : string){
let authHeader = new HttpHeaders({
authorization : 'Basic ' + btoa(username + ':' + password),
withCredentials: 'true'
});
return this.http.get(`${API_URL}/basicAuth`, {headers: authHeader})
}
My backend code:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
@Override
protected void configure(HttpSecurity http) {
try {
http
.cors().and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.authorizeRequests()
.antMatchers("/xrtool/*").permitAll()
.antMatchers("/advanced_search").hasRole("USER")
.antMatchers("/creation").hasRole("USER")
.antMatchers("/edit").hasRole("USER")
.antMatchers("/basicAuth").permitAll()
.antMatchers("/**").permitAll()
.anyRequest().authenticated().and()
.httpBasic();
} catch (Exception e){
System.out.println(e);
}
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
The response header for my initial request:
HTTP/1.1 200
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Allow-Credentials: true
Set-Cookie: XSRF-TOKEN=475b11b6-39a4-4a46-a58d-e096a3bc07c6; Path=/
Set-Cookie: JSESSIONID=3E8382E29CB7C0AD9BBC168EA08ACF30; Path=/; HttpOnly
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 21 Jan 2023 17:56:43 GMT
Keep-Alive: timeout=60
Connection: keep-alive
And the subsequent request headers:
GET /xrtool/Test HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/109.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
withCredentials: true
Origin: http://localhost:4200
DNT: 1
Connection: keep-alive
Referer: http://localhost:4200/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
The storage of the webclient also doesn't contain the Cookies.
I have tried it with different browsers (Chrome, Firefox, Edge), but the result is consistent.
I tried accessing the APIs directly (localhost:8080/api/request) and everything is fine. I can also see the cookies in the webclient storage.
Postman also works fine. I can see the cookies being saved in the header, and after a initial authorization request, subsequent request are authorized.
I tried adding the authorization header in every request through an interceptor, and i was able to reach the APIs.
EDIT:
Thanks to Tr1Monsters' observation i was able to find the issue. The withCredentials header was at the wrong place. Appearently it doesn't need to be in the initial authentification request, but every subsequent request. I have added it to my Http-Intercept like this:
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let username = "b"
let password = "b"
let basicAuthHeaderString = "Basic " + window.btoa(username + ':' + password)
request = request.clone({
setHeaders: {
// Authorization: basicAuthHeaderString
}
, withCredentials: true
})
return next.handle(request);
}
I have left in the original way i've added the header to show that the withCredentials header is placed outside the setHeaders part.
Thanks to Tr1Monsters' observation i was able to find the issue. The withCredentials header was at the wrong place. Appearently it doesn't need to be in the initial authentification request, but every subsequent request. I have added it to my Http-Interceptor like this:
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let username = "b"
let password = "b"
let basicAuthHeaderString = "Basic " + window.btoa(username + ':' + password)
request = request.clone({
setHeaders: {
// Authorization: basicAuthHeaderString
}
, withCredentials: true
})
return next.handle(request);
}
I have left in the original way I've added the header, to show that the withCredentials header is placed outside the setHeaders part.