history.push('/somePath') not working, confused by async function - javascript

I am working on a React/Redux excercise. It works for the most part, but I'm having trouble with the useHistory hook. I have a login form that receives a username and password, then it sets a token in the browser local storage. login() is a POST fetch request that works and sets the storage successfully. Then I have a Home component that checks for the token. If there's a token, the page should load, if there's no token, it should redirect to the login page. The problem is history.push() is executing before my token is set, thus sending me back to the login form. To make things even worse, the token actually gets set, but only AFTER the -undesirably early- redirect to the login form. If I try to log in again, it works, but only because the token was already there. So to make the question short, how can I make history.push() wait for the token before redirecting?
Here's my form submission function:
const handleSubmit = e => {
e.preventDefault();
props.login(state.userName, state.password); // This sets the token
history.push('/home'); // This is happening before the token is set
}

if login is an async function you can try this
const handleSubmit = e => {
e.preventDefault();
props.login(state.userName, state.password)
.then(history.push('/home')); //waits until promise resolves
}

Related

I have a conditional component that renders after useeffect sets the state, however it on happens if the useeffect goes into a "if" block

I'm currently working on a Spotify api react project, for the app I require the user to login to their Spotify account so I can make calls to the api to display their data. When the user first uses the app they are greeted by a login popup redirecting them to a Spotify login page, and once they have logged in; they are redirected with their bearer token back to the app. I then have a useEffect hook take the token and store it in local storage. However the issue is when the user comebacks to the app the login modal is displayed again. I want it so that the login only ever appears when there is no token stored in local storage. This is the useEffect hook code:
useEffect(() => {
const hash = window.location.hash;
let token = window.localStorage.getItem('token');
let lastLogin = window.localStorage.getItem('lastLogin');
if (!token && hash) {
token = hash.substring(1).split("&").find(elem => elem.startsWith("access_token")).split("=")[1]
window.location.hash = ""
window.localStorage.setItem('token', token);
window.localStorage.setItem('lastLogin', Date.now());
}
setToken(token);
setLastLogin(lastLogin)
}, [])
I then have a conditional in my jsx to see if the token is a falsy value to show the popup. I'm also slightly confused as to how after the user refreshes the webapp. Why doesn't the popup reappear as the token is set in the useEffect hook which happens after a rendering.
I have tried to create other states and set them on depending if the token was in local storage or not. I've also attempted to put into my conditional to check if the local storage token is empty or not. However they all still result in the login popup reappearing

useEffect with function that update the state as dependency leads to infinite loop

Code Sandbox: https://codesandbox.io/s/new-breeze-d260k
The code:
This is a simple auth app
When you click Login, the accessToken is exchanged and stored in memory while the refreshToken is stored in localStorage.
While the accessToken is valid (here a timestamp), the Home page shows the protected content
At every page reload (i.e App initialization), the refreshToken is sent to the server and if it is valid, a new accessToken is exchanged.
The problem:
To refresh the token on App initialization, I have an onRefreshToken() function in a useEffect to be executed once (I wanted to pass an empty array as dependency but typescript/eslint complains and suggest that onRefreshToken() should be the dependency. I admit that I don't understand why this is recommended to have always a dependency when you want the effect to be executed once).
Once the token is renewed, I store the accessToken and the user profile in their respective context.
Infinite re-render loop begins. On my local server, this is due to setProfile() and not setAccessToken(). However I don't understand why.
Side note
The above issue is the main issue of this post but on a side note, the login/logout process don't sync between tabs so if you have any idea why, I would be happy to hear your advice on this point as well.
Happy new year
One way to fix this would be to check to see if you have an access token and only refresh it if you need to:
export default function App() {
const { accessToken } = useAuthContext();
const { onRefreshToken, onSyncLogin, onSyncLogout } = useAuth();
useEffect(() => {
const refresh = async () => {
await onRefreshToken();
};
!accessToken && refresh();
}, [onRefreshToken, accessToken]);

Keycloak js not executing THEN after init

I am trying to integrate Keycloak login into my React app and I'm trying to get the JWT from keycloak. Here is the code:
const [keycloakState, setKeycloakState] = useState<any>();
const login = () => {
const keycloak = Keycloak("/keycloak.json");
keycloak.init({onLoad: 'login-required'}).then(authenticated => {
console.log('kk', keycloak)
console.log('at', authenticated)
setKeycloakState({ keycloak: keycloak, authenticated: authenticated });
}).catch(err => {
alert(err);
});
console.log('log after')
}
The login function is triggered when a button is clicked. It redirects properly to keycloak, I can log in and I am properly redirected to the app. The problem is that after the redirect back to the app with proper login the code in the then part of the chain is not executed, and even the 'log after' does not appear in the logs. The catch error part works fine.
Why might this be happening? I have keycloak-js added to my project.
I used to face this problem before. The way that I passed is separating the "init" function and then invoke it later.
Here is my example on jsFiddle: 'https://jsfiddle.net/gzq6j3yu/1/'
Our solution was to use the functions onAuthSuccess and onAuthError avaliable on the KeycloakInstance keycloak-js provides. (The documentation around this is a little shaky but you can find them if you check out the source code.) As the names imply these functions get called when an auth attempt is successful or unsuccessful, respectively.
note: in the following snippets this._instance replaces OP's keycloak constant.
Basic code snippet:
import Keycloak from 'keycloak-js';
...
// pulled from a class's init function from a custom Keycloak helper class so won't translate one for one but you get the point.
this._instance = Keycloak(configObject);
this._instance.onAuthSuccess = () => {
// code to execute on auth success
};
this._instance.onAuthError = () => {
// code to execute on auth error
};
this._instance.init(initOptions)
...
We also had a getter to get the token on the KeycloakInstance (or empty string) on the same class. This is an easy way to refer to the token in your code to check if it actually exists, etc. Here's what that'd look like inside the class.
get token() {
return this._instance ? this._instance.token : '';
}
Hope this can help out some folks.
I think the reason your fulfilled callback is not executed is the way your app interacts with Keycloak. You initialize the Keycloak-Adapter with onLoad: 'login-required' which will redirect the user to Keycloak - which means the Javascript execution is interrupted at this point. Keycloak will redirect the user back to your app and since you wrapped the Keycloak-Adapter in a function which is only executed when a button is clicked, the promise callback is not executed.
Simple example:
// do this on page load
keycloak.init({onLoad: 'login-required'}).then((authenticated) => {
console.log('authenticated', authenticated)
})
You will not see a "authenticated", false in your console when you open up your app. If the user is not authenticated, he will be redirected (so no chance to execute that callback). If he then comes back and is authenticated, the callback will be executed and authenticated should be true.
If you want the user to click a button, a setup like this should work:
// do this somewhere early in your App or main.js and in a way that this code is executed on page load
const keycloak = new Keycloak(configuration);
keycloak.init({onLoad: 'check-sso'}).then((authenticated) => {
if (authenticated) {
// do what needs to be done if sign in was successful, for example store an access token
} else {
// handle failed authentication
}
}).catch(err => {
alert(err);
});
const login = () => { // this could be an on-click event handler
keycloak.login();
};
check-sso won't redirect the user to Keycloak if unauthenticated, so the user can trigger the login when needed.
Keep in mind that your JavaScript code will run twice and you have to cover both cases, first the user is not authenticated and needs to be redirected to Keycloak and a second time once the user comes back from Keycloak (then we should get the information that the user is authenticated in .then().

why is my firebase authentication display name not getting updated properly with react js?

I am trying to build firebase authentication for my react app..After I sign in I am trying to update the displayName and then redirect..On the redirected page I am trying to greet the user by fetching the display name saved while signing up with firebase..This page works properly immediately after I redirect but if I reload this page then it is not able to show the displayName and throws this error:
TypeError: Cannot read property 'displayName' of null
This is the function which gets triggered when signup button is clicked..
const signup = async () => {
try{
await firebaseApp.auth().createUserWithEmailAndPassword(email, password)
await firebaseApp.auth().currentUser.updateProfile({displayName:username})
console.log(firebaseApp.auth().currentUser)
if (!firebaseApp.auth().currentUser){
setLoading(true)
}
history.push('/home')
}catch (error){
alert(error.message)
}
}
This is the JSX of the page which is being redirected to by signup page:
<div className="greetings">
Good Evening {firebaseApp.auth().currentUser.displayName}
</div>
Why is this issue happening and how to resolve it?
firebaseApp.auth().currentUser is always null when a page first loads. It won't contain a User object until some time later, after the SDK is able to load and verify the auth token for that user. Instead of using currentUser, you should set up an auth state observer as shown in the documentation. This observer will get invoked as soon as the User object is known.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
// ...
} else {
// User is signed out.
// ...
}
});
You can use the results of this observer function to know when the user is signed in or signed out over time. To learn more about how it works, read this blog post.

How should I handle JWT token refresh in my Redux app?

Our Redux application use JWT tokens for authentication. The access_token expires every 15 minutes and the refresh_token expires every 30 days. Both of them are provided by our API every time you log in and stored in the browser's local storage. If a secure endpoint receives a request with an expired token, it returns a 401 HTTP error.
Unfortunately, I don't know how to proceed to handle the refresh process without having a negative impact on the user. From a technical point of view, here is what I would like to achieve:
Action creator calls the API with an expired token
Client receives a 401 HTTP error
Client triggers a function that calls the API to obtain a new token (by providing the refresh token).
If the call fails (refresh_token is expired), prompt the user the re-enter its credentials to re-obtain both tokens then re-attempt the original request.
If the call succeeds, re-attempt the original request.
I would like to have a function that would handle the refreshing process and that would be called in the error handling portion of the action creator.
Here is what I have tried so far:
export function handleError(dispatch, current_func, error, handling) {
if(error.response) {
if(error.response.status === 401 && readToken("token") !== null) {
return attemptTokenRefresh(dispatch, current_func)
}
if(error.response.status === 422 && readToken("token") === null) {
return attemptTokenRefresh(dispatch, current_func)
}
}
return(handling())
}
export function attemptTokenRefresh(dispatch, on_success) {
let token = readToken("refresh_token");
let instance = axios.create({
headers: {"Authorization": token}
});
instance.post("api/refresh").then(response => {
if (response.data["token"]) {
storeToken("token", response.data["token"]);
on_success();
}
}).catch(error => {
//TODO: Allow user to sign back (prevent wiping the state)
});
}
dispatch refers to the dispatch function provided by Redux
current_func refers to the action creator
error refers to the error returned by the API
handling refers to the error handling function for other types of errors
Any help would be greatly appreciated :)

Categories

Resources