What does JWT being stateless really means?

Viewed 317

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);
      });
  };

1 Answers

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