Skip to content

Instantly share code, notes, and snippets.

@ahnafnafee
Last active December 13, 2022 17:31
Show Gist options
  • Save ahnafnafee/0e0e29180e565c6f9a3bbd41fdbe9b28 to your computer and use it in GitHub Desktop.
Save ahnafnafee/0e0e29180e565c6f9a3bbd41fdbe9b28 to your computer and use it in GitHub Desktop.
Auth Provider that auto refreshes auth tokens when they expire
import { UserTokens } from "../models/player";
import { useAppDispatch, useAppSelector } from "../store/hooks";
import {
NotificationService,
PushNotificationPayload,
} from "../services/notification-service";
import { authActions, authThunks } from "../store/slices/auth";
import { config } from "../constants/config";
import * as SecureStore from "expo-secure-store";
import axios from "axios";
import React, { createContext, useCallback, useEffect, useState } from "react";
interface AuthContextState {
logout: () => Promise<void>;
}
const AuthContext = createContext<AuthContextState>({} as AuthContextState);
const whitelistedPaths = [
"/players/signup",
"/players/signin",
"/players/refresh",
"/players/confirm",
"/players/forgetPassword",
"/players/verifycode",
"/players/resetPassword",
];
interface AuthProviderProps {
children: React.ReactNode;
}
const getJWTTokens = async () => {
console.log("AuthProvider: getJWTTokens called");
const tokens = await SecureStore.getItemAsync("tokens");
if (tokens) {
const jwt: UserTokens = JSON.parse(tokens);
return jwt;
}
return undefined;
};
const AuthProvider = ({ children }: AuthProviderProps) => {
const dispatch = useAppDispatch();
const { authState } = useAppSelector((state) => state.auth);
// States
const [loaded, setLoaded] = useState(false);
// for multiple requests
let isRefreshing = false;
let failedQueue: any[] = [];
const refreshTokenFunc: any = null;
const processQueue = async (error: any, token = null) => {
console.log("processQueue");
failedQueue.forEach((prom) => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
const logout = async () => {
console.log("Logout called!");
dispatch(authActions.resetAuthState());
dispatch(authThunks.signOut({}));
dispatch(authActions.resetState());
dispatch({ type: "LOGOUT", payload: undefined });
};
const loadJWT = useCallback(async () => {
const jwt = await getJWTTokens();
jwt && dispatch(authActions.setAuthState(jwt));
setLoaded(true);
}, []);
useEffect(() => {
if (!loaded) {
loadJWT();
}
}, [loaded]);
axios.interceptors.request.use(
async (config) => {
if (authState.authorizationToken && !config.headers!.Authorization) {
/*
* Does not add Bearer Token to auth URLs
*/
if (!whitelistedPaths.some((path) => config.url?.includes(path))) {
config.headers!.Authorization =
"Bearer " + authState.authorizationToken;
}
}
return config;
},
async (error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
async (response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
console.log("Failed Request", originalRequest);
if (isRefreshing) {
return new Promise((resolve, reject) => {
if (
typeof originalRequest.url === "string" &&
originalRequest.url.includes("players/refresh")
) {
console.log("players/refresh fail");
logout();
}
failedQueue.push({ resolve, reject });
})
.then((token) => {
originalRequest.headers["Authorization"] = "Bearer " + token;
return axios(originalRequest);
})
.catch((err) => {
return Promise.reject(err);
});
}
originalRequest._retry = true;
isRefreshing = true;
const jwt = await getJWTTokens();
if (jwt !== null) {
return new Promise((resolve, reject) => {
axios
.post<UserTokens>(`${config.api.baseUrl}/players/refresh`, {
tokens: { refreshToken: jwt?.refreshToken },
})
.then(async ({ data }) => {
dispatch(authActions.setAuthState(data));
originalRequest.headers["Authorization"] =
"Bearer " + data.authorizationToken;
processQueue(null, data.authorizationToken as any);
resolve(axios(originalRequest));
})
.catch((err) => {
logout();
processQueue(err, null);
reject(err);
})
.finally(() => {
isRefreshing = false;
});
});
}
return Promise.reject(new Error("No token found"));
}
return Promise.reject(error);
}
);
return (
<AuthContext.Provider
value={{
logout,
}}
>
{children}
</AuthContext.Provider>
);
};
export { AuthContext, AuthProvider };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment