JWT - No authorization is added in the header in browser - java

I am using JWT and Spring security for developing a Forum Application. I am getting 403 error when accessing users' endpoints. It happened after the merge, previously everything working properly. The endpoint works properly from POSTMAN but the issue occurs when accessing from browser
Nothing in the code has been mixed up, now the Authorization header is not added to the request, but only in the endpoints for users, in other cases, it works. The bare token is stored at the local storage of the browser. What could be the reason for something like that?
Angular interceptor adding authorization header:
intercept(request: HttpRequest<any>, next: HttpHandler) {
const authHeader = AUTHORIZATION_HEADER;
const accessToken = this.authService.getAuthorization();
if (accessToken !== null) {
request = request.clone({
headers: request.headers.set(authHeader, accessToken),
withCredentials: false
});
}
return next.handle(request);
}
}
Angular Auth Service
login(userCredentials: UserCredentials): Observable<any> {
return this.http
.post<AccountInfo>(`${API_URL}/login`, userCredentials, { observe: 'response' })
.pipe(
tap((response: HttpResponse<AccountInfo>) => {
const token = response.headers.get(AUTHORIZATION_HEADER);
this.storeAuthorization(token);
const body = response.body;
this.storeAccountInfo(body);
})
);
}
getAuthorization(): string {
return localStorage.getItem(AUTHORIZATION_KEY);
}
private storeAuthorization(authToken: string) {
localStorage.setItem(AUTHORIZATION_KEY, authToken);
}
private storeAccountInfo(accountInfo: AccountInfo) {
localStorage.setItem(USERNAME_KEY, accountInfo.username);
localStorage.setItem(ROLE_KEY, accountInfo.role.toString());
}
Here is the git repo containing the source code
https://github.com/PatrykKleczkowski/Forum/tree/feature/improvments

Related

Invalid CSRF token found - Spring Boot and Axios

I would like to post using Axios to my Spring Boot server. If I disable csrf using .csrf().disable() it works correctly however it fails when enabled.
I've tried adding X-CSRF-TOKEN to the header, or _csrf in the body but it is saying it is invalid. Checking the request the csrf is being passed in as expected.
CSRF Controller
#RequestMapping("/csrf")
public String csrf(final CsrfToken token) {
return token.getToken();
}
Spring Security
httpSecurity
.antMatcher("/api/**")
.cors().and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
Axios
axios
.post(
`/api/doSomething`,
{ id: id, _csrf: csrf},
{
withCredentials: true,
headers: {
'X-CSRF-TOKEN': csrf
}
}
)
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error);
});
MetaTags react-meta-tags store the CSRF
<MetaTags>
<meta name="csrf-token" content={csrfToken} />
</MetaTags>
Axios to get the CSRF token
axios
.get('/csrf', {
withCredentials: true
})
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error);
});
Function to get the CSRF token from the meta tags
function getCSRFFromPage(): string | null {
const element = document.querySelector("meta[name='csrf-token']");
if (element !== null) {
const csrf: string | null = element.getAttribute('content');
return csrf;
} else {
return null;
}
}
Error
Invalid CSRF token found for <url>
What could be causing this to fail?
There are two possible causes.
First of all, the CSRF token endpoint should match the Spring Security configuration. In your example, you're using antMatcher("/api/**"), but CSRF token endpoint is /csrf. This should likely become /api/csrf.
The second part is that the CSRF token changes after each request. You didn't show the code where you invoke getCSRFFromPage(), but this should happen before each call you make. If not, then only the first call with that CSRF token will succeed. All consecutive calls will fail.
If you don't like making an additional call with each request, then you can use the CookieCsrfTokenRepository:
httpSecurity
// ...
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
By doing so, each time you make a request, you'll get an XSRF-TOKEN cookie containing the next CSRF token. This should be passed within the X-CSRF-TOKEN or X-XSRF-TOKEN header. Many libraries, including Axios do this out of the box, so you don't have to do anything else.
You can customize this behavior by configuring the xsrfHeaderName or xsrfCookieName properties (see Request Config).

POST an appointment/reservation - CORS Policy problems

I try to post a dictionary with data for a reservation. But chrome logs this error:Access to XMLHttpRequest at 'http://localhost:8080/reservations' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This is strange since I can post images, videos, html content because I put a #CrossOrigin annotation above my controllers. But with this particular post request it doesn’t seem to work.
rest controller:
#CrossOrigin(origins="http://localhost:4200",
maxAge=2000,allowedHeaders="header1,header2",
exposedHeaders="header1",allowCredentials= "false")
#RestController
public class ReservationsController {
private ReservationDao dao;
#Autowired
public ReservationsController(ReservationDao dao) {
this.dao = dao;
}
#PostMapping("/reservations")
public Map<String, String> bookReservation(#RequestBody Map<String, String> reservation) {
System.out.println(reservation);
return null;
}
}
angular api bookReservation method:
bookReservation(data) {
console.log(data);
const result = this.http.post(this.apiUrl + 'reservations', data).subscribe(
(val) => {
console.log('POST call succesful value returned in body',
val);
},
response => {
console.log('POST call in error', response);
},
() => {
console.log('The POST observable is now completed');
});
console.log(result);
}
If you set allowedHeaders only you will allow this params and if it receive other params it never send cross origing headers and chrome will throw error.
You should remove allowedHeaders, exposedHeaders and allowCredentials if you don't need them.

No 'Access-Control-Allow-Origin' header present in request

I have read a lot of topic about this, and it still doesn't work, i'm loosing my mind.
I have an Angular6 App, with a Spring Api and i keep having this error when i call it :
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
And yet i feel i did everything :
Here is my call to the api in Angular App:
getGamesFromServer() {
const httpOptions = {
headers: new HttpHeaders({
'Access-Control-Allow-Origin':'*'
})
};
this.httpClient
.get<any[]>('http://localhost:8080/api/rest/v1/game/progress', httpOptions)
.subscribe(
(response) => {
console.log(response);
this.games = response;
console.log(this.games);
this.emitGameSubject();
},
(error) => {
console.log('Erreur ! : ' + error);
}
);
}
Here is the java method called :
#RequestMapping(method = GET, value = "/progress")
#ResponseBody
public Response findAllAndProgress() {
return Response.status(Response.Status.OK)
.entity(gameService.findAllAndProgress())
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
.build();
}
I have also started chrome with --disable-web-security option activated, and i also downloaded a chrome extension to allow CORS request but it was no use
I'm assuming it's because the Angular app is running in dev mode and is being served from a different port than the Spring webapp. You can either solve this with a proxy.conf.json file that you specify to the app startup (ng serve --proxy-config proxy.conf.json) or as a quick and dirty (but less production ready) solution you can add #CrossOrigin(origins = "*") to your Spring controllers.
The reason that the following code doesn't work:
#RequestMapping(method = GET, value = "/progress")
#ResponseBody
public Response findAllAndProgress() {
return Response.status(Response.Status.OK)
.entity(gameService.findAllAndProgress())
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
.build();
}
is because that method never actually gets executed to add the header. Cross-origin requests initiate a preflight request (HTTP OPTION) before issuing the actual request (HTTP GET in your case). It is that preflight request that is failing. This is why adding the annotation to the controller will address the issue.
Create proxy.conf.json file in root directory with next content:
{
"/api/*": {
"target": "http://localhost:8080",
"secure": false
}
}
And in package.json file you must change start script from ng serve to ng serve --proxy-config proxy.conf.json
More details about proxy-config: Link
On java add following this line on your api
#CrossOrigin(origins = "http://localhost:4200")
You can also proxy your request from Angular side. Angular provide you inbuilt server that can proxy request. Following following steps:
Add proxy.config.json file on root directory of your project.
Add following code in proxy.config.json file
{
"/api/": {
"target": {
"host": "localhost",
"protocol": "http:",
"port": 8080
},
"secure": false,
"changeOrigin": true,
"logLevel": "debug"
}
}
In package.json find start under scripts and replace with following:
ng serve --proxy-config proxy.config.json
Edit your angular code with following code:
this.httpClient
.get('/api/rest/v1/game/progress', httpOptions)
.subscribe(
(response) => {
console.log(response);
this.games = response;
console.log(this.games);
this.emitGameSubject();
},
(error) => {
console.log('Erreur ! : ' + error);
}
);

Request works in postman not IONIC

I'm developing an ionic app with a back-end with Java spring Boot with Spring security on it. When I tried to get a token with postman everything went well but in ionic, I got a 401 HTTP code. I have no cors problem, I have disabled it in my browser.
Here is the code from Ionic:
login() {
return new Promise((resolve, reject) => {
var credentials = "grant_type=password" + "&username=admin" + "&password=admin";
let headers = new HttpHeaders();
headers.append('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8');
headers.append('Authorization', 'Basic c2NhbnRhYm91ZmZlOlhZN2ttem9OemwxMDA=');
console.log(headers.toString);
this.http.post('http://localhost:8443/oauth/token', credentials, {
headers: headers
})
.subscribe(res => {
console.log(res);
let data = JSON.parse("" + res.toString); // res.json();
this.token = data.token;
this.storage.set('token', data.token);
resolve(data);
resolve(JSON.parse("" + res.toString) /* res.json() */ );
}, (err) => {
reject(err);
});
});
}
Thx for yr help I'm stuck on it for more than 3 weeks

CSRF with Spring and Angular 2

I am trying to implent CSRF-protection with Spring Security (4.1.3) and Angular 2.0.1
There are many sources to related topics but I cannot find a clear instruction. Some of the statements even contradict each other.
I read about springs way of doing it (though the guide describes the Angular 1 way) Spring Security Guide with Angular
IT implies, that with
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
everything should work "out of the box".
Even further the angular guide to security describes the CSRF-protection as build in.
In my enviroment the POST looks like this:
There is an OPTIONS-call which returns POST, 200 OK and a XSRF-TOKEN - cookie.
my http.post adds an authorization-header and adds the RequestOption "withCredentials"
It sends three cookies, two JSessionID's and an XSRF-TOKEN that is different from the one recieved by the OPTIONS-call, no XSRF-header.
Debugging into the Spring CsrfFilter shows me that it looks for a header named X-XSRF-TOKEN and compares it to the token in the cookie named XSRF-TOKEN.
Why doesn't Angular send the header, too?
How is this secure if Spring only checks the provided cookie and the provided header with no serverside action whatsoever?
There are some similar questions like this but the only answer with 0 upvotes seems (to me) plain wrong, as CSRF, from my understanding, has to have a serverside check for the cookie validation.
This question only provides information on how to change the cookie or header name as explained here
What am I missing here? I doubt there is a mistake in the Spring Security implementation but I cannot quite get it to work.
Any ideas?
POST-call
login(account: Account): Promise<Account> {
let headers = new Headers({ 'Content-Type': 'application/json' });
headers.append('X-TENANT-ID', '1');
headers.append('Authorization', 'Basic ' + btoa(account.userName + ':' + account.password));
let options = new RequestOptions({ headers: headers, withCredentials:true });
return this.http.post(this.loginUrl, account, options).toPromise()
.then(this.extractData)
.catch(this.handleError)
}
Spring Security Config
[] csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
The problem was the application path. Spring has the option to set the cookie-path in its pipeline but it is not released yet.
I had to write my own implementation for the CsrfTokenRepository which would accept a different cookie path.
Those are the relevant bits:
public final class CookieCsrfTokenRepository implements CsrfTokenRepository
private String cookiePath;
#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());
// cookie.setPath(getCookiePath(request));
if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
cookie.setPath(this.cookiePath);
} else {
cookie.setPath(getRequestContext(request));
}
if (token == null) {
cookie.setMaxAge(0);
} else {
cookie.setMaxAge(-1);
}
if (cookieHttpOnly && setHttpOnlyMethod != null) {
ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
}
response.addCookie(cookie);
}
public void setCookiePath(String path) {
this.cookiePath = path;
}
public String getCookiePath() {
return this.cookiePath;
}

Categories

Resources