Skip to content

Commit fd163be

Browse files
committed
Fix Axios infinite loop bug
1 parent 4ba91da commit fd163be

File tree

5 files changed

+47
-23
lines changed

5 files changed

+47
-23
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This GitHub repo accompanies my tutorial on the subject of how to use JWT Authen
55

66
If you want to use React as a frontend with Django Rest Framework as a backend, you'll notice that getting the Authentication system set up presents one of the largest early hurdles. Follow this tutorial to build a really ugly website demonstrating the process from start to finish, including Custom Users, refreshing tokens, and protected views. It's the tutorial I wish I had when I first started.
77

8-
The full tutorial on Medium lives here:
8+
The full tutorial on Hackernoon lives here: https://hackernoon.com/110percent-complete-jwt-authentication-with-django-and-react-2020-iejq34ta
99

1010

1111
## Tutorial content
@@ -25,6 +25,10 @@ Part 2 - React:
2525

2626
6. [Logging out & blacklisting tokens](https://github.com/Toruitas/Complete-JWT-Authentication/tree/2_4_logging_out)
2727

28+
Part 3 - Improvements to Axios Interceptor
29+
30+
7. [Fixing Axios infinite loop]()
31+
2832
Requirements:
2933
* Django 2 or 3
3034
* Django Rest Framework

djsr/djsr/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@
140140

141141
SIMPLE_JWT = {
142142
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
143-
'REFRESH_TOKEN_LIFETIME': timedelta(days=14),
143+
'REFRESH_TOKEN_LIFETIME': timedelta(minutes=10),
144144
'ROTATE_REFRESH_TOKENS': True,
145145
'BLACKLIST_AFTER_ROTATION': True,
146146
'ALGORITHM': 'HS256',

djsr/frontend/src/axiosApi.js

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,48 @@ axiosInstance.interceptors.response.use(
1717
error => {
1818
const originalRequest = error.config;
1919

20-
// test for token presence, no point in sending a request if token isn't present
21-
if (localStorage.getItem('refresh_token') && error.response.status === 401 && error.response.statusText === "Unauthorized") {
22-
const refresh_token = localStorage.getItem('refresh_token');
20+
if (!originalRequest._retry){
21+
originalRequest._retry = true;
22+
// test for token presence, no point in sending a request if token isn't present
23+
if (error.response.data.code === "token_not_valid" && error.response.status === 401 && error.response.statusText === "Unauthorized") {
24+
const refresh_token = localStorage.getItem('refresh_token');
2325

24-
return axiosInstance
25-
.post('/token/refresh/', {refresh: refresh_token})
26-
.then((response) => {
26+
if (refresh_token){
27+
const tokenParts = JSON.parse(atob(refresh_token.split('.')[1]));
2728

28-
localStorage.setItem('access_token', response.data.access);
29-
localStorage.setItem('refresh_token', response.data.refresh);
29+
// exp date in token is expressed in seconds, while now() returns milliseconds:
30+
const now = Math.ceil(Date.now() / 1000);
31+
console.log(tokenParts.exp);
3032

31-
axiosInstance.defaults.headers['Authorization'] = "JWT " + response.data.access;
32-
originalRequest.headers['Authorization'] = "JWT " + response.data.access;
33+
if (tokenParts.exp > now) {
34+
return axiosInstance
35+
.post('/token/refresh/', {refresh: refresh_token})
36+
.then((response) => {
37+
38+
localStorage.setItem('access_token', response.data.access);
39+
localStorage.setItem('refresh_token', response.data.refresh);
40+
41+
axiosInstance.defaults.headers['Authorization'] = "JWT " + response.data.access;
42+
originalRequest.headers['Authorization'] = "JWT " + response.data.access;
3343

34-
return axiosInstance(originalRequest);
35-
})
36-
.catch(err => {
37-
console.log(err)
38-
});
44+
console.log("Tokens refreshed.")
45+
46+
return axiosInstance(originalRequest);
47+
})
48+
.catch(err => {
49+
console.log(err)
50+
});
51+
}else{
52+
console.log("Refresh token is expired", tokenParts.exp, now);
53+
}
54+
}else{
55+
console.log("Refresh token missing.")
56+
}
57+
}
3958
}
59+
4060
// specific error handling done elsewhere
41-
return Promise.reject({...error});
61+
return Promise.reject(error);
4262
}
4363
);
4464

djsr/frontend/src/components/login.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ class Login extends Component {
2626
localStorage.setItem('access_token', result.data.access);
2727
localStorage.setItem('refresh_token', result.data.refresh);
2828
}
29-
).catch (error => {
30-
throw error;
31-
})
29+
).catch (error => {
30+
throw error;
31+
})
3232
}
3333

3434
async handleSubmit(event) {

djsr/frontend/static/frontend/public/main.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)