What does JWT being stateless really means? - django

Hi and thanks in advance,
I've successfully setup JWT authentication using django-rest-framework-simplejwt and React but I'm still very confused about the advantages and specifically database hits.
I'm using simplejwt with ROTATE_REFRESH_TOKENS': True 'BLACKLIST_AFTER_ROTATION': True, when my access_token expire I ask for a new one through /api/token/refresh and it blacklist old tokens, I'm using axios interceptors to perform that automatically.
But in my understanding the benefits of JWt is that they are stateless, meaning I don't have to hit the user database table everytime I want to make an a request that needs authentication permission.
The problem is even with a simple view like this :
class IsConnecteddAPI(APIView):
permission_classes = [permissions.IsAuthenticated]
def get(self, request, *args, **kwargs):
data = "You seem to be connected"
return Response(data, status=status.HTTP_200_OK)
using django-silk I see that it still performs 1 query to my user table when I call it with a valid access token, is that normal ? If so why do we say that JWT are stateless ? I'm really confused.
That's my axios code if needed :
import axios from "axios";
const baseURL = "http://localhost:5000";
const axiosInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
headers: {
Authorization: localStorage.getItem("accesstoken")
? "JWT " + localStorage.getItem("accesstoken")
: null,
"Content-Type": "application/json",
accept: "application/json",
},
});
const axioAnonymousInstance = axios.create({
baseURL: baseURL,
timeout: 5000,
headers: {
"Content-Type": "application/json",
accept: "application/json",
},
});
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error.config;
if (typeof error.response === "undefined") {
alert(
"A server/network error occurred. " +
"Looks like CORS might be the problem. " +
"Sorry about this - we will get it fixed shortly."
);
return Promise.reject(error);
}
if (
error.response.status === 401 &&
originalRequest.url === baseURL + "token/refresh/"
) {
window.location.href = "/login/";
return Promise.reject(error);
}
if (
error.response.data.code === "token_not_valid" &&
error.response.status === 401 &&
error.response.statusText === "Unauthorized"
) {
const refreshToken = localStorage.getItem("refreshtoken");
if (refreshToken) {
const tokenParts = JSON.parse(atob(refreshToken.split(".")[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
console.log(tokenParts.exp);
if (tokenParts.exp > now) {
return axioAnonymousInstance
.post("/api/token/refresh/", { refresh: refreshToken })
.then((response) => {
localStorage.setItem("accesstoken", response.data.access);
localStorage.setItem("refreshtoken", response.data.refresh);
axiosInstance.defaults.headers["Authorization"] =
"JWT " + response.data.access;
originalRequest.headers["Authorization"] =
"JWT " + response.data.access;
return axiosInstance(originalRequest);
})
.catch((err) => {
// redirect ro /login here if wanted
console.log("axios Safe Instance error");
console.log(err);
// window.location.href = "/login/";
});
} else {
console.log("Refresh token is expired", tokenParts.exp, now);
window.location.href = "/login/";
}
} else {
console.log("Refresh token not available.");
window.location.href = "/login/";
}
}
// specific error handling done elsewhere
return Promise.reject(error);
}
);
export { axiosInstance, axioAnonymousInstance };
( I know I shouldn't use localStorage but whatever )
and I would typically just call this function to make the simple request to the view written above :
const IsConnected = () => {
axiosInstance
.get("/api/is_connected/")
.then((response) => {
if (response.status === 200) {
console.log(response.data);
console.log("Is connected : CONNECTED ");
} else {
console.log("IS connected : not connected");
}
})
.catch((error) => {
console.log("Is connected : NOT CONNECTED");
console.log(error);
});
};

Without the specifics of the exact query hit your db, it's hard to tell what is happening (the db query must have originated from a middleware because there's nothing in your code that does it, and I suspect it's django's CsrfViewMiddleware). However, as for your question of JWT being stateless, I suggest you to take a look at the official introduction.
Basically, what happens with a JWT is that your server performs a signature verification on the token using your server's secret key (please beware of some problems). If the verification passes, then the data stored inside the JWT is trusted and read as is, which is why no database query is necessary. Of course, this does mean that your user will know exactly what is stored inside their token because the data is a simple base64 encoded JSON object.

Related

Twitter_api_v2 reply to a tweet example

Could anyone Show me an example of your finished Parameters and Endpoint for a Twitter Reply maybe with a Screenshot? Because i dont understand exactly what to type in my Params and, do I got to change anything in the Pre-request Script?
Kind regards Alex
For the Params for https://api.twitter.com/2/tweets I tried:
Key : in_reply_to
Value : tweet_id
And the Result was "errors"
"message": "The query Parameters [in_reply_to] is not one of [expantions,tweet.fields,media.fields,poll.fields,place.fields,user.fields]"
"title":"Invalid Request"
"detail": "One or more Parameters to your request Was invalid.",
"type":"https://api.twitter.com/2/problems/invalid-request"
From the twitter's documentation
query parameter ids is required. You missed that parameter.
I will get tweet this demo
https://twitter.com/pascal_bornet/status/1604754709000200193
BY Postman
Full code by node.js
#1 Get an access token by API Key and API secret
#2 Get text by access token
Credential in config.json
{
"API_KEY" : "7hK your API Key GND",
"API_KEY_SECRET" : "Zr4 your API Key secret 0qX0"
}
Save as get-tweet.js
const axios = require('axios')
const config = require('./config.json');
const getAccessToken = async () => {
try {
const resp = await axios.post(
'https://api.twitter.com/oauth2/token',
'',
{
params: {
'grant_type': 'client_credentials'
},
auth: {
username: config.API_KEY,
password: config.API_KEY_SECRET
}
}
);
// console.log(resp.data);
return Promise.resolve(resp.data.access_token);
} catch (err) {
// Handle Error Here
console.error(err);
return Promise.reject(err);
}
};
const getTweetText = async (token, tweet_id) => {
try {
const resp = await axios.get(
`https://api.twitter.com/2/tweets?ids=${tweet_id}`,
{
headers: {
'Authorization': 'Bearer '+ token,
}
}
);
return Promise.resolve(resp.data);
} catch (err) {
// Handle Error Here
console.error(err);
return Promise.reject(err);
}
};
getAccessToken()
.then((token) => {
console.log(token);
getTweetText(token, '1604754709000200193')
.then((result) => {
console.log(result.data[0].text);
})
})
Get Result
$ node get-tweet.js
AAAAAksadf--very long access token in here ----JlIMJIIse
Is this the future of Christmas shopping?
Credit: Nike
#innovation #AR # VR #AugmentedReality https://~~~

Expecting to get "non_field_errors: Unable to log in with provided credentials", but not getting it

Expectation: when wrong login credentials are provided, "non_field_errors: Unable to log in with provided credentials" is returned, such as below (screenshot from a tutorial which I'm following verbatim)
Reality: instead I'm getting the error below.
This gets printed to the console:
POST http://127.0.0.1:8000/api/v1/token/login 400 (Bad Request)
Interestingly I get this same error when I try to create users with passwords that are too short. I'm not having any issues with axios or the server when I provide the right credentials for log in, or use passwords of sufficient length when creating new users. When trying to catch errors such as these that I'm failing to get the expected result.
My code for catching the error is the same as in the tutorial:
methods: {
submitForm() {
axios.defaults.headers.common['Authorization'] = ''
localStorage.removeItem('token')
const formData = {
username: this.username,
password: this.password
}
axios
.post('/api/v1/token/login', formData)
.then(response => {
const token = response.data.auth_token
this.$store.commit('setToken', token)
axios.defaults.headers.common['Authorization'] = 'Token ' + token
localStorage.setItem('token', token)
this.$router.push('/dashboard/my-account')
})
.catch(error => {
if (error.response) {
for (const property in error.response) {
this.errors.push(`${property}: ${error.response.data[property]}`)
}
} else if (error.message) {
this.errors.push('Something went wrong. Please try again!')
}
})
}
}
Is there something in the server settings that I should change?
I'm using Django, rest framework, and djoser.
Don't know if you're using a custom exception handler in Django rest framework but it looks like the issue could be from the way you're handling the error in your frontend application.
You can handle the errors like this.
methods: {
submitForm() {
axios.defaults.headers.common['Authorization'] = ''
localStorage.removeItem('token')
const formData = {
username: this.username,
password: this.password
}
axios
.post('/api/v1/token/login', formData)
.then(response => {
const token = response.data.auth_token
this.$store.commit('setToken', token)
axios.defaults.headers.common['Authorization'] = 'Token ' + token
localStorage.setItem('token', token)
this.$router.push('/dashboard/my-account')
})
.catch(error => {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
})
}
Can be found here

AWS Lambda Rerouting after Twitter Authorizing

I implemented a twitter login authorizer and I put an API route as the callback.
The Lambda function evoked on that route is the following:
const loginTwitterCallback = async (e, context) => {
const fetch = (...args) =>
import("node-fetch").then(({ default: fetch }) => fetch(...args));
const state = e.queryStringParameters.state;
const code = e.queryStringParameters.code;
try {
await fetch(
"https://api.twitter.com/2/oauth2/token?code=" +
code +
"&grant_type=authorization_code&client_id=" +
process.env.TWITTER_CLIENT_ID +
"&code_verifier=jwqoijoiw&redirect_uri=" + MY REDIRECT URI,
{
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded",
},
}
)
.then((res) => {
return res.json();
})
.then(async (data) => {
const accessToken = data.access_token;
return {
headers: {
Location:
"http://127.0.0.1:3000/auth/social?type=twitter&access_token=" +
encodeURIComponent(accessToken),
},
body: null,
statusCode: 302,
};
});
} catch (err) {
console.log(err);
}
};
Basically the user should be re-routed to the front-end where another POST request will be made to the API which will make a request to the Twitter API with the Bearer token and update the database.
The point is, I'm not being redirected to the front-end in the first place and I don't understand how to fix it.
Thanks a lot in advance.

Fixing custom authenticator's restore method

I'm using ember-simple-auth and a custom authenticator for an HTTP basic login with CSRF protection. Everything is working fine except sometimes my restore method resolves when it should be failing, like when the session expires.
When authentication succeeds I resolve with the csrf token, but then when the token or session expires and I refresh the page, the resolve method still succeeds because all I'm doing is checking if the token is still there (not if it's valid). I know this is wrong, so I guess my question would be what is the proper way to handle this? Should I also be resolving with the session id? Should I be sending an AJAX request in the restore method with the stored token to see if it is still valid and returns success? I'm interested in hearing about any other improvements I could make as well.
Here is my authenticator code:
import Ember from 'ember';
import ENV from 'criteria-manager/config/environment';
import Base from 'ember-simple-auth/authenticators/base';
export default Base.extend({
restore(data) {
return new Ember.RSVP.Promise((resolve, reject) => {
if (data.token) {
Ember.$.ajaxSetup({
headers: {
'X-XSRF-TOKEN': data.token
}
});
resolve(data);
}
else {
reject();
}
});
},
authenticate(credentials) {
let csrfToken = this.getCookie('XSRF-TOKEN');
return new Ember.RSVP.Promise((resolve, reject) => {
Ember.$.ajax({
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(credentials.username + ":" + credentials.password));
xhr.setRequestHeader("X-XSRF-TOKEN", csrfToken);
},
url: ENV.host + "/api/users/login",
method: 'POST'
}).done(() => {
//A new CSRF token is issued after login, add it to future AJAX requests
Ember.$.ajaxSetup({
headers: {
'X-XSRF-TOKEN': this.getCookie('XSRF-TOKEN')
}
});
Ember.run(() => {
resolve({
token: this.getCookie('XSRF-TOKEN')
});
});
}).fail((xhr) => {
Ember.run(() => {
if(xhr.status === 0) {
reject("Please check your internet connection!");
}
else if (xhr.status === 401) {
reject("Invalid username and/or password.");
}
else {
reject("Error: Http Status Code " + xhr.status);
}
});
});
});
},
invalidate() {
return new Ember.RSVP.Promise((resolve, reject) => {
let csrfToken = this.getCookie('XSRF-TOKEN');
Ember.$.ajax({
beforeSend: function(xhr) {
xhr.setRequestHeader("X-XSRF-TOKEN", csrfToken);
},
url: ENV.host + '/logout',
method: 'POST'
}).done(() => {
Ember.run(() => {
resolve();
});
}).fail(() => {
Ember.run(() => {
reject();
});
});
});
},
getCookie(name) {
let alLCookies = "; " + document.cookie;
let cookieArray = alLCookies.split("; " + name + "=");
if (cookieArray.length === 2) {
return cookieArray.pop().split(";").shift();
}
}
});
Should I also be resolving with the session id? Should I be sending an
AJAX request in the restore method with the stored token to see if it
is still valid and returns success?
It all depends on your project's needs. In my opinion it's good to check if token is still valid. For example, oauth2-password-grant stores expiring date in session and when restoring simply compares it with current time. You may do this too. Or, if your backend has some token validation endpoint, you may send request to be sure if token is valid.

Trying to get then send a cookie using react and fetch

I've been trying to implement some authentication component in my app for a few hours now, and I still don't understand some of the things that are happening.
Basically, I'd like to send a POST request containing some credentials to my API, which sends me a cookie back with a token if the credentials worked. Then, the cookie should be included in the headers of all future requests to my API (which I believed was automatic).
server.js (my API is a mockup for now, with JSON files)
...
app.post('/api/login', jsonParser, (req, res) => {
fs.readFile(ACCOUNTS_FILE, (err, data) => {
if (err) {
console.error(err);
process.exit(1);
}
const accounts = JSON.parse(data);
const credentials = {
email: req.body.email,
password: req.body.password,
};
var token = null;
for (var i = 0; i < accounts.length; ++i) {
const account = accounts[i];
if (account.email === credentials.email
&& account.password === credentials.password) {
token = account.token;
break;
}
}
if (token) {
res.setHeader('Set-Cookie', `access_token=${token}; Secure; HttpOnly;`);
res.json({ token });
} else {
res.json({ token: null });
}
});
});
...
app.js
...
handleConnection(e) {
e.preventDefault();
const email = this.state.email.trim();
const password = this.state.password.trim();
if (!email && !password) {
return (false);
}
fetch(loginUrl, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
credentials: 'include',
},
body: JSON.stringify(this.state),
})
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.warn(error);
});
return (true);
}
...
Now the console.log(data) always displays my token (or null if my credentials are wrong), but the cookie thing doesn't work...
See, I receive the Set-Cookie header, but I still have no cookie on my page.
And even if I managed to get the cookie, when I try to create a cookie using document.cookie = "access_token=123"; and then send the request again, my cookie doesn't go in my header like it would with a jQuery Ajaxcall :
I read here that adding credentials: 'include' would save the day, but unfortunately it didn't.
What am I missing here?
Thanks in advance!
I had the same problem and I found the answer in Peter Bengtsson's comment here: https://davidwalsh.name/fetch
If I understood, in your case the fetch should be:
fetch(loginUrl, {
credentials: 'same-origin',
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(this.state),
})