JWT authentication between Django and ReactJS I am currently using Django (2.1) to build an API, and I have added djangorestframework-jwt to manage JWT.

Here is the configuration:

JWT_AUTH = {
    'JWT_SECRET_KEY': SECRET_KEY,
    'JWT_VERIFY': True,
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=14),
    'JWT_ALLOW_REFRESH': True,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    'JWT_AUTH_HEADER_PREFIX': 'Bearer',
}


and the endpoints:

urlpatterns = [
    path('auth/get-token/', obtain_jwt_token),
    path('auth/refresh-token/', refresh_jwt_token),
]


The client is built with ReactJS. I use an axios instance as a client to communicate with the API. This instance is created that way :

import axios from 'axios'
import jwt_decode from 'jwt-decode' // eslint-disable-line
import { signOut } from '../actions/authActions'

const signOutOn401 = (statusCode) =&gt; {
  if (statusCode === 401) {
    signOut()
    window.location = '/signin'
  }
}

const client = axios.create({
  baseURL: process.env.API_URL,
  headers: {'Authorization': ''}
})

/*
 * This interceptor is used for:
 * - adding Authorization header if JWT available
 * - refreshing JWT to keep user authenticated
 */
client.interceptors.request.use((config) =&gt; {
  if (window.localStorage.getItem('token')) {
    let token = window.localStorage.getItem('token')
    // Calculate time difference in days
    // between now and token expiration date
    const t = ((jwt_decode(token).exp * 1000) - Date.now()) / 1000 / 60 / 60 / 24
    // Refresh the token if the time difference is
    // smaller than 13 days (original token is valid 14 days)
    if (t &lt; 13) {
      axios.post(`${process.env.API_URL}/auth/refresh-token/`, {
        token: token
      })
        .then(({data}) =&gt; {
          token = data.token
        })
        .catch((error) =&gt; {
          signOutOn401(error.response.status)
          return error
        })
    }
    config.headers['Authorization'] = `Bearer ${token}`
  }

  return config
})

/*
 * This interceptor is used for:
 * - disconnect user if JWT is expired or revoked
 */
client.interceptors.response.use(
  (response) =&gt; {
    return response
  },
  (error) =&gt; {
    signOutOn401(error.response.status)
    return error
  }
)

export default client


The signout action only clear the session and clean the store :

export const signOut = () =&gt; {
  window.localStorage.clear()

  return ({
   type: SIGN_OUT,
    payload: {
      authenticated: false,
      user: {},
      errorMessage: ''
    }
  })
}


Everything looks working fine, I would just like to know if that implementation is correct, and if there is no security flaw :)
UX concern: refresh period

I wonder if you are confusing the access token expiration setting (JWT_EXPIRATION_DELTA) with the refresh token expiration (JWT_REFRESH_EXPIRATION_DELTA). In either case, your t &lt; 13 check should be related to the refresh token expiration, not the access token expiration. 13 seems to be chosen because it is almost 14; hence my comment. At the very least these refreshes should never go beyond your refresh expiration (7 days), or they will fail, which defeats the purpose of providing a refresh token (because the user would always have to authenticate after the expiration).

Security concern: access token expiration

On a slightly similar note, but this one is a security concern -- you typically want the access token to have a much shorter life than the refresh token. Yours is reversed, as the access token (JWT_EXPIRATION_DELTA) is 14 days vs. the refresh token (JWT_REFRESH_EXPIRATION_DELTA) at 7 days. Note the default JWT_EXPIRATION_DELTA for the djangorestframework-jwt library is a much more conservative and standard 5 minutes:


  Default is datetime.timedelta(seconds=300)(5 minutes).


Per OAuth 2 spec:


  access tokens may have a shorter lifetime and fewer permissions than
  authorized by the resource owner


Per Auth0 guidance:


  Access tokens carry the necessary information to access a resource directly.
  
  ...
  
  Access tokens usually have an expiration date and are short-lived.
  
  ...
  
  Common implementations allow for direct authorization checks against
  an access token. That is, when an access token is passed to a server
  managing a resource, the server can read the information contained in
  the token and decide itself whether the user is authorized or not (no
  checks against an authorization server are needed). This is one of the
  reasons tokens must be signed (using JWS, for instance). On the other
  hand, refresh tokens usually require a check against the authorization
  server. This split way of handling authorization checks allows for
  three things:
  
  
  Improved access patterns against the authorization server (lower load, faster checks)
  Shorter windows of access for leaked access tokens (these expire quickly, reducing the chance of a leaked token allowing access to a
  protected resource)
  
  
  ...


Security concern: refresh token revocation

It is difficult for me to tell whether that djangorestframework-jwt library will revoke all previous refresh tokens for a given session once a new refresh token is issued for that session. Ideally it should, especially if you are using unauthenticated clients, which it looks like you are (assuming that React app is just a public web app; if it's instead packaged in a native client you may and probably should be authenticating that client). You may want to verify that behavior of the library with your own testing.

If the library does not revoke previous refresh tokens, then you should probably mitigate the risk of having so many outstanding valid refresh tokens per session (~= expiration time divided by refresh period, or currently 7 / 1 = 7 valid tokens!!!) by making the refresh period much closer to the refresh token expiration. So if you keep the refresh expiration at 7 days, then only refreshing at 6 days would make sense. Then you would typically have 1 or at most 2 outstanding valid refresh tokens for a session. However, if given a choice, it is much more secure to have automatically revoked refresh tokens and shorter refresh periods; that way a compromised refresh token is much more likely to be invalid -- and you still wouldn't have to lose the benefit of the authorized user having a long refresh expiration. (The whole point of the long expiration is for situations such as an unattended laptop or mobile browser.)

Per OAuth 2 spec:


  The authorization server MUST verify the binding between the refresh
  token and client identity whenever the client identity can be
  authenticated.  When client authentication is not possible, the
  authorization server SHOULD deploy other means to detect refresh
  token abuse.
  
  For example, the authorization server could employ refresh token
  rotation in which a new refresh token is issued with every access
  token refresh response.  The previous refresh token is invalidated
  but retained by the authorization server.  If a refresh token is
  compromised and subsequently used by both the attacker and the
  legitimate client, one of them will present an invalidated refresh
  token, which will inform the authorization server of the breach.
  
  The authorization server MUST ensure that refresh tokens cannot be
  generated, modified, or guessed to produce valid refresh tokens by
  unauthorized parties.