Managing Access Tokens in React or React Native with Axios and Context API.
Tags: react, reactnative, codenewbie, security
Published: 2022-02-19 11:50:56


When I started developing Stateful Applications in React or React Native, I had to balance the ease of development as well as usefulness of the functionality while dealing with the statefulness especially while calling APIs.
I started with using redux and it was easy but with extra boilerplate code to be written with it. Here comes Context API, I have not used Context API before. However, for this application, it seems perfect.

**Benefits of using this approach**:

1. Less code to write, less code to be tested, less prone to errors.
2. The main changes happens at only one place.
3. Ability to cover more functionalities.
4. Faster than redux approach.

**Limitations of using this approach**:

1. Code newbies might have hard time understanding it at first.
2. Some scenarios might be difficult to debug.

**Process**:

1. Create and export a function which manipulates default axios options.
```tsx
import axios from "axios";

export const HOST = 'https://api.example.com/v1';

const basicToken = 'cGXysWJlcJhdsSJdIUP873mVzaFYxLTEyM1NlY3JldA';

export const configureAxiosHeaders = (token: string = basicToken): boolean => {
    try {
        axios.defaults.baseURL = HOST;
        axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
        axios.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded';
        console.log("Configured Axios")
        return true;
    } catch (error) {
        console.log(error);
        return false;
    }
}

```
2. Create a React Context.
```tsx

import React, { createContext, useState, useEffect } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage"
import { TokenDetails } from "../redux/slices/user";
import { configureAxiosHeaders } from "../api/config";
export interface AuthContextProps {
  auth: TokenDetails | null;
  setAuth: (auth: TokenDetails | null) => Promise<void>;
}
// Create a context
const AuthContext = createContext<AuthContextProps>({
  auth: null,
  setAuth: async () => {},
});
const AuthProvider: React.FC = ({children}) => {
  const [auth, setAuthState] = useState<TokenDetails|null>(null);
  // Get current auth state from AsyncStorage
  const getAuthState = async () => {
    try {
      const authDataString = await AsyncStorage.getItem('auth');
      console.log("authDataString", authDataString);
      const authData: TokenDetails | null = authDataString !== null ? JSON.parse(authDataString, (key, value) => {
        switch (key) {
          case 'status':
          case 'error_description':
          case 'access_token':
          case 'refresh_token':
            return String(value);
          case 'error_code':
          case 'user_id':
          case 'login_logid':
            return parseInt(value);
          case 'accessTokenExpiresAt':
          case 'refreshTokenExpiresAt':
            return new Date(value);
          default:
            return value;
        }
      }) : authDataString;
      // Configure axios headers
        if (authData !== null) {
            configureAxiosHeaders(authData.access_token);
          setAuthState(authData);
          console.log({authData});
        } else {
          setAuthState(null);
          console.log("authData is null");
        }      
    } catch (err) {
      console.log("Caught Auth Exception", err);
      setAuthState(null);
    }
  };
  // Update AsyncStorage & context state
  const setAuth = async (auth: TokenDetails | null) => {
    console.log("Try Setting Authentication")
    if (auth === null) {
      console.log("Setting Authentication to null")
      await AsyncStorage.removeItem('auth');
      setAuthState(auth);
    }
    else {
      try {
        console.log('Set Authentication', {auth});
        await AsyncStorage.setItem('auth', JSON.stringify(auth));
        // Configure axios headers
        configureAxiosHeaders(auth.access_token);
        setAuthState(auth);
        console.log('Setting done.')
      } catch (error) {
        console.log('Caught Auth Exception', error);
      }
    }
  };
  useEffect(() => {
    getAuthState();
  }, []);
  return (
    <AuthContext.Provider value={{auth, setAuth}}>
      {children}
    </AuthContext.Provider>
  );
};
export {AuthContext, AuthProvider};
```
3. Using it in sign in page when sign in is successful.
```tsx
await setAuth(data);
```
calling this function will update the Context State and provide the state to other components. 

While in step 1, we have automatically have the state replaced when setAuth is called with new props. Due to the configuring of default axios options, it automatically use the latest available token provided in Auth Context State.

That's all for today, I didn't explain the code in step 2 because the code is readable enough already.
Thank You for reading this post.