I am trying to use the MSAL library within REDUX for auth but having some trouble. When I make a react only app and do the same thing, I get the access token successfully but trying to use it in REDUX, I always get a timeout when trying to fetch the access token.
function Auth() {
var userAgentApplication = new Msal.UserAgentApplication(*my app id*, null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
});
return new Promise((resolve, reject) => {
console.log('inside the promise');
userAgentApplication.loginPopup(["user.read"]).then((token) => {
console.log("Successfully got id token");
console.log("first token: ", token);
console.log(userAgentApplication.getUser().name);
userAgentApplication.acquireTokenSilent(["user.read"]).then((token) => {
resolve(token);
}, function(error) {
reject(error);
});
}, function (error) {
reject(error);
});
});
}
This is the code I have but I always get the following error Token renewal operation failed due to timeout: null
When I try to do this in plain HTML or react only app, it works perfectly. Any sort of help would be highly appreciated.
see if adding a 'catch' and if condition helps to identify the problem.
function Auth() {
return new Promise((resolve, reject) => {
const userAgentApplication = new Msal.UserAgentApplication(*my app id*, null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
});
console.log('inside the promise');
userAgentApplication.loginPopup(["user.read"])
.then((token) => {
console.log("Successfully got id token");
console.log("first token: ", token);
console.log(userAgentApplication.getUser().name);
if (userAgentApplication.getUser()) {
userAgentApplication.acquireTokenSilent(["user.read"])
.then((token) => {
resolve(token);
})
.catch((error) => {
reject(error);
});
} else {
reject("User not logged in");
}
})
.catch((error) => {
reject(error);
});
});
}
Related
I've got a react front end that performs some actions. The relevant axios requests look like so:
const login = async () => {
await Axios.post('http://localhost:8000/login', {
username: username,
password: password,
}).then((response) => {
console.log("login response: ", response);
window.location.href = "http://localhost:3000/";
}).catch(err => {
alert(err.response.data);
});
};
// run on first render to see if user session is still active - remove console log later
useEffect(() => {
Axios.get("http://localhost:8000/isLoggedIn").then((response) => {
console.log("isLoggedIn resonse: ", response);
if (response.data.loggedIn === true) {
setLoginStatus(`Logged in as ${response.data.user}`);
}
})
}, [])
const Logout = async () => {
try {
await Axios.get('http://localhost:8000/logout').then((response) => {
console.log(response);
window.location.href = "http://localhost:3000/login";
}).catch(err => {
alert(err);
});
} catch (error) {
alert(error)
}
};
I keep having to press log out twice to actually log my user out. The logout route runs before the "isLoggedIn" route, according to my network tab. And it's successful, too. Here are the isLoggedIn and logout routes in my express backend:
export function isLoggedIn( req: any, res: any) {
if (req.session.user) {
// if our session already has a user, send true to the frontend
// frontend runs this get login on first render, so will have user data if cookie has not expired.
res.send({loggedIn: true, user: req.session.user})
} else {
res.send({loggedIn: false});
}
}
export function logout(req: any, res: any) {
if (req.session) {
req.session.destroy( (err: any) => {
if (err) {
res.status(400).send('Unable to log out');
} else {
res.send("Logout successful");
}
});
} else {
res.end();
}
}
I'm getting a successful logout. I just cannot figure out why I need to hit the logout button twice on the frontend to actually destroy the session and log the user out? Is there something timing related that I may be missing here?
I'm using React.useEffect() to retrieve the users list.
React.useEffect(() => {
dispatch(UsersActions.creators.fetchingUsersAction());
UsersApi.methods.getUsers().then(
(res) => {
dispatch(UsersActions.creators.fetchUsersSuccessAction(res.data));
},
(e) => {
dispatch(UsersActions.creators.fetchUsersErrorAction());
}
);
}, [dispatch]);
On this example, fetchingUsersAction is used to set "loading" to true, and fetchUsersErrorAction to false. This works fine, except when the request fails due to token expiration.
ApiClient.interceptors.response.use(
function (response) {
return response;
},
function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refresh = JSON.stringify({
refreshToken: localStorage.getItem("refresh"),
});
AuthenticationApi.methods.refresh(refresh).then((res) => {
if (res.data.accessToken) {
localStorage.setItem("token", res.data.accessToken);
}
ApiClient.defaults.headers.common["Authorization"] =
"Bearer " + res.data.accessToken;
originalRequest.headers["Authorization"] =
"Bearer " + res.data.accessToken;
return ApiClient(originalRequest);
});
}
return Promise.reject(error);
}
);
This is sending a request to generate a new token and the previous request, but since the first request failed, the useEffect is going to the error section, making the "loading" false and showing the users list based on the previous state. What is the best way to deal with this problem?
Thanks
You should create an Async fucntion inside useEffect hook and use await to wait for the response, then call the function. Here is one example:
useEffect(() => {
const getRoles = async () => {
await authService.roles().then((res) => {
//Do your stuff.
console.log(res);
}).catch((error) => {
console.log(`'Catching the error: '${error}`);
});
};
//Call the recent created function.
getRoles();
}, []);
Your interceptor looks good to me.
I am trying to write login code, but this firebase get function is refraining me to do so. I am unable to call any function (except alert), within this get function. Navigating to another component also does not work here. I know I have to use async/await keywords but I dont know how to. Can someone please help me with this?
Pasting the code below.
navigate() {
alert("Aya");
}
login() {
const { uname } = this.state;
const { password } = this.state;
var userid = "";
var data;
if (uname && password) {
firebase
.auth()
.signInWithEmailAndPassword(uname, password)
.then(async user => {
userid = await firebase.auth().currentUser.uid;
await db.collection("Users").doc(userid)
.get()
.then(function (doc) {
if (doc.exists) {
data = doc.data();
alert(JSON.stringify(data.role));
if (data.role === "Company Admin") {
logged = true;
alert("Yahoo");
this.navigate();
}
else {
logged = false;
}
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function (error) {
console.log("Error getting document:", error);
});
})
.catch(error => {
alert(error);
this.setState({ error });
});
if (logged) {
alert(logged);
}
else {
alert("Nope");
}
}
else {
alert("Enter all fields data");
}
}
Don't use normal function, you are going to lose the context of this. The this in the callback function is not pointing to your class. So this.navigate() line of code won't work
.then(function (doc) {
As a solution, Use arrow function.
...
.then((doc) => {
...
I have set up Auth0 using custom inputs with react and hooks. I am trying to protect my routes so that the user directs where they are based on our API and what information is provided back from that. In order to do that I am setting the user data inside of the setSession, but the item is coming back undefined/ has to do with auth0Client.client.userInfo not finishing before setSession function is done.
const setSession = async authResult => {
const expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
)
localStorage.setItem('access_token', authResult.accessToken)
localStorage.setItem('id_token', authResult.idToken)
localStorage.setItem('expires_at', expiresAt)
localStorage.setItem('auth', true)
setAuthenticated(true);
await auth0Client.client.userInfo(authResult.accessToken, (err, user) => {
localStorage.setItem('user', JSON.stringify(user))
setUser(JSON.stringify(user));
})
}
I call setSession in handle authentication:
const handleAuthentication = () => {
console.log("auth0Client", auth0Client);
if (typeof window !== 'undefined') {
auth0Client.parseHash(async (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
await setSession(authResult);
history.replace('/');
} else if (err) {
console.log(err)
return err;
}
})
}
}
I have tried return the value and calling it this way instead:
await setSession(authResult).then(
() => history.replace('/')
)
Either way, the first time it accesses the route that I want it has a null value for user.
I have put a complete mock repo here. You can create accounts and if you create extra accounts use the same email with +modifier/ example test+1#test.com
https://github.com/Sbphillips19/ManualAuth0Flow
I'm sure it's something simple, I have just tried probably 100 different ways over and over again and can't see it.
I'd give Jim and Jfriend00 the answer, but since it's a comment and I can't here is the answer using Bluebird:
await new Bluebird(function (resolve, reject) {
auth0Client.client.userInfo(authResult.accessToken, (err, user) => {
if (err) return reject(err)
resolve(user);
})
}).then(
data =>
{
localStorage.setItem('user', JSON.stringify(data))
setUser(JSON.stringify(data));
}
)
It looks like auth0 has talks about converting all their functions to be able to use async await, but for now bluebird will do!
I have a logging function which logs errors. When an Ajax Request fails with a non JSON data type, the log method should log it, however, we are getting the mutated error as the attached screenshot shows. I am trying to call this log action within a service.
Code
...
import {log} from '../actions/LoggingActions';
...
export default function request(url, opts, dispatch, type = 'application/x-www-form-urlencoded') {
...
return new Promise((resolve, reject) => {
$.ajax(args).then((data) => {
dispatch(httpEndRequest([url, opts, dispatch]));
resolve(data);
}).fail((jqXHR, textStatus, errorThrown) => {
const error = (jqXHR && jqXHR.responseJSON) ?
jqXHR.responseJSON.message :
'Error Making Request';
dispatch(httpFailRequest([url, opts, dispatch], error));
try {
reject(JSON.parse(jqXHR.responseText));
} catch (e) {
console.log(jqXHR.responseText, jqXHR, error);
reject(error);
dispatch(log('Received data is not in JSON format', {requestUrl: url}, {result: e, response: jqXHR, status: textStatus, error: errorThrown}, 'error'));
}
});
});
}
Instead of using jQuery with React, Use axios or fetch (Promise based HTTP clients). I personally prefer axios.
To use axios, do
npm install axios --save. Then
import axios from 'axios';
return new Promise((resolve, reject) => {
axios.get(url, {
params: params
})
.then((response) => {
resolve(response.data);
})
.catch((error) => {
// error.response.status
dispatch(log(error));
reject(error);
});
});