8

I'm searching for a clean way to store my access token in React.

  • First thing: I don't want to use local storage. I don't need to make my access token persistent since I can always refresh it.
  • I also excluded a cookie since I want to prevent CSRF attacks. By storing the access token only in memory, in fact, the page needs to be loaded to get the token and authenticate requests (refresh token can be used only to refresh)
  • I thought of using redux/context, however, the function calling the API is not a child of a component so I can't access the token from that. Furthermore, I don't want to pass the token as a parameter, since I want to keep decoupled the HTTP logic. Maybe there is a clean way to use it?

After a bit of research, I found that using a global variable is a working "cheat" to obtain what I want. However, I was guessing if there was a clearer way to get the same result.

//token.js

const access_token= "";

export const setAccessToken(token){
 access_token=token;
}

export const getAccessToken(){
 return access_token;
}

//NOTICE:
// -ON LOGIN/REFRESH: I set the access token
// -ON API CALLS: I get the access token and I add it to the header
api.js

const baseURL= "http://my_base_url";

const generateApiInterface = ()=>{
    let headers : any= {
       
    };
    
    token=getAccessToken(); //HERE I NEED TO RETRIEVE MY ACCESS TOKEN

    if(token){
        headers={
            'Authorization': 'Token '+ token,
            ...headers //append other basic proprieties
        }
    }

    return axios.create({
        baseURL: baseURL,
        headers: headers
    });
}

const api = generateApiInterface();

AndreaCostanzo1
  • 1,799
  • 1
  • 12
  • 30
  • What's wrong with storing it in a cookie and just retrieving it from there every time you want to make an HTTP call? – codemonkey Feb 01 '21 at 23:45
  • I already have the refresh token in an HTTP only cookie. I don't want to put the access token in a cookie to prevent CSRF attacks – AndreaCostanzo1 Feb 01 '21 at 23:49
  • Since you've mentioned login, you must have some sort of a session. I'd store the access token on session level, there's no need to always get a fresh one, even if you can do it. – Zoli Szabó Feb 02 '21 at 08:25
  • Actually I'm using JWTs to store "session" on client-side. To avoid security issues I need to refresh my tokens. To better understand my approach I link you to the post in which I explain my authentication process (asking if it's a good approach): https://stackoverflow.com/questions/65946041/guidelines-to-build-a-secure-jwt-authentication-process – AndreaCostanzo1 Feb 02 '21 at 08:52

1 Answers1

5

The way you're doing it is preferable to keep anything in memory. If this is server side, then you'd want to make sure you delete the token once you're done, but if not then the current approach is desirable. Note, you're using a const, you'd want to change that to a let. Another idea would be to use session storage, but then that brings the idea of XSS.

However, React provide a way to have "global" state - this would be to use a provider and context. Here's an example:

// provider.tsx

import React, { useContext, createContext, FC, useState } from 'react'

type AccessTokenContext = [string, React.Dispatch<React.SetStateAction<string>>]

const AccessTokenProvider: FC = (props) => {
    const [accessToken, setAccessToken] = useState<string>(null)
    return <AccessToken.Provider value={[accessToken, setAccessToken]} {...props} />
}

const AccessToken = createContext<AccessTokenContext>(null)

const useAccessToken = (): AccessTokenContext => useContext<AccessTokenContext>(AccessToken)

export { AccessTokenProvider, useAccessToken }

You'd have to wrap your app container in AccessTokenProvider:

// index.tsx

import React from 'react'
import ReactDOM from 'react-dom'

import './index.css'

import App from './App'
import { AccessTokenProvider } from './providers/AccessTokenProvider'

ReactDOM.render(
    <AccessTokenProvider>
        <App />
    </AccessTokenProvider>,
    document.getElementById('app')
)

Then you can then use the hook useAccessToken in App and any children of App. Of course, this provider doesn't have to be root level, but it's easiest to include it here.

// app.tsx

import React, { FC } from 'react'

const App: FC = props => {
    const [accessToken, setAccessToken] = useAccessToken()
    return <div>{/* content */}</div>
}

export default App
Kobe
  • 6,226
  • 1
  • 14
  • 35
  • Hi kobe, thanks for the answer. The only thing I'm missing is: how I properly use the access token inside an "API" file where I use Axios since there isn't a functional component there? I have updated my question to make clear what is the most "complicated" part that I'm missing. Take a look to api.js. Notice: if you know a better way to call APIs and append access token to them I'm open to suggestions – AndreaCostanzo1 Feb 02 '21 at 08:38
  • PS. I found something that seems better of my choice in terms of code design. Can your solution be re-adapted to obtain something similar? I give you the link: https://stackoverflow.com/a/38480550/14106548 (In this specific case instead of using context hooks it relies on redux, but I think that something similar can be achieved. I'm still diving in topics like context and redux for this particular use case) – AndreaCostanzo1 Feb 02 '21 at 08:48
  • In your api function, can't you just accept the token as a parameter? So you'd call `generateApiInterface(token)`. @AndreaCostanzo1 – Kobe Feb 02 '21 at 10:45
  • 1
    In response to your second comment, it seems nicer to do something that way, so you don't have to worry about where you set the token. I think using context already solves this issue - when you update the token, this triggers a re-render, so the latest value will always be used. – Kobe Feb 02 '21 at 10:48
  • Thanks kobe, I didn't want to use the token as a parameter since I wanted to separate the http logic from the UI. It's just because I wanted to have all the http logic centered in the API file to simplify future updates of the code. – AndreaCostanzo1 Feb 02 '21 at 10:53