After redirect to login page, show Toaster message - reactjs

how to display this toster message after reloading the page in React js.
const store = configureStore();
let isLogout = false;
const handleResponse = (response) => {
if (response && response.data && response.data.status && response.data.status.code === 551 && !isLogout ) {
isLogout = true;
store.dispatch(actions.logout()).then(() => {
window.location.reload();
handleErrorMessageToastr("Authentication Fail")
});
}
return response
}

Related

How to stop React from finishing render when axios.interceptors.response handles the error?

I am working on a react app and I use tokens and refresh tokens for authentication. Whenever the backend returns a 401, the axios.interceptors.response picks it up and tries to refresh my token. If it succeeds, it will reinitiate the original call with the updated headers. See the code below:
// To avoid infinite loops on 401 responses
let refresh = false;
axios.interceptors.response.use(
(resp) => resp,
async (error) => {
if (error.response.status === 401 && !refresh) {
refresh = true;
const response = await axios.post(
"/api/auth/refresh",
{},
{ withCredentials: true }
);
if (response.status === 200) {
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${response.data["accessToken"]}`;
return axios(error.config);
}
}
refresh = false;
return error.response;
}
);
This by itself works great, but not in combination with the code below in one of my components:
const [pages, setPages] = useState();
const [error, setError] = useState();
const navigate = useNavigate();
useEffect(() => {
async function fetchInfo() {
const response = await getMyPages();
if (response.status === 200) {
setPages(response.data);
}
else if (response.status === 401) {
setError(t("error.notAuthorized"));
navigate(`/login`, { replace: true });
}
// Any other error
else {
setError(t("error.unexpected"));
}
}
fetchInfo();
}, [t, navigate]);
// getMyPages function
export async function getMyPages() {
try {
const result = await axios.get(`/api/user/mypages`);
return result;
} catch (err) {
return err.response;
}
}
The problem is that the user is navigated to /login before the new request (with refreshed token) is made and finished. So when the new request finishes, I am not in the original component anymore and I can no longer update the pages state.
Any suggestions on how to handle this?
useEffect(() => {
let isMounted = true;
const controller = new AbortController();
const getMyPages = async () => {
try {
const response = await axios.get(`/api/user/mypages`, {
signal: controller.signal
});
isMounted && setPages(response.data);
} catch (err) {
navigate(`/login`, { replace: true });
}
}
getMyPages();
return () => {
isMounted = false;
controller.abort();
}
}, [])

Why is my firebase user returning null inside props with reactjs?

I am using the following code to obtain a bearer token from firebase which is then sent in the authorization header to the backend.
const user = firebase.auth().currentUser
const idToken = await user.getIdToken()
It works well on most of my reactjs requests, however on one page the request is inside props and i am receiving the following error:
Uncaught (in promise) TypeError: user is null
Sometimes when i refresh the page it works, but 99% of the time it fails which is very odd.
The code i am using is below:
const Routing = (props) => {
let uid = localStorage.getItem("account-info");
let { id } = useParams();
const loadBlockchainData = async () => {
const { dispatch } = props;
if (id === null || id === undefined) {
id = "test";
}
const user = firebase.auth().currentUser
const idToken = await user.getIdToken()
var res = await axios.post(backUrl + "account/load_balance", {
uid: uid,
id: id
},
{
headers: {
Authorization: 'Bearer ' + idToken
}});
if (res.data === null) {
document.location.href = "/logout"
return;
}
else {
localStorage.setItem("account-address", res.data.address);
dispatch(web3AccountLoaded(res.data.address));
if (res.data.token_flag && res.data.exchange_flag) {
await dispatch(setLoginUserName(res.data.name));
await dispatch(setLoginUserEmail(res.data.email));
if (res.data.balance !== null) {
// redacted
}
}
else {
Swal.fire({
// redacted
});
return;
}
}
};
useEffect(() => {
if (uid) {
async function fetchData() {
await loadBlockchainData();
}
fetchData();
}
}, [uid]);
return (
<>
{uid ? (
<div>
{
props.contractsLoaded ? <Exchange id={id} /> : <></>
}
</div>
) : (
<Login />
)}
</>
);
};
How can i fix this?

Chrome show a sign up alert even if there is no alert in the code

I'm developing a web app using react, I'm facing an annoying bug.
When I log in with incorrect credentials, an alert appears on the screen asking me to enter my credentials, the problem is that this alert is displayed only using chrome and that there is no alert on the code ...
I leave you the photo of the alert below
This is my Login Api
const doLogin = (evt: FormEvent) => {
evt.preventDefault();
setErrorMessage('');
if (userData.username !== '' && userData.password !== '') {
setIsProcessingLogin(true);
doPostData('/auth/login', {}, {
auth: {
username: userData.username,
password: userData.password
}
})
.then((result: LoginResponse) => {
const user = TokenDecode(result.tokens.accessToken, result.tokens.refreshToken);
if (user) {
dispatch(setAuthToken(user));
}
navigate("/companies");
})
.catch(e => {
let message = '';
if(e?.response?.status === 400){
message = t("error.badRequest");
}
if(e?.response?.status === 500){
message = t("login.error.internalServer");
}
if(e?.response?.status === 401){
message = t("login.error.unauthorized");
}
console.error("ERROR", message);
setErrorMessage(message);
}).finally(() => {
setIsProcessingLogin(false)
});
} else {
setErrorMessage(t("login.error.credentials"));
}
};
Thank you all.

Redux state is not uniform across all tabs

Im facing an issue now with my authentication system that is based on django-simplejwt and redux-saga.
Whenever i click on the log out button , the following saga is run:
export function* workerLogout(action) {
const r_token = localStorage.getItem('refresh_token');
yield call(() => axios.post('http://127.0.0.1:8000/api/blacklist/', {
"refresh_token": r_token
})) //<--- API end-point to blacklist the tokens
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token'); // <--- removing the tokens
yield call(() => axiosInstance.defaults.headers['Authorization'] = null ); //<--- setting the headers to null
yield put({ type: "LOGOUT_SUCCESS" }) //<---- This will go to the reducer to set the states to null
}
Which will then acccess the reducer here:
const authLogout = (state, action) => {
return updateObject(state, {
access_token: null,
refresh_token: null,
isAuthenticated: false,
group: {},
username: null,
});
}
This works and my redux tool bar shows that the state of the tab is indeed cleared:
isAuthenticated(pin):false
access_token(pin):null
refresh_token(pin):null
group(pin):
username(pin):null
My interceptor:
axiosInstance.interceptors.response.use(
response => {
return response
},async error => {
const originalRequest = error.config;
// Prevent infinite loops
if (error.response.status === 401 && originalRequest.url === '/token/refresh/') {
store.dispatch({type: "LOGOUT"});
return Promise.reject(error);
}
if (error.response.data.code === "token_not_valid" &&
error.response.status === 401 &&
error.response.statusText === "Unauthorized")
{
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken){
const tokenParts = JSON.parse(atob(refreshToken.split('.')[1]));
// exp date in token is expressed in seconds, while now() returns milliseconds:
const now = Math.ceil(Date.now() / 1000);
if (tokenParts.exp > now) {
console.log('access token is expired , attempting refresh')
try {
const response = await axiosInstance
.post('/token/refresh/', { refresh: refreshToken });
localStorage.setItem('access_token', response.data.access);
localStorage.setItem('refresh_token', response.data.refresh);
axiosInstance.defaults.headers['Authorization'] = "JWT " + response.data.access;
originalRequest.headers['Authorization'] = "JWT " + response.data.access;
axiosInstance.get('group/get/').then(groups => {
store.dispatch({type: "LOGIN_SUCCESS" , payload:[response.data.access, refreshToken, groups.data, jwt_decode(response.data.access).username]});
})
return axiosInstance(originalRequest);
}
catch (err) {
store.dispatch({type: "GET_ERRORS" , error : err.response.data})
}
}else{
store.dispatch({type: "LOGOUT"});
}
}
}
store.dispatch({type: "GET_ERRORS" , error : error.response.data})
return Promise.reject(error);
}
);
However , if let's say a second tab was open at the point of the logout , i can still access my server's resources from the second tab. Upon closer inspection of the redux tool bar , it seems the state of the second tab is not affected by the first tab.
Is this an expected behavior? If so how do i make it in synced across all tabs as this seems like a bad way to secure the application.
I would suggest to store your Accesstoken and refreshtoken in Browser's local storage. Clearing accesstoken from redux store will not impact the other tabs.
On your Login success you can store accesstoken as
static saveToken(token, refreshToken){
window.localStorage.setItem('token', token);
window.localStorage.setItem('refresh_token', refreshToken);
}
On your logout success you can just remove the token by
static clearAccessToken(){
window.localStorage.removeItem('token');
window.localStorage.removeItem('refresh_token');
}
In your request Interceptor you can access the accesstoken/refreshtoken by
static isAuthenticated(){
return window.localStorage.token != null;
}
static getAccessToken(){
return window.localStorage.token;
}
static getRefreshToken(){
return window.localStorage.refresh_token;
}
By doing this way when you logout from one tab it will logged out from your browser.
You can contorl the logout error in authInterceptor
axios.interceptors.response.use((response) => {
return response
}, (error) => {
const { config, response: { status } } = error;
const originalRequest = config;
if (status === 401) {
if(config.url.endsWith('oauth/token') || config.url.endsWith('users/logout')){
return Promise.reject(error);
}
if (!this.isAlreadyFetchingAccessToken) {
this.isAlreadyFetchingAccessToken = true;
var refreshTokenValue = AuthService.getRefreshToken();
refreshToken(refreshTokenValue).then((token) => {
this.isAlreadyFetchingAccessToken = false
if(token && token.data && token.data.access_token){
AuthService.saveToken(token.data.access_token, token.data.refresh_token);
this.onAccessTokenFetched(token.data.access_token);
}
}).catch(err => {
this.isAlreadyFetchingAccessToken = false;
this.subscribers = [];
this.history.push('/login');
return Promise.reject("Auth error");
})
}
const retryOriginalRequest = new Promise((resolve) => {
this.addSubscriber(access_token => {
originalRequest.headers.Authorization = 'Bearer ' + access_token
resolve(axios(originalRequest))
})
})
return retryOriginalRequest
}
console.log("Unknown error :" + error);
let errorMessage = '';
if(error.response && error.response.data) {
return Promise.reject(error.response.data);
} else {
return Promise.reject({
code: UNKNOWN_ERROR,
message: "Oops, Unable to complete the request"
});
}
})
This is my request interceptor
axios.interceptors.request.use(function (config) {
if(AuthService.isAuthenticated()){
if(config.url.startsWith(apiUrl) && !config.url.startsWith(OAUTH_URL)){
config.headers.Authorization = 'Bearer ' + window.localStorage.token;
}
}
return config;
});
In the above code I am reading my access token directly from localstorage. Upon logout the Authorization header by default becomes invalid and throws 401 now In your reponse interceptor you can redirect to logout page when refresh token fails. Hope this will help.

reactjs call a function

I have a class API with a function which I wish to call in my component.
export default function(authentification){
axios.post('/login',params)
.then(response=> {
localStorage.setItem(ACCESS_TOKEN, response.headers.authorization);
localStorage.setItem(IS_AUTHENTICATED, true);
this.setState({isAuthenticated:true});
})
.catch(error =>{
const statusCode = error.response.status;
if(statusCode === 401)
errors.authentification = true;
else if (statusCode === 404)
errors.resource = true;
else
errors.server = true;
this.setState({errors});
});
}
I do not arrive in found how to call this function in my component and as to get back its result to put it in setState
First: separate your setState from your api helper method like:
export default function(authentification){
axios.post('/login',params)
.then(response=> {
localStorage.setItem(ACCESS_TOKEN, response.headers.authorization);
localStorage.setItem(IS_AUTHENTICATED, true);
return {status: "ok"}
})
.catch(error =>{
const statusCode = error.response.status;
if(statusCode === 401)
errors.authentification = true;
else if (statusCode === 404)
errors.resource = true;
else
errors.server = true;
return {status: "error", errors}
});
}
Or if you want to use a async/await syntax in your api method:
const authentification = async (params) => {
const response = await axios.post('/login',params);
const statusCode = response.status;
if (statusCode >= 200 && statusCode < 300) {
localStorage.setItem(ACCESS_TOKEN, response.headers.authorization);
localStorage.setItem(IS_AUTHENTICATED, true);
return {status: "ok"}
}
let errors = {};
if (statusCode === 401) {
errors.authentification = true;
}
else if (statusCode === 404) {
errors.resource = true;
}
else {
errors.server = true;
}
return {status: "error", errors}
}
export default authentification;
Then call you api function inside of componentDidMount() lifecycle method of your Component like:
...
componentDidMount = async () => {
let resp = await helperfunction();
if (resp.status === "ok") {
this.setState({isAuthenticated:true});
return;
}
this.setState({resp.errors});
}
...
in a file.js create all function you want and export it
let xxx = (authentification) => {
axios.post('/login',params)
.then(response=> {
localStorage.setItem(ACCESS_TOKEN, response.headers.authorization);
localStorage.setItem(IS_AUTHENTICATED, true);
this.setState({isAuthenticated:true});
})
.catch(error =>{
const statusCode = error.response.status;
if(statusCode === 401)
errors.authentification = true;
else if (statusCode === 404)
errors.resource = true;
else
errors.server = true;
this.setState({errors});
});
}
export default xxx;
then import it where you wanna use it like so --> import xxx from 'path';
I restructured my code. I do not know if it is the good architecture to employee. But I does not obtain answer in my component yet
Classe AuthentificationAPI :
import axios from "axios";
const AuthentificationAPI = {
login(params){
return axios.post('/login',params);
},
}
export {AuthentificationAPI as default}
AuthentificationService :
import { ACCESS_TOKEN, IS_AUTHENTICATED } from "constants/constants.js";
import AuthentificationAPI from "api//authentificationAPI.js";
const AuthentificationService = {
login(params){
let errors = {};
AuthentificationAPI.login(params).then(response => {
localStorage.setItem(ACCESS_TOKEN, response.headers.authorization);
localStorage.setItem(IS_AUTHENTICATED, true);
return {isAuthenticated:true};
})
.catch(error => {
const statusCode = error.response.status;
if(statusCode === 401)
errors.authentification = true;
else if (statusCode === 404)
errors.resource = true;
else
errors.server = true;
return errors;
});
},
}
export {AuthentificationService as default}
Call in my component :
let resp = AuthentificationService.login(params);
console.log(resp);

Resources