I'm working on a login flow for my web app built in React and I'm using AWS Cognito for user management. I'm working on the login flow, and I have a use case where a user is created via the AWS Console and a temporary password is provided to the user. When the user goes to login to my application for the first time, AWS Cognito returns a newPasswordRequired Challenge, and the user is forced to change their password.
I'm using the amazon-cognito-identity-js API to authenticate the user. The docs for that can be found here. I have the newPasswordRequired callback function setup just like the docs instruct, but I'm struggling to figure out the best way to gather the new password from the user using React within the newPasswordRequiredfunction. I initially used prompt() within the function to get the inputs, but I would like the app to flow to a new page where the user can enter a new password, confirm new password, and login to the app. That new page should be able to call the cognitoUser.completeNewPasswordChallenge() that is required to update the new password. Please HELP! Here's my code below:
onFormSubmission = (username, password) => {
const poolData = {
UserPoolId : AWSConfig.cognito.USER_POOL_ID,
ClientId : AWSConfig.cognito.APP_CLIENT_ID
}
const userPool = new CognitoUserPool(poolData);
const userData = {
Username: username,
Pool: userPool
}
const authenticationData = {
Username : username,
Password : password
}
const authenticationDetails = new AuthenticationDetails(authenticationData);
const cognitoUser = new CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
/*Use the idToken for Logins Map when Federating User Pools with identity pools or when passing through an Authorization Header to an API Gateway Authorizer*/
console.log('idToken + ' + result.idToken.jwtToken);
},
onFailure: function(err) {
console.log(err);
},
newPasswordRequired: function(userAttributes, requiredAttributes) {
// User was signed up by an admin and must provide new
// password and required attributes, if any, to complete
// authentication.
// userAttributes: object, which is the user's current profile. It will list all attributes that are associated with the user.
// Required attributes according to schema, which don’t have any values yet, will have blank values.
// requiredAttributes: list of attributes that must be set by the user along with new password to complete the sign-in.
*** THIS IS WHERE I WANT REACT TO RENDER A NEW PAGE TO GET THE NEW PASSWORD***
// Get these details and call
// newPassword: password that user has given
// attributesData: object with key as attribute name and value that the user has given.
cognitoUser.completeNewPasswordChallenge(pw, userAttributes, this);
}
});
}
render() {
return (
<div>
<LoginScreenComponent isInvalidForm={this.state.isInvalidForm} onFormSubmission={this.onFormSubmission}/>
</div>
)
}
I had exactly the same problem! Here is my solution:
Login.js react container can render two different components. <NewPassswordForm /> is to ask a new password, <LoginForm /> is for common login. According to isFirstLogin flag you decide which one to render.
Since you have the cognito user in this.state.user you can use it to call completeNewPasswordChallenge to finish the login flow:
handleLogin = (username, password) => {
const authDetails = new AuthenticationDetails({
Username: username,
Password: password,
});
const userData = {
Username: username,
Pool: getUserPool(),
Storage: getStorage(),
};
const cognitoUser = new CognitoUser(userData);
cognitoUser.authenticateUser(authDetails, {
onSuccess: () => {
// login
}
newPasswordRequired: userAttr => {
this.setState({
isFirstLogin: true,
user: cognitoUser,
userAttr: userAttr,
});
},
});
};
changePassword = (newPassword) => {
const cognitoUser = this.state.user;
const userAttr = this.state.userAttr;
cognitoUser.completeNewPasswordChallenge(newPassword, userAttr, {
onSuccess: result => {
// login
}
});
};
render() {
return (
<div>
{this.state.isFirstLogin ? (
<NewPassswordForm changePassword={this.changePassword} />
) : (
<LoginForm handleLogin={this.handleLogin} />
)}
</div>
);
}
Related
const createUser = async () => {
await createUserWithEmailAndPassword(email, password);
await updateProfile({ displayName });
await sendEmailVerification();enter code here
}
<button onClick={ async () => await createUser() }>Create User</button>
The user will receive en email with a verification link.
Clicking this link will verify this user.
However, the user is created even without verification in the database. This is normal.
But: One can check the field emailVerified of the user object.
It should more or less look something like this, for example if the user tries to login without having the email address verified:
final credential = await
signInWithEmailAndPassword(email: email, password: password);
if( credential.user.emailVerified == false ) ...
You can disable user after he will create account using Firebase Functions and firebase-admin SDK. If user verify email account, you can enable his account same using Firebase Functions.
const createUser = async () => {
await createUserWithEmailAndPassword(email, password);
await sendEmailVerification();enter code here
if(user.emailVerfied === true){
await updateProfile({ displayName });
}
}
I have given a task to code about sending email verification to user after they have registered themselves in signup screen which is react native based. Create new user is handle in one js file called AuthProvider.js. In one of the return value of AuthContext.Provider, there is one action which handle create new user which is shown code below and is working fine.
registerWithEmail: async (userDetails) => {
const { email, password } = userDetails;
return await auth()
.createUserWithEmailAndPassword(email, password)
.then(() => {
//wish to add on the send email verification action here
return true;
});
}
The return true code above is used to do checking in signup screen. If I wish to return the true value only if the condition where verification email is send to them and is clicked. How can I do it and can I have any kind of guidance?
You can use async-await syntax with `try-catch this way.
registerWithEmail: async (userDetails) => {
try {
const { email, password } = userDetails;
const {user} = await auth().createUserWithEmailAndPassword(email, password)
await user.sendEmailVerification()
return true
} catch (e) {
console.log(e)
return false
}
It'll return true only when the email is sent. If there's any error in the function it'll trigger catch and the function will return false.
I don't know more about react-native but in android studio when we send verification email and when user clicked on it. We have firebase function to check user clicked on verification link or not.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user.isEmailVerified())
{
// user is verified, so you can finish this screen or send user to other screen which you want.
}
i hope it will help you and give you some idea...
Hi all—I'm building an app using Next.js and Firebase, both brand new technologies for me. It's a simple app where a user creates an account and must log in. If the user doesn't create an account, the app is useless—they can't move on to the next screen, which is a dashboard. Anyway, when they log in, they can then create a trip/vacation itinerary. I'm using Firebase Auth for auth and Firestore (not real-time db) as my db. My goal is that when a user logs in, the user can see every itinerary that they created and no one else's. It should be full CRUD. This is the first time I've done this sort of authentication as well, so that's likely adding to my confusion.
I know that my code isn't right, but it sort of worked. What keeps happening is that it seems like there's a lag when a user logs in and out. I've tested this on my local copy. When I log out and then log back in as a different user, it tells me that the uid is null. Anywhere from 1 - 30 minutes later (seriously), all of a sudden the page loads for the uid that I logged in with! Everything that I've read says that there's a lag with the authentication, but I couldn't really find a solution other than just pointing out the problem—so basically writing a console log that says who's logged in at the time and then the same when they've logged out. Also, I watched / read tons of tutorials, so maybe it's my code? I'm so sorry in advance for this novel of code—I'll organize as best as I can!
Here's my config info, so I'm referring to Firebase as fire. The sign-in method is email and password, and everything looks as it should in Firebase as far as capturing that information on the Authentication screen.
import firebase from 'firebase';
const firebaseConfig = {
apiKey: 'AIzaSyB-xEPETXSfjboKe5H0kPUu-ZdRDGfszmA',
authDomain: "where-to-jess.firebaseapp.com",
databaseURL: 'https://where-to-jess-default-rtdb.firebaseio.com/',
projectId: "where-to-jess",
storageBucket: "where-to-jess.appspot.com",
messagingSenderId: "914509599583",
appId: "1:914509599583:web:80cdf3e4090417b0f35cea"
};
try {
firebase.initializeApp(firebaseConfig);
} catch(err){
if (!/already exists/.test(err.message)) {
console.error('Firebase initialization error', err.stack)}
}
const fire = firebase;
export default fire;
When a user creates an account, they're also added to a collection 'users' in my db. I am using React Hooks (for the first time) as well. Their email is their username to login, but I'm capturing their email in the db. They are also immediately logged in upon account creation. This part also works.
const handleSubmit = (e) => {
e.preventDefault();
setPassword('');
fire.auth().createUserWithEmailAndPassword(userName, password)
.then(() => {
fire.firestore().collection('users').doc(fire.auth().currentUser.uid)
.set({
firstName: firstName,
lastName: lastName,
email: userName
})
.catch(error => {
console.log('user wasn\'t added to db: ', error);
})
})
.catch(error => {
console.log('user wasn\'t able to create an account: ', error);
})
router.push('/users/dashboard')
};
This is my login code:
const handleLogin = (e) => {
e.preventDefault();
fire.auth()
.signInWithEmailAndPassword(username, password)
.catch((error) => {
console.log('user wasn\'t able to login: ', error);
})
setUsername('')
setPassword('')
router.push('/users/dashboard')
};
Now for the fun part! This is my code for form submission for the itinerary. What I'm trying to achieve here is to have this newly created itinerary attached to their uid in the 'users' db. I'm leaving out all the form stuff because it's super long. This also seems to work—I can see it coming in in the db for whichever account I'm using.
const handleSubmit = (e) => {
e.preventDefault();
fire.firestore()
.collection('users').doc(fire.auth().currentUser.uid).collection('itineraries')
.add({
//
})
.catch(error => {
console.log('itinerary not added to db ', error)
})
router.push('/users/dashboard')
}
Here's where it all went to heck! I suspect it's because I'm cutting corners, which I'll explain next. This dashboard should show ONLY itineraries that the current logged-in user created. If the current logged-in user didn't create any itineraries, I'd get an error saying that the uid was null. SO, my workaround was to just create a fake itinerary manually in the db on their account (since I was testing) and give the tripName value as null. This seems to work, but this is where the weird login / logout stuff happens.
export default function Dashboard() {
const router = useRouter();
const [itineraries, setItineraries] = useState([]);
const [loggedIn, setLoggedIn] = useState(false);
fire.auth()
.onAuthStateChanged((user) => {
if (user) {
console.log(user.email + " is logged in!");
setLoggedIn(true)
} else {
setLoggedIn(false)
console.log('User is logged out!');
}
})
useEffect(() => {
const unsubscribe =
fire.firestore()
.collection('users').doc(fire.auth().currentUser.uid).collection('itineraries').where('tripName', '!=', 'null')
.onSnapshot(snap => {
const itineraries = snap.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setItineraries(itineraries);
return () => {
unsubscribe();
};
});
}, []);
const handleLogout = () => {
fire.auth()
.signOut()
router.push('/')
};
Lastly, here is the one rule that I have on the db. I got confused reading the rule docs, and I feel like I cut a corner here.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read
allow write
}
}
}
Again, I'm really sorry for ALL of that code. This is my first time using React Hooks, Next, and Firebase—so it's a mashup of Firebases's docs, tutorials, and my own code. I'd appreciate ANY help or advice here.
That rule will allow all access to all documents in your db at present. You want something like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user_id}{
allow read, write: if request.auth != null && request.auth.uid == user_id;
}
}
}
That will allow access only to users that are authenticated
This was 100% user error on my part, but I wanted to share since some of my issues seem pretty common. In addition to AspiringApollo's advice above, I had my function completely out of order (as I mentioned, hook newbie). The above plus structuring my function like this fixed it:
useEffect(() => {
const unsubscribe =
fire.auth()
.onAuthStateChanged((user) => {
if (user) {
let uid = user.uid
console.log(user.email + ' is logged in!');
setLoggedIn(true)
// all the things you want to do while the user is logged in goes here
} else {
setLoggedIn(false)
console.log('user is logged out!');
}
return () => {
unsubscribe();
};
});
}, []);
Still open to suggestions and more sets of eyes because I know this is a little messy!
I want to show logged user information (name, avatar, role) on appBar. I created some custom components : layout, appBar and userMenu How can I pass the logged user data to the userMenu or appBar component?
I believe that in the getIdentity method of your Auth provider you want to return an object with this information in your Promise.resolve. So it is somewhat indirect and not done within a custom AppBar itself.
Here is a snippet example from an auth provider:
getIdentity: () => {
return Promise.resolve({
id: userName,
fullName: userFullName,
avatar: <Avatar />
});
The Avatar aspect is optional of course and React-Admin will place its own avatar there if you leave that out.
One approach would be retrieving the user information from the authentication storage you are using on the client-side.
In my case, I'm showing user initials instead of the standard avatar in the component by pulling the information from the JWT token stored on localStorage.
decodeJwt(localStorage.getItem('token')).fullname
Probably not the best approach but worked for my use case.
See https://github.com/marmelab/react-admin/issues/7042
This is what worked for me in my AuthProvider. I am using session-based authentication and don't have a jwt client side - so I expose an API on the server that provides the logged in user info. I don't have an avatar so I pass the user initials (only the first letter appears on the page)
getIdentity: async () => {
const userinfo = await UserService.getUserInfo();
const {id: id, userName: fullName, firstLastInitials: avatar} = userinfo;
console.log(`getIdentity UserService user name is ${fullName}'`);
return Promise.resolve({ id, fullName, avatar });
}
and
const getUserInfo = async () => {
const apiUri = HostApiServer(); // GET SERVER URI
const mainResponse = await fetch(`${apiUri}/loggedInUser`, {
method: 'GET'
});
const resp = await mainResponse.json();
console.log(`UserService - GOT user info from ${apiUri}`);
console.log(JSON.stringify(resp));
return resp;
};
const UserService = {
doLogin,
doLogout,
isLoggedIn,
getToken,
getUserInfo
};
I am using Firebase and trying to get the current signed in user. I check to see if a user is signed in using componentDidMount and if a user is signed in, I try and update my state.
Now, even though I am getting a Firebase user object in my console, the state is never updating. It also doesn't update when signing a user up for the first time using the toggleSignIn method.
All of this is being done in my App.js file. I'm wondering if there is a better way to go about trying to get the currently signed-in user and update the state accordingly so I can use the user object in other components and get things like the users photoURL, displayName, etc.
var App = React.createClass({
**getInitialState:** function () {
// Set initial state of user to null
return {
user : null
}
},
componentDidMount: function () {
// Checks to see if a user is signed in and sets state to the user object.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
**this.setState({
user: user
});**
} else {
// No user is signed in.
}
});
},
toggleSignIn: function () {
if (!firebase.auth().currentUser) {
// Create an instance of the Google provider object
var provider = new firebase.auth.GoogleAuthProvider();
// Sign in with popup.
firebase.auth().signInWithPopup(provider).then(function(result) {
var token = result.credential.accessToken;
// Get signed-in user info
**this.setState({
user: result.user
});**
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});
} else {
// Sign user out
firebase.auth().signOut();
}
},
render: function () {
return (
<div className="app">
<main className="app-content">
{this.props.children}
</main>
</div>
);
}
});
module.exports = App;