Importing PHP crypt MD5 passwords into Google Cloud Identity Platform? - google-cloud-platform

I'm trying to import existing users from another application into Google Cloud Identity Platform using passwords that were hashed using PHP's crypt function with an MD5 output.
This is an example of the hashed password being used in PHP:
$hashed_password = '$1$AT$JGYIRSP7xIYmg1XSoJmvB1';
$user_input = 'test123';
if (hash_equals($hashed_password, crypt($user_input, $hashed_password))) {
echo "Password verified!";
}
I've tried all sorts of combinations for importing the user and their password, but no combination seems to work. This is the NodeJS import script I'm using:
var admin = require('firebase-admin');
var app = admin.initializeApp({
credential: admin.credential.applicationDefault()
});
admin
.auth()
.importUsers(
[
{
uid: '31',
email: 'user31#test.test',
// Must be provided in a byte buffer.
passwordHash: Buffer.from('$1$AT$JGYIRSP7xIYmg1XSoJmvB1'),
// Must be provided in a byte buffer.
passwordSalt: Buffer.from('$1$AT$'),
},
],
{
hash: {
algorithm: 'MD5',
rounds: 0,
},
}
)
.then((results) => {
console.log(results);
results.errors.forEach((indexedError) => {
console.log(`Error importing user ${indexedError.index}`);
});
})
.catch((error) => {
console.log('Error importing users :', error);
});
As mentioned above, I've tried just about every combination of hash and passwordSalt I could think of. I've tried:
passing no passwordSalt
passing a passwordSalt of AT
passing a passwordSalt of AT$
passing a passwordSalt of $AT
passing a passwordSalt of $AT$
all of the above but with a hash algorithm of BCRYPT
I can see the user getting imported. If I change the hash to something like a regular MD5 hash, I'm able to authenticate as that user, so I know the import process is working correctly.
Does GCP Identity Platform simply not support these hashes? Am I passing the salt incorrectly or passing an incorrect number of rounds? Am I passing the wrong hash algorithm? I'm a little surprised, as I would've thought passwords hashed using PHP's crypt function would've been supported.

4 points:
1.- You are mentioning that you are trying to import existing users and their passwords from another application into Google Cloud Identity Platform. Also, you mentioned that you are using a NodeJS import script focused on MD5. Based on that, and using the same official GCP documentation that you quoted Migrating users from an existing app vs the NodeJS’s script you posted, it seems that you didn’t use the code sample that is posted in that documentation, exactly in the way it should be used, unless that you are the owner of the domain "#test.test":
getAuth()
.importUsers(
[
{
uid: 'some-uid',
email: 'user#example.com',
// Must be provided in a byte buffer.
passwordHash: Buffer.from('password-hash'),
// Must be provided in a byte buffer.
passwordSalt: Buffer.from('salt'),
},
],
{
hash: {
algorithm: 'PBKDF2_SHA256',
rounds: 100000,
},
}
)
.then((results) => {
results.errors.forEach((indexedError) => {
console.log(`Error importing user ${indexedError.index}`);
});
})
.catch((error) => {
console.log('Error importing users :', error);
});
2.- Are we sure you have the required admin rights or role over IAM in your GCP’s Organization? You can use the following official GCP documentation for understanding roles Understanding roles
3.- How are you figuring out that it is not working? Are you facing any of the following errors? Error codes. Is there any particular behavior after the importing process finishes or any log that you can share here editing your original post? Or is it only because you see that once the users are imported to IAM, they are not able to authenticate?
4.- I suggest you by now to follow (Method: users.insert) as stated here Method: users.insert first, this way you are going to review the hash formats that are supported. Finally, you need to do this test with the domains that you have verified in Cloud Identity.

Related

Is it possible to do a password less signIn & signOut with amplify_auth_cognito in flutter?

I'm trying to do a password less signIn & signOut in flutter with amplify, but I don't see an option on the amplify_auth_cognito package (that aws provides in pub.dev) that allows or hints me how to do it, is it even possible to do it with this package or should I use some other package? if yes then which one and how to do it?
The user pool that I'm using is from an actual working web application which I imported following the aws flutter documentaion
Thank you in advance.
What I experimented with was the default methods provided by amplify_auth_cognito as shown below:
var signUpConfirmedd = await auth.signUp(
username: _email,
// tried not filling the password field but it doesn't work
password: '1234567890',
options: CognitoSignUpOptions(
userAttributes: {
CognitoUserAttributeKey.email: _email,
CognitoUserAttributeKey.name: 'nityy'
},
),
);

AWS Cognito Software Token MFA works once, then unexpectedly reverts to SMS MFA for all future logins

Background:
I have a React web app (utilizing aws-amplify) which is connecting to/using an AWS Cognito User Pool for auth.
I am trying to enable MFA and, more specifically, I want my users to have the option to utilize Software Token TOTP MFA (i.e. Google Authenticator, or similar, app).
When I set my User Pool to have MFA required, I am forced to enable SMS MFA and then Software TOTP is optional. In my case, I have TOTP enabled.
In my web app, I have added the necessary component via:
import { SelectMFAType } from 'aws-amplify-react';
# other code
<SelectMFAType authData={user} MFATypes={{ SMS: true, TOTP: true }} />
# other code
If you're unfamiliar with aws-amplify-react and/or SelectMFAType, this component provides a UI element where the user can choose if they prefer to use SMS or Software TOTP as their MFA method. If they choose SMS, their previously-verified phone number is used and everything works.
If the user chooses TOTP, they are shown a QR code to scan in the authenticator app of their choice and they are prompted with an input field to enter a 6-digit number from the authenticator app to verify TOTP.
This is all very standard for anyone who has used a TOTP MFA option on any other website. If the user enters a correct code from the app, their TOTP choice is verified.
In short SelectMFAType is just a shortcut/stand-in to quickly prototype and test without needing to create a custom component.
Problem:
Now, here's the problem and how to reproduce it. (The starting point is a user who has just enabled TOTP.):
User logs out.
User logs in.
If correct username/password, user is prompted for TOTP.
If correct TOTP, user is logged in. This is working perfectly so far, but it won't stay that way.
User logs out.
User logs in.
If correct username/password, user is prompted for SMS MFA and will receive a text message with 6-digit code. This is the unexpected behavior. I expect it to continue requesting TOTP MFA from their authenticator app unless the user changes their preferred method back to SMS.
From this point forward, the user will only ever be asked for SMS MFA.
In between step 4 and step 7, the user's preference was not changed. Absolutely nothing changed in the React app or in the AWS User Pool settings.
Further, if I interrogate the user via the AWS CLI command
aws cognito-idp admin-get-user --user-pool-id ${MY_ID} --username ${MY_USER_NAME}, I can confirm that the user's MFA preference is exactly what I expect:
{
<other irrelevant keys redacted>
"PreferredMfaSetting": "SOFTWARE_TOKEN_MFA",
"UserMFASettingList": [
"SMS_MFA",
"SOFTWARE_TOKEN_MFA"
]
}
The MFA challenge is an API response from AWS Cognito when the user attempts to authenticate and it is either SMS_MFA or SOFTWARE_TOKEN_MFA. In my case, I get a single SOFTWARE_TOKEN_MFA challenge, but then all future challenges revert, against my wishes, to SMS_MFA.
If I repeat the TOTP setup process (delete the entry from authenticator app, re-verify, etc.), I can repeat all of the steps again. By that, I mean the MFA will expect TOTP one time and then again revert back to SMS after that first time.
Can anyone shed light on this situation? Have you experienced it? Is it a known issue/bug in AWS Cognito? Am I doing something wrong? I feel like if this was broken, there would be a pretty big amount of noise being generated, but I can't find anyone else with the same issue.
Things I have tried:
Searched extensively for anyone else with the same issue here, Google, and the AWS forums.
I have posted on the AWS forums, but that has gone nowhere: https://forums.aws.amazon.com/thread.jspa?threadID=324131
Created a custom component to replace SelectMFAType under the theory that there was something wrong with the implementation in aws-amplify-react. I will paste that custom component at the bottom of this question as a reference.
Completely destroyed and re-created the AWS Cognito User Pool. I thought perhaps it was corrupted or otherwise not working. This made no difference.
SetupTOTP.js Component:
import React, { useContext, useEffect, useState } from 'react';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContextText,
DialogTitle,
TextField,
} from '#material-ui/core';
import { Auth } from 'aws-amplify';
import { ToastsStore } from 'react-toasts';
import QRCode from 'qrcode.react';
import { AuthStateContext } from 'Context/auth-context';
const SetupTOTP = React.memo(props => {
const { open, handleClose } = props;
const { username } = useContext(AuthStateContext);
const [user, setUser] = useState(null);
const [qrCode, setQrCode] = useState('');
const [token, setToken] = useState('');
const handleSave = () => {
Auth.verifyTotpToken(user, token)
.then(() => {
Auth.setPreferredMFA(user, 'TOTP').then(() => {
ToastsStore.success('Token Verified Updated');
handleClose();
});
})
.catch(err => {
console.log(err);
ToastsStore.error(err.message);
});
};
useEffect(() => {
Auth.currentAuthenticatedUser().then(user => {
setUser(user);
});
}, []);
useEffect(() => {
if (username && user) {
Auth.setupTOTP(user).then(code => {
setQrCode(
`otpauth://totp/AWSCognito:${username}?secret=${code}&issuer=REDACTED`
);
});
}
}, [username, user]);
return (
<Dialog
open={open}
onClose={handleClose}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">Change Password</DialogTitle>
<DialogContent>
<QRCode value={qrCode} />
<TextField
margin="dense"
id="name"
label="Verify Token"
type="text"
fullWidth
onChange={e => setToken(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleSave} color="primary">
Save
</Button>
</DialogActions>
</Dialog>
);
});
export default SetupTOTP;
I believe this is a bug in Cognito. See this Amplify issue.

Can you manually set 'phone_number_verified' to true on an AWS Cognito user, without having the end user need to interact with your web-app?

We just solved an issue where entering a wrong phone number (starting digit was missing) resulted in 'phone_number_verified' defaulting to false. Now that the issue is resolved, we do still have a lot of registered users who have had an activation sent to them (and entered said code) whose statuses are CONFIRMED in AWS Cognito, but have the 'phone_number_verified' property still set to false. Is there any way to edit this property in bulk / per user in Cognito itself or is there an API call that needs to be made? Or any solution that does not require the end user to go through the activation process again themselves.
(Further Info)
- AngularJS as Front-End
- Node v6.9 as Back-End
The recommendation here is that the users should call GetUserAttributeVerificationCode and receive a code to verify the phone/email which is then supplied to VerifyUserAttribute. This will ensure that if their numbers are verified.
Although if you are sure that all of their numbers are verified, you can use AdminUpdateUserAttributes to mark them verified. We do not have a batch API right now.
The phone number verified can be set to true by using the following example payload:
let parameters = {
UserPoolId : `${USER_POOL_ID}`,
Username : `${Username}`,
UserAttributes : [
{
'Name': "phone_number" ,
'Value': `${phoneNumber}`
},{
'Name':"phone_number_verified",
'Value': "true"
}]
}
cognitoIdentityServiceProvider.adminUpdateUserAttributes(parameters,function (err, result) {
if(err)
console.log(err);
else
console.log("Attribute updated successfully");
})

Loopback angular sdk unable to login

I'm trying to use loopback angular SDK to login but instead I'm getting back a 401 unauthorized response.
User.login({ rememberMe: true }, {email: $scope.user.email, password: $scope.user.password})
.$promise
.then(function() {
var next = $location.nextAfterLogin || '/';
$location.nextAfterLogin = null;
$location.path(next);
})
.catch(function(x) {
$scope.authError = 'Wrong Credentials';
});
};
Also i can see the user with the given credentials in the datasource that I've defined for the User model.
So basically, I have a boot script that creates a default user
User.create([{
username: 'admin',
email: 'xxxx#gmail.com',
password: 'admin'
}], on_usersCreated);
And I also have created a custom User model that extends the built-in User model.
Why am I getting a 401 unauthorized response when trying to login? I have even tried to login via the explorer, but with no success.
Update:
I debugged the build-in User model and the login method; and it seems that it cannot find the user with the given email. I can see it though in the mongodb database.
Inspect the ACL records of User model, starting you app in debug mode and viewing console: DEBUG=loopback:security:acl slc run.
I don't see anything wrong with your code. Are you sure the values you're passing into the function are correct? Try logging $scope.user.email and $scope.user.password before calling User.login.
See my new example here: https://github.com/strongloop/loopback-getting-started-intermediate/blob/master/client/js/services/auth.js#L6-L15
The tutorial part is not complete, you can look around the source code to get an idea of what you're doing different.

PassportJS - How to make separate passport.session

My question is related to PassportJS - Using multiple passports within Express application topic.
So as long as I already have two separate passport instances, I realized that they both share the same cookie, created here:
application.use(session({ secret: 'my secret cookie', key: 'usid' }));
I can add one more cookie with different name using this:
app.use(connect.session({ secret: 'keyboard cat', key: 'rsid' }))
However, it is not clear to me how to instruct each passport to use its own cookie.
I figured out temporary solution for passport 0.4.0 via
passport._key = passport._sm._key = 'customPassportSessionKey'
You can solve it by making from the router and not from the app. I've solved the same issue by developing something like an AuthBuilder which receives certain parameters, each instance generates a new passport that way.
class AuthBuilderService {
constructor() {
this.passport = new Passport();
this.registerSerializers(); // Multiple serializers
this.registerStrategies(); // Multiple strategies
}
Then you can register multiple routers with the same passport and key (or just one) by calling the authbuilder.addRouter(...)
addRouter(router, loginFileName, failureUrl, successUrl) {
router.use('/login', express.static(`${__dirname}/../views/whatever`, {
index: loginFileName,
fallthrough: true,
}));
router.use(session({
secret: 'any secret',
key: (_isRouterA()) ? 'a' : 'b',
resave: true,
saveUninitialized: true
}));
router.use(this.passport.initialize());
router.use(this.passport.session());
this._configureRouter(router, failureUrl, successUrl); // There I'm handling login, logout and some middleware.
return this;
}
From the routers you want to protect:
routerUsers_1.get('something_1', method)
routerUsers_2.get('something_2', method2)
let authBuilder = new AuthBuilderService();
authBuilder.addRouter(routerUsers_1, 'login_users_vip.html', '/path/failure_vip/', '/path/success_vip');
authBuilder.addRouter(routerUsers_2, 'login_users.html', '/path/failure/', '/path/success');
routerAdmins.get('something', methodAdmin)
new AuthBuilderService().addRouter(routerAdmins, 'login_admins.html', '/path2/failure/', '/path2/success');
Then express app just use each router.
app.use('path-client-vip', routerUsers_1)
app.use('path-client', routerUsers_2)
app.use('path-admin', routerAdmin)
I'm working with 2 webapps (with different users, logins and content) in the same express app server, each webapp uses different instances of this AuthBuilderService for multiple routers using different passport, sessions, strategies and serializers between each AuthBuilderService instance.
Hope it will help somebody.