How to sign user in after registration using AWS Amplify and React - javascript

When a user signs up using the withAuthenticator component, they are automatically logged in after confirming their email address. It this possible to automatically sign them in using a custom sign-in flow and the Auth object? How would this be accomplished?

I recently dealt with an issue in which I wanted to use the Storage module of Amplify, but had to implement the Auth module in order to do it. I had a working signin flow, and didn't want it disturbed, so I used the Auth api to sign users into AWS in the background, while signing them into my app using my original flow.
The docs describe how to sign a user in programmatically here: https://docs.amplify.aws/lib/auth/emailpassword/q/platform/js
Implementing the JS code isn't too hard
import { Auth } from 'aws-amplify';
async function signIn() {
try {
const user = await Auth.signIn(username, password);
} catch (error) {
console.log('error signing in', error);
}
}
I implemented a pre-sign up lambda trigger to auto confirm users, their email, etc bc I didn't want this Auth flow interrupting my existing flow. This method is described here:
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html#aws-lambda-triggers-pre-registration-example-2
Here are the notes I took as I was working, in case it helps to give you more context. Sorry if they're confusing, they're only really meant for me to read: https://www.evernote.com/shard/s713/sh/ccf96dcc-b51e-9963-5207-ac410b02a13a/0bcdb4bc85ea8a31b7f5621a6812c837

As of Amplify JS API v4.3.29 it is now possible. Simply include the autoSignIn attribute in the signUp method.
Auth.signUp({
username: 'xxxxxx',
password: '*********,
attributes: {
email: 'xxxxxxxxxx'
},
autoSignIn: {
enabled: true
}
})

Related

Possible to authenticate an AWS Cognito user via the API?

JavaScript/AWS here, although this is more of an AWS API question (since the JavaScript SDK just implements the API).
I'm looking at the AWS Cognito JavaScript SDK and trying to figure out how to programmatically authenticate a user given:
a username
a password
a user pool ID
But nothing obvious is jumping out at me. Is this not possible to do through their API/SDK? Do they force you to login from a web form?! Thanks in advance for any-and-all-steering!
I think there are couple of options that we can use here.
Let me provide examples with InitiateAuthCommand and AdminInitiateAuthCommand.
InitiateAuthCommand:
In order to user this, we should enable ALLOW_USER_PASSWORD_AUTH in the app client. This can be used in client-side authentication flow.
return new CognitoIdentityProviderClient({region: "<aws-region>"}).send(
new InitiateAuthCommand({
ClientId: "<the client id of the app>",
AuthFlow: AuthFlowType.USER_PASSWORD_AUTH,
AuthParameters: {
USERNAME: "<user#example.com>",
PASSWORD: "<password>",
},
})
)}
AdminInitiateAuthCommand:
In order to user this, we should enable ALLOW_ADMIN_USER_PASSWORD_AUTH in the app client. Also we have to configure the AWS credentials in the execution environment. So this can be used in Server-side authentication flow
return new CognitoIdentityProviderClient({region: "<aws-region>"}).send(
new AdminInitiateAuthCommand ({
ClientId: "<the client id of the app>",
AuthFlow: AuthFlowType.ADMIN_NO_SRP_AUTH,
UserPoolId:"<the user pool id>",
AuthParameters: {
USERNAME: "<user#example.com>",
PASSWORD: "<password>",
},
})
)}
NOTE: You can change the AuthFlow based on your requirement. Please read User pool authentication flow, InitiateAuthCommandInput.AuthFlow and AdminInitiateAuthCommandInput.AuthFlow for more details.

Is there a better way of implementing this without multiple fetch request?

This is login component from a react application and I am trying to do a simple authentication login with Firebase as my real time database.
My first approach is to execute a fetch request (GET) to see if there is any existing user. Afterwhich, if the password do match with the user, I want to update the "isLoggedIn" field to true. In order to achieve the following, another fetch request (PATCH) was made.
Therefore, I am wondering if it is a bad practice to have multiple fetch request in one function and would like to know if there is a more efficient or better way of implementing this simple application?
const loginHandler = async (userName, password) => {
const url = `insert_url_link_here/users/${userName}.json`;
try {
const response = await fetch(url);
const user = await response.json();
if (user.password === password) {
await fetch(url, {
method: "PATCH",
body: JSON.stringify({ isLoggedIn: true }),
});
}
} catch (error) {
console.log(error);
}
This is the schema of my database.
--users:
eric:
isLoggedIn: false
password: "321"
test:
isLoggedIn: false
password: "222"
You're accessing the Firebase Realtime Database through its REST API, which implements simple CRUD operations, and there's no way to combine a read and a write operation into a single operation there.
The only other relevant operation is the conditional write, which you can use to only write data if it hasn't been modified since you read it. You can use this to implement an optimistic compare-and-set like transaction mechanism, but given that you're toggling a boolean it doesn't seem to apply here.
If you're on an environment that supports it, you may want to consider using the Firebase SDK for that platform, as the SDKs typically use a web socket to communicate with the server, which often ends up being a lot more efficient than performing multiple individual HTTP requests.
I suggest using a Firebase client implementation and looking at the Firebase documentation. There are well described and explicit examples in various languages:
https://firebase.google.com/docs/auth/web/start
Besides, I don't see the point of storing passwords or login state on your side since Firebase Auth already does this for you in a secure way.

Any suggestions on how to implement challenge (2 factor authentication, etc...) in authentication

I am trying to incorporate 2FA in the react admin login flow.
The issue is that the standard way to validate a login is to use useLogin.
const login = useLogin();
try {
await login({username: "joeblack", password: "mybadpassword"}, "/redirectlocation");
} catch (err) {
// display an error notice or whatever
}
Basically, the login function from useLogin will either complete the login process and log the user in or show an error.
Second Authentication Step
For things like 2FA, new password required, etc..., there needs to be an in between step where the user isn't authenticated yet to view resources, but is not in an error state.
So for instance, perhaps login would return a challenge with the type of challenge.
Technically this can be done by returning that info in the authProvider login function and then making decisions based on that.
const loginResult = login({username: "joeblack", password: "mybadpassword"});
// loginResult returns { challenge: "2FA" }
if (challenge) {
// redirect to challenge page
} else {
// redirect to dashboard or wherever
}
The issue is that even if we handle it after the login function, once that login function runs, technically the user is authenticated. So they could just bypass the challenge and directly input the resource they want and they would be able to view it.
The login function of the authProvider only has 2 results, a resolved promise or rejected promise.
Before I go further to figure out how to make this work I wanted to see if anyone else has looked into this issue.
This is basically a workaround, but I think it's the best option for now (although I'd love to be proven wrong).
The idea is that even though Amplify's auth comes back with a resolved promise containing the Cognito user, we check if there is a challenge property. If there is, then we reject the login promise so react-admin doesn't log the user in.
Then we return an error message in the rejected promise that can be read by whatever is calling the login function. If it's a challenge then the login interface can handle it appropriately presenting a 2FA screen to confirm.
Had to also include the Cognito user in the our case because we need that in order to confirm the 2FA.
Here is the authProvider login function
login: async ({ username, password }) => {
try {
const cognitoUser = await Auth.signIn(username, password);
if (cognitoUser.hasOwnProperty("challengeName")) {
return Promise.reject({
message: cognitoUser.challengeName,
cognitoUser: cognitoUser,
});
} else {
return { ...createUser(cognitoUser) };
}
} catch (err) {
return Promise.reject({ message: err.code });
}
}
Hopefully that helps someone. Let me know if you have a better solution.

Firebase - check if user created with Google Account is signing up or logging in?

I'm trying to create a web application, where you can log-in and register an account with a google account. I have managed to make it so they can log-in with the signInWithPopup(provider), but not sure how to Implement the sign-up. Any suggestions? or know of any functions in firebase i can use?
There aren't any separate methods to login and sign up when using Google Sign-In or any other provider. If a user with that credential exists then user will be logged in else a new user account will be created. You can then use additionalUserInfo.isNewUser property from the result received from signInWithPopUp method to check if the user is new.
firebase.auth().signInWithPopup(provider).then(function (result) {
const {additionalUserInfo: {isNewUser}} = result;
console.log(isNewUser ? "This user just registered" : "Existing User")
})
For the new Modular SDK (V9.0.0+), the same can be written as:
import { signInWithPopup, getAdditionalUserInfo } from "firebase/auth"
const result = await signInWithPopup(auth, googleProvider);
// Pass the UserCredential
const { isNewUser } = getAdditionalUserInfo(result)
So far, as I understood, you have two options to log in to your website: one is to make a local username/password account on your website, and the other option is to use your google account. I suppose the best way would be to check if the Google account is linked to any existing user using additionalUserInfo.isNewUser, and then linking up your google account with the account that is created via your website locally by following this article: https://firebase.google.com/docs/auth/web/account-linking?hl=lt
Once you have Firebase dependency inside your application. You can use createUserWithEmailAndPassword method to do that.
firebase
.auth()
.createUserWithEmailAndPassword("email#domain.com", "123123")
.then(data => {
data.user.updateProfile({
displayName: this.form.name
}).then(() => {});
})
.catch(err => {
this.error = err.message;
});

Firebase: recent login requested

I'm dealing with Firebase authentication for web.
The documentation states that
Some security-sensitive actions—such as deleting an account, setting a primary email address, and changing a password—require that the user has recently signed in.
If not, the request would fail with error code auth/requires-recent-login and I should manage the case by prompting the user to re-insert her credentials. Once I have done that, I could easily re-authenticate the user with the following code:
firebase.auth().currentUser.reauthenticate(credential)
In the API reference there's some details more. It turns out credential is actually an object of type firebase.auth.AuthCredential. That being said, I still have a bunch of questions to which I couldn't find answer on the docs:
How do I create the AuthCredential object?
More importantly, how do I deal with providers (Google, Facebook, ...). I agree that changing email/password doesn't make sense for providers, because this is not the right place to change them, so re-authentication does not apply in this case. However, deleting a user is still an action requiring re-authentication, and this could be performed regardless of the authentication method. How do I re-authenticate a user that logged in with a provider?
The documentation states that the user must have logged in recently. I couldn't find any definition of recent in the docs.
You can initialize a credential by calling credential static method on any provider (include email/password provider):
firebase.auth.FacebookAuthProvider.credential(fbAccessToken);
To reauthenticate an OAuth provider, you can call in a browser signInWithPopup or redirect. This will return an object with 2 fields: user and credential. You can use that credential directly. Here is a simplified example:
var tempApp = firebase.initializeApp(originalConfig, 'temp');
var provider = new firebase.auth.FacebookAuthProvider();
tempApp.signInWithPopup(provider).then(function(result)) {
tempApp.auth().signOut();
originalApp.auth().currentUser.reauthenticate(credential);
});
That doesn't matter, as the firebase auth backend could change that. You shouldn't hard code this value. Instead try to catch that error and act appropriately when it happens.
You should reauthenticate with the provider;
import { getAuth, signInWithPopup, reauthenticateWithPopup, GoogleAuthProvider } from "firebase/auth";
const loginAuth = getAuth();
const googleProvider = new GoogleAuthProvider();
function reauthWithGoogle() {
return reauthenticateWithPopup(loginAuth, googleProvider)
}
and when you get the auth/requires-recent-login error call that function;
updatePassword(currentUser, "new password")
.catch(e => reauthWithGoogle()) //better check if the error is auth/requires-recent-login

Categories

Resources