Skip to content

Commit ceb105a

Browse files
authored
Merge pull request #9 from Toruitas/3_bugfixes
3 bugfixes
2 parents 4ba91da + b532ffd commit ceb105a

File tree

5 files changed

+60
-27
lines changed

5 files changed

+60
-27
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](https://github.com/Toruitas/Complete-JWT-Authentication/tree/3_bugfixes)
31+
2832
Requirements:
2933
* Django 2 or 3
3034
* Django Rest Framework

djsr/djsr/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@
139139
}
140140

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

djsr/frontend/src/axiosApi.js

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import axios from 'axios'
44

5+
const baseURL = 'http://127.0.0.1:8000/api/'
6+
57
const axiosInstance = axios.create({
6-
baseURL: 'http://127.0.0.1:8000/api/',
8+
baseURL: baseURL,
79
timeout: 5000,
810
headers: {
911
'Authorization': localStorage.getItem('access_token') ? "JWT " + localStorage.getItem('access_token') : null,
@@ -12,33 +14,60 @@ const axiosInstance = axios.create({
1214
}
1315
});
1416

17+
1518
axiosInstance.interceptors.response.use(
1619
response => response,
1720
error => {
18-
const originalRequest = error.config;
21+
const originalRequest = error.config;
1922

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');
23+
// Prevent infinite loops early
24+
if (error.response.status === 401 && originalRequest.url === baseURL+'token/refresh/') {
25+
window.location.href = '/login/';
26+
return Promise.reject(error);
27+
}
2328

24-
return axiosInstance
25-
.post('/token/refresh/', {refresh: refresh_token})
26-
.then((response) => {
29+
if (error.response.data.code === "token_not_valid" &&
30+
error.response.status === 401 &&
31+
error.response.statusText === "Unauthorized")
32+
{
33+
const refreshToken = localStorage.getItem('refresh_token');
2734

28-
localStorage.setItem('access_token', response.data.access);
29-
localStorage.setItem('refresh_token', response.data.refresh);
35+
if (refreshToken){
36+
const tokenParts = JSON.parse(atob(refreshToken.split('.')[1]));
3037

31-
axiosInstance.defaults.headers['Authorization'] = "JWT " + response.data.access;
32-
originalRequest.headers['Authorization'] = "JWT " + response.data.access;
38+
// exp date in token is expressed in seconds, while now() returns milliseconds:
39+
const now = Math.ceil(Date.now() / 1000);
40+
console.log(tokenParts.exp);
3341

34-
return axiosInstance(originalRequest);
35-
})
36-
.catch(err => {
37-
console.log(err)
38-
});
39-
}
42+
if (tokenParts.exp > now) {
43+
return axiosInstance
44+
.post('/token/refresh/', {refresh: refreshToken})
45+
.then((response) => {
46+
47+
localStorage.setItem('access_token', response.data.access);
48+
localStorage.setItem('refresh_token', response.data.refresh);
49+
50+
axiosInstance.defaults.headers['Authorization'] = "JWT " + response.data.access;
51+
originalRequest.headers['Authorization'] = "JWT " + response.data.access;
52+
53+
return axiosInstance(originalRequest);
54+
})
55+
.catch(err => {
56+
console.log(err)
57+
});
58+
}else{
59+
console.log("Refresh token is expired", tokenParts.exp, now);
60+
window.location.href = '/login/';
61+
}
62+
}else{
63+
console.log("Refresh token not available.")
64+
window.location.href = '/login/';
65+
}
66+
}
67+
68+
4069
// specific error handling done elsewhere
41-
return Promise.reject({...error});
70+
return Promise.reject(error);
4271
}
4372
);
4473

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)