Spring Keycloak Adapter sometimes returns with "Token is not active" when uploading files - spring-boot

lately we are facing an issue that our Spring Boot backend service (stateless REST service) SOMETIMES returns an HTTP 401 (Unauthorized) error when users try to upload files >70 MB (or in other words, when the request takes longer than just a couple of seconds). This does not occur consistently and only happens sometimes (~every second or third attempt).
The www-authenticate header contains the following in these cases:
Bearer realm="test", error "invalid_token", error_description="Token is not active"
Our Spring (Boot) configuration is simple:
keycloak.auth-server-url=${KEYCLOAK_URL:http://keycloak:8080/auth}
keycloak.realm=${KEYCLOAK_REALM:test}
keycloak.resource=${KEYCLOAK_CLIENT:test}
keycloak.cors=true
keycloak.bearer-only=true
Essentially, our frontend code uses keycloak-js and does the following to keep the access token fresh:
setInterval(() => {
// updates the token if it expires within the next 5s
this.keycloak.updateToken(5).then((refreshed) => {
console.log('Access token updated:', refreshed)
if (refreshed) {
store.commit(AuthMutationTypes.SET_TOKEN, this.keycloak.token);
}
}).catch(() => {
console.log('Failed to refresh token');
});
}, 300);
Further, we use Axios and a respective request filter to inject the current token:
axios.interceptors.request.use(
(request: AxiosRequestConfig) => {
if (store.getters.isAuthenticated) {
request.headers.Authorization = 'Bearer ' + store.getters.token;
}
return request;
}
);
This worked very well so far and we have never experienced such a thing for our usual GETs/POSTs/PUTs etc. This happens only when users try to upload files larger than (around) 70MBish.
Any hint or tip how to debug this any further? We appreciate any help...
Cheers

Related

Remove token in frontend using Vue JS after deleting it from database in backend with Laravel?

I'm developing an application with Vue 3 and Laravel 9.
I did all login, registration and logout. However, I had an idea to keep the session unique per browser. I delete all tokens on user login if they exist. That way I can have front-end control with just one session open per browser, as I've already done this logic there.
The problem knowing the ways I can remove the token on the front-end to logout/redirect the user to login, after receiving the "Unauthenticated". For I check the vue routes with the token in localStorage.
Remembering, I'm using sanctum.
I'm looking for procedures. However, I'm afraid of doing something wrong, as I wonder about data security and vulnerabilities, as these processes of building a software require caution and a lot of testing.
I'm not sure if i got the question right, but you would normalle use an axios interceptor like so:
_axios.interceptors.response.use(
response => response,
error => {
if (error.response.status === 401) {
console.log('Intercept: 401 - Unauthenticated')
//DELETE THE TOKEN FROM STORAGE AND FROM AXIOS HEADER
delete window.axios.defaults.headers.common.Authorization
localStorage.removeItem('token')
return router.replace({ name: 'login' })
}
if (error.response.status === 403) {
console.log('Intercept: 403 - Unauthorized')
}
if (error.response.status === 422) {
console.log('Intercept: 422 - Validation Error')
}
return Promise.reject(error)
},
)

Axios : put on hold requests

I have the following axios interceptor.
It checks the validity of the stored token. If not valid, a request is fired to retrieve and store the refreshed token.
axios.interceptors.request.use(async config =>
if(checkValidity(localStorage.getItem('token')) === false) {
const response = await axios.get('http://foobar.com/auth/refresh');
localStorage.setItem('token', response.headers['token']);
config = getConfigFromResponse(response);
}
return config;
});
It works great. The problem is that if I have many requests with invalid token then many requests to http://foobar.com/auth/refresh are done to refresh it.
Is it possible to put all the requests in an array and fire them after when the refresh is done ?
The idea is to avoid catching 401 errors and replaying the request : this is why I want to "save" the requests while the token is being retrieved and then fire them when the token is ready.

Dealing with axios interceptors when sending many ajax requests

I use Larave+JWT and vue2 + vuex2 + axios
So when user logins I store auth token in vuex store. When the token expires I need to refresh it. In order to refresh it I need to send the same token to /refresh route, and get a new token. At least that's how I got it and actually it works.
The problem is that interceptor catches 401 responses and tries to refresh token, but what if, say, in my component I send many requests with expired token? Since ajax requests are async, the interceptor code runs many times. So I got many refresh requests. Once the initial token is refreshed it is not considered valid. When interceptor tries to refresh invalid token server responds with error and I redirect to login page.
Here is the code:
axios.interceptors.response.use((response) => {
return response;
}, (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
axios.post('auth/refresh').then((response) => {
let token = response.data.token
store.dispatch('auth/setAuthToken', token)
let authorizationHeader = `Bearer ${token}`
axios.defaults.headers = { 'Authorization': authorizationHeader }
originalRequest.headers['Authorization'] = authorizationHeader
return axios(originalRequest)
}, (error) => {
store.dispatch('auth/clearAuthInfo')
router.push({ name: 'login' })
})
}
return Promise.reject(error);
});
I think you'll have to change your approach on how you refresh your token. Leaders like Auth0 recommends proactive periodic refresh to solve this problem.
Here is a SO answer where they talk about it.
Set the token expiration to one week and refresh the token every time the user open the web application and every one hour. If a user doesn't open the application for more than a week, they will have to login again and this is acceptable web application UX.

Rest API call fails with status 302 (spring security, html5)

I'm having 2 projects:
1) Restful Project with jdbc spring security (username:password) => port:9091
2) HTML5 Application with a JQGrid => port:9092
I have disabled csrf token in both the projects. Now, I'm able to hit the rest service successfully from browser and using postman and by passing the credentials
But when I try to hit the service from HTML5 Application (Jqgrid), I'm see that XHR Call is ending with status 302 and I'm not getting the results back.
So, please guide me on the same.
Additional Points:
I'm able to successfully hit the rest service from postman by passing basic authentication. But from JQGrid, I'm not able to query data even after using below code in my JQGrid. It always goes to status 302. (An FYI, I'm using stateless authentication in my spring security) :
loadBeforeSend: function(jqXHR) {
jqXHR.setRequestHeader("Authorization", CURRENT_AUTH_KEY);
},
beforeSend: function (request)
{
request.withCredentials = true;
request.setRequestHeader("Authorization", CURRENT_AUTH_KEY);
},
ajaxEditOptions: {
beforeSend: function(jqXHR) {
jqXHR.setRequestHeader("Authorization", CURRENT_AUTH_KEY);
}
},
ajaxGridOptions: { Authorization: CURRENT_AUTH_KEY } ,

SailsJS - using sails.io.js with JWT

I have implemented an AngularJS app, communicating with Sails backend through websockets, using sails.io.js.
Since the backend is basically a pure API and will be connected to from other apps as well, I'm trying to disable sessions completely and use JWT.
I have set up express-jwt and can use regular HTTP requests quite nicely, but when I send a request through sails.io.js, nothing happens at all - websocket request keeps pending on the client, and there's nothing happening on the server (with "silly" log level).
I've tried patching sails.io.js to support the query parameter, and when connecting, I send the token from Angular, but in the best case, I get a response with error message coming from express-jwt saying credentials are missing...
I've also seen some hints that socket.js in sails needs to be modified with beforeConnect, I've seen socketio-jwt, but have no idea where and how to plug that in, in Sails.
Has anyone implemented this and is using JWT with Sails and sockets? I'd appreciate any kind of hint in what direction to go :)
I realised that policy I've put in place and that was using express-jwt abstracted too much away from me, so I didn't figure out what exactly was happening. Once I looked at other examples, I've figured out that I only needed to check what's different for websocket requests than regular, and I quickly found a way around the problem.
So:
set up token signing and sending on login
Angular takes the token and saves to local storage
Create an interceptor for HTTP requests to add authorization header and token
Fix up sails.io.js to forward query parameters provided through options (as mentioned in the question)
When connecting using sails.io.js, send token as query parameter, i.e. url + '?token=' + token
In sails policy, check all combinations for token, including req.socket.handshake.query, as below:
module.exports = function (req, res, next) {
var token;
if (req.headers && req.headers.authorization) {
var parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
var scheme = parts[0],
credentials = parts[1];
if (/^Bearer$/i.test(scheme)) {
token = credentials;
}
} else {
return res.json(401, {err: 'Format is Authorization: Bearer [token]'});
}
} else if (req.param('token')) {
token = req.param('token');
// We delete the token from param to not mess with blueprints
delete req.query.token;
}
// If connection from socket
else if (req.socket && req.socket.handshake && req.socket.handshake.query && req.socket.handshake.query.token) {
token = req.socket.handshake.query.token;
} else {
sails.log(req.socket.handshake);
return res.json(401, {err: 'No Authorization header was found'});
}
JWTService.verifyToken(token, function (err, token) {
if (err) {
return res.json(401, {err: 'The token is not valid'});
}
sails.log('Token valid');
req.token = token;
return next();
});
};
It works well! :)

Resources