Last active
December 13, 2022 17:31
-
-
Save ahnafnafee/0e0e29180e565c6f9a3bbd41fdbe9b28 to your computer and use it in GitHub Desktop.
Auth Provider that auto refreshes auth tokens when they expire
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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