I used the following login with react/redux tutorial to build a signup / signin functionality into my React app, however I did not realize until recently that I now also need a reset-password / forgot-password functionality.
This feature is not a part of the tutorial at all, and I am simply wondering if anybody has any suggestions as to how I can go about this?
Let me know if I can share any info about my app that will help with this, or if there's a better place to post this type of question. I'm holding off on sharing more on the app as I think it's redundant given the info in the tutorial is nearly exactly how my signup / signin is setup.
Thanks!
After the user enters the proper credentials that you state (usually username, email, or both)
Make an api call to your backend that creates a password reset token. Store it in the database and, in one form or another, associate it with the user (usually it's the same database entry).
Send an email to the user with a link that has the password reset token embedded into it. Have a route in your react-router routes that will handle the url you link to.
Have the route mount a component that has a componentDidMount, which takes the token and makes an api to the backend to validate the token.
Once validated, open a ui element in the react component that allows the user to set a new password
Take the new password, password confirmation, and reset token and make an api call to the backend to change the password.
Delete the reset token in the backend after successful password change
// in your routes file
<Route path="/password_reset/:token" component={PasswordResetValidator}/>
//
class PasswordResetValidator extends React.Component {
state = {password: '', passwordReset: '', isValidated: false}
async componentDidMount() {
const response = await validatePasswordResetToken(this.props.token)
if (response.ok) {
this.setState({ isValidated: true })
} else {
// some error
}
}
handleSubmit = () => {
const { token } = this.props
const { password, passwordReset } = this.state
sendPasswordResetData({password, passwordReset, token})
// probably want some confirmation feedback to the user after successful or failed attempt
}
render() {
if(this.state.isValidated) {
return (
<div>
<input />
<input />
<button onClick={this.handleSubmit}>Set new password</button>
<div>
)
}
return // something while token is being validated
}
}
Obviously you need to make your own text input handlers. You should also have error handling, and good ui feedback to the user. But ultimately, that's all at your discretion.
Best of luck
Related
The nature of the sign-in flow with Google/Facebook is that after we login in the redirected page of Google, it comes back to our website's sign-in page.
The following code runs when the Google/Facebook login button is clicked:
fire.auth().signInWithRedirect(provider);
So, my current approach is that I check the Firebase user object using the onAuthStateChanged() function. If the user state is populated, I render a component, else if it is null, I render the component.
{user ? (
<Home />
) : (
<Signup />
)}
But the problem is that after logging in using Google or Facebook, the component is showing for some time (maybe 1-2 secs) and then rendering the component.
I want to render the component immediately after I login using Google redirect. What should I do?
google and facebook login system are asynchronous in nature so you shoud you async await method inside you code.
You should show a full loading state in the login screen when clicking on the signin button
So when the authentication starts show the user a loader,
and if fails stop the loader
you could do something like
if (authUser === undefined || isLoading) {
return <AuthLoader />;
}
return <LoginComponentContents/>
I would recommend to use for all authentication methods the onAuthStateChanged listener in auth. That way it doesn't matter what method you use. It will give you the user if someon is logged in and null if not. The code looks like this:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
Use that listener to define the state of your auth (if a user is signed in or not). You can also persiste the data in your state menegement to awoid flickering on page reloads. You can use the same method for email/password login. That way you have a single and simple solution for all Firebasse authentication methods.
For the time you await the Google or other provider redirect login I would recommend to set a loading flag and show a circular loading indicator. The login could always fail. I would not recommend to show pages that require a signed in user untill you get the response from authStateChanged as expected.
For Firebase v9 and above:
If using an AuthContext file, you can define your signInWithRedirect and getRedirectResult functions. Then, import those functions into your Signup page.
AuthContext:
//Log in user with provider. Redirects away from your page.
async function loginGoogle() {
await signInWithRedirect(auth, provider);
return
}
//Checks for provider login result, then navigates
async function getRedirect(){
const result = await getRedirectResult(auth);
if (result) {
navigate('/')
}
}
In your Signup page, call the login function on button click
//Sign in with Google.
async function handleGoogleLogin(e) {
e.preventDefault()
try {
setError('')
await loginGoogle()
} catch(error) {
setError('Failed to log in')
console.log(error)
}
}
Then, just place the getRedirect() function in your Signup component. This function will run when the page reloads from the google redirect, thus sending your user to the desired page.
//Checks for Google Login result.
getRedirect();
For me, it only worked when using this 2-step approach because when the provider redirect occurs, the async function appears to not finished as expected. So loginWithRedirect in one step, then getRedirectResult and navigate in a second step.
so I might have difficulty explaining this issue I am having, which I am not able to reproduce consistently. I have a React app on which I am using react-redux-firebase and that I thought I was successfully implementing to keep track of the user session.
My App.js file has the following bit or routing code as a sample (using react-router-dom):
<Route
path="/signin"
render={() => {
if (!isLoaded(this.props.auth)) {
return null;
} else if (!isEmpty(this.props.auth)) {
return <Redirect to="/posts" />;
}
return <Signin />;
}}
/>
This works correctly. I go to Signin component when user is not logged in or Posts when user is logged in. In Signin component I have this bit of logic that happens:
// sign the user in
this.props.firebase.login({
email: user.email,
password: user.password
}).then(response => {
// detect the user's geolocation on login and save
if (isLoaded(this.props.auth)) {
const navigator = new Navigator();
navigator.setGeoLocation(this.props.firebase);
}
// redirect user to home or somewhere
this.props.history.push('posts');
})
I am importing isLoaded like so:
import { firebaseConnect, isLoaded } from 'react-redux-firebase';
the condtional works fine, the user is logged in and then the isLoaded conditional happens - this is where the problem arises. With isLoaded true I would assume that the user and all the redux-firestore user properties are ready for use....but that is sometimes not the case. In navigator.setGeoLocation call I have this:
setGeoLocation(propsFirebase, cb) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
propsFirebase.auth().currentUser
.getIdToken(/* forceRefresh */ true)
.then((idToken) => {
...
});
}
)
}
}
At this point, propsFirebase.auth().currentUser is sometimes null (this originates in the parameter passed in navigator.setGeoLocation(this.props.firebase);). If I try the signin in all over again, then it works. I am not able to find any consistent way of reproducing.
I have noticed this in other components too. I am not sure if this is an issue that happens when my computer goes to sleep and I should restart the whole React process or what? has anyone seen similar issues? If so, what could I possibly be missing during checking user state in the routing?
If more code is necessary, let me know...
currentUser will be null with the user is not signed in. It will also be null when a page first loads, before the user's token has been loaded and a User object is available. You should use an auth state observer to get the User object if you want to act immediately after it is loaded asynchronously after page load.
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
You might also want to read for more detail: Why is my currentUser == null in Firebase Auth?
I use React Context API to store the information that a user is authenticated.
In development mode when I type in any URL that redirects to the 404 error page the context data is lost. When I navigate to a valid page a previously logged in user is not logged in any more.
EDIT: I just tested this with gatsby build and gatsby serve. A built gatsby site keeps the context when redirecting to 404 error page. But the context is still lost when navigating to completely different URL such as www.google.com.
Now my question is: How do I resupply the context with the login information without having the user be manually log in again?
Here is my AuthContextProvider wrapper class:
export class AuthContextProvider extends React.Component {
constructor(props) {
super(props);
this.state = { user: {} };
}
// ...
render() {
return (
<AuthContext.Provider value={{ getUser: this.getUser, setUser: this.setUser }}>
{this.props.children}
</AuthContext.Provider>
);
}
}
I wrap my whole app with the Context Provider in a root layout:
const RootLayout = ({ children }) => {
return (
<AuthContextProvider>
{children}
</AuthContextProvider>
);
}
React Context is about providing some data to one or more child components without having to pass the data down through intermediary components. There's no built-in mechanism for persisting state between page loads, so you'll need to reach for another tool for that.
If you haven't already implemented your authentication layer, you'll want to look into how that will work. There are a number of strategies for maintaining that state, even just within using cookie-based storage. JWT (JSON Web Token) are a popular method that will let you store signed user and client-readable data in the cookie at the cost of requiring a bit more work to manage expiration/renewal and having a larger payload. Assuming that's the approach you took, you might do something like this:
import React from "react";
import jwt from "jsonwebtoken"; // Add jsonwebtoken via npm/yarn
function getCookieValue(a) {
var b = document.cookie.match('(^|[^;]+)\\s*' + a + '\\s*=\\s*([^;]+)');
return b ? b.pop() : '';
}
const AUTH_PUBLIC_KEY = "your JWT public key here"
export const AuthContext = React.createContext();
export class AuthContextProvider extends React.Component {
state = {
authenticated: false,
userid: null,
};
componentDidMount() {
jwt.verify(getCookieValue("session"), AUTH_PUBLIC_KEY, (err, session) => {
if (!err && session.userid) {
this.setState({ userid: session.userid, authenticated: true })
}
})
}
// Important: REMOVE THIS AFTER TESTING/DEV
toggleLogin = () => {
this.setState(state => ({
authenticated: !state.authenticated,
userid: 2,
}));
}
render() {
return (
<AuthContext.Provider
value={{
...this.state,
toggleLogin: this.toggleLogin,
}}
>
{this.props.children}
</AuthContext.Provider>
);
}
}
This will parse the JWT token in the session cookie when the AuthContextProvider is mounted and update the state with the userid value stored in the JWT if one is present.
You will probably want to wrap the Gatsby App with this component, which you can do from gatsby-browser.js and gatsby-ssr.js files (create them in the root of your repo if you don't have them yet):
// gatsby-browser.js
import React from "react"
import AuthContextProvider from "components/AuthContextProvider"
export const wrapRootElement = ({ element }) =>
<AuthContextProvider>{element}</AuthContextProvider>
// gatsby-ssr.js
import React from "react"
export { wrapRootElement } from "./gatsby-browser"
You will still need to handle generating the JWT token (probably from a backend that is handling authentication) and if it's not already being persisted in a cookie you can access from the browser you will need to handle creation of that cookie at the relevant point in your application lifecycle.
I hope this helps you or others. The blog post below describes how you need to use gatsby-browser.js to wrap the root element in the provider so that it doesn't reset it on page change.
https://www.gatsbyjs.org/blog/2019-01-31-using-react-context-api-with-gatsby/
You have 3 possibilities:
web storage aka localStorage or sessionStorage (easiest, least secure)
session cookies (secure, requires backend server)
json web tokens (JWT) (most secure, requires backend server)
An excellent read about background infromation is this blog on dev.to.
1. web storage such as localStorage
This is considered to be the least secure option secure option. Do not save personal data such as email adresses here. Never ever save sensitive information such as credit card information and such.
This question describes how to use it:
var testObject = { 'one': 1, 'two': 2, 'three': 3 };
// Put the object into storage
localStorage.setItem('testObject', JSON.stringify(testObject));
// Retrieve the object from storage
var retrievedObject = localStorage.getItem('testObject');
console.log('retrievedObject: ', JSON.parse(retrievedObject));
2. cookies or session cookies
For Express you can use express-ession. Other web servers have similar middleware. The point is to supply the user info within a cookie as described on MDN.
3. json web tokens
This is similar to cookies but uses JSON web tokens. #coreyward gave an excellent answer. You can also read more in this blog post.
I want to know the authentication process on the front end (how and where to save the tokens, how to check if a user is logged in etc), redirect to the login page, when they try to access pages where login is required etc.
I do not want an implementation of this, just libraries to help me, and in case of saving things like tokens, where do I save this?
I am currently learning Redux and have little knowledge, I also saw an article on Saga and it seems to be useful for this authentication process.
As for the back end, I basically need to install some Django extensions and I will have endpoints for things like: enter username / password and return access token, expire an access token, register a user, reset password etc.
For now I know I need Redux and use the Provider and Router of the react-router. Also the basics about actions, reducers, store etc. But nothing more.
Important note: I intend to use hooks instead of class components.
short explanation:
I recommend you to use JWT for auth, you should save token in localStorage
when you login/signup server response a jwt-token:
localStorage.setItem('usertoken', token)
and then you should check user auth:
const isAuth = localStorage.getItem('usertoken')
if you use react-router-4:
if (!isAuth) {
return <Router render={() => <Redirect to="/login" />}
}
// ...your protected routes
Also every api request should contain a jwt-token in api.js file:
const apiResponse = await fetch(url, {
...someOptions,
headers: { 'x-access-token': localStorage.getItem('usertoken') }
})
if server returns 401 response you should delete token:
if (response.status === 401) {
localStorage.removeItem('usertoken');
window.location.href = '/login';
}
An alternative way for auth to use cookies
I am facing a problem while I am trying to redirecting user to home page after successful login. I am using this example in github.
https://github.com/cornflourblue/react-redux-registration-login-example
In this file, "src/LoginPage/LoginPage.jsx", There is such a part which sends login request in a POST message but I do not see any redirection to home page, just login page. Here is the part:
handleSubmit(e) {
e.preventDefault();
this.setState({ submitted: true });
const { username, password } = this.state;
const { dispatch } = this.props;
if (username && password) {
dispatch(userActions.login(username, password));
}
}
When I look at the network activity, I see that I receive successful message from response of server. Do I need to add something that does redirection?
Yes you need to add Browser Routing to your project and after a successful login you should redirect to page which you want. For example to="/Home". At first you need something like isAuth in your redux reducer. After login submit, if it is successful set this to true and in your LoginPage with mapStateToProps get this isAuth value. Also in your render() section use this before your other jsx code ->
render() {
let redirect = null;
if(isAuth == true){
redirect = <Redirect to="/Home" />;
}
return (
{redirect}
)
}
I found the reason of problem. If response is successful, something has to be set into localStorage. In private route, it checks whether that thing exists in localStorage. If not, it does not do anything but if it has, it operates the component to be redirected. I forgot to edit this part. That's why, it did not do redirection.