In Keystone JS v6, is there a way to store users without password being a required option? - keystonejs

I want to offer my users password-based authentication but also the possibility to log in with Oauth providers. I've looked into the Next-Auth adapters to get a reference for creating the schema and I'm also aware that there's an OpenSource package that adapts the createAuth method for Oauth, but it seems that the solutions provided force me to pick one of the those two.
I'm not sure how to approach this with Keystone. Should I, for example, create a Client list in the form of:
const Client = list({
fields: {
name: text({validation: {isRequired: true}}),
email: text({
validation: {isRequired: true},
isIndexed: 'unique',
isFilterable: true,
}),
password: password(),
oauthProvider: text()
}
})
that represent the clients of my app, and then a User for Admins in the form of:
const User = list({
fields: {
name: text({validation: {isRequired: true}}),
email: text({
validation: {isRequired: true},
isIndexed: 'unique',
isFilterable: true,
}),
password: password({validation: {isRequired: true}}),
}
})
the latter being the one used as a listKey for the createAuth function?
I've also thought of generating random passwords for users that Sign In with Oauth, but It feels like a liability from the security standpoint.

I'm not sure I understand the problem. You should just be able to set isRequired: false for the password field, add whatever other fields you need to store for Oauth then use one or the other.
There's no need to generate random/placeholder passwords; the Password field stores bcrypt hashes so blank/missing values will never be matched. Ie. storing null in the password field will prevent that user from authenticating with a password, it doesn't let people authenticate by submitting a blank string or anything like that.
Does that help?

Related

How to include TOTP MFA in AWS Cognito authentication process

I'm using Cognito user pools to authenticate my web application. I've got it all working right now but now I need to enable MFA for it. This is how I do it right now (all the code provided are server-side code):
Signing up the user:
const cognito = new AWS.CognitoIdentityServiceProvider();
cognito.signUp({
ClientId,
Username: email,
Password,
}).promise();
An email is sent to the user's address (mentioned as username in the previous function call) with a code inside.
The user reads the code and provides the code to the next function call:
cognito.confirmSignUp({
ClientId,
ConfirmationCode,
Username: email,
ForceAliasCreation: false,
}).promise();
The user logs in:
const tokens = await cognito.adminInitiateAuth({
AuthFlow: 'ADMIN_NO_SRP_AUTH',
ClientId,
UserPoolId,
AuthParameters: {
'USERNAME': email,
'PASSWORD': password,
},
}).promise();
I'm pretty happy with this process. But now I need to add the TOTP MFA functionality to this. Can someone tell me how these steps will be changed if I want to do so? BTW, I know that TOTP MFA needs to be enabled for the user pool while creating it. I'm just asking about how it affects my sign-up/log-in process.
Alright, I found a way to do this myself. I must say, I couldn't find any documentation on this so, use it at your own risk!
Of course, this process assumes you have a user pool with MFA enabled (I used the TOTP MFA).
Signing up the user:
const cognito = new AWS.CognitoIdentityServiceProvider();
cognito.signUp({
ClientId,
Username: email,
Password,
}).promise();
An email is sent to the user's address (mentioned as username in the previous function call) with a code inside.
The user reads the code and provides the code to the next function call:
cognito.confirmSignUp({
ClientId,
ConfirmationCode: code,
Username: email,
ForceAliasCreation: false,
}).promise();
The first log in:
await cognito.adminInitiateAuth({
AuthFlow: 'ADMIN_NO_SRP_AUTH',
ClientId,
UserPoolId,
AuthParameters: {
'USERNAME': email,
'PASSWORD': password,
},
}).promise();
At this point, the return value will be different (compared to what you'll get if the MFA is not enforced). The return value will be something like:
{
"ChallengeName": "MFA_SETUP",
"Session": "...",
"ChallengeParameters": {
"MFAS_CAN_SETUP": "[\"SOFTWARE_TOKEN_MFA\"]",
"USER_ID_FOR_SRP": "..."
}
}
The returned object is saying that the user needs to follow the MFA_SETUP challenge before they can log in (this happens once per user registration).
Enable the TOTP MFA for the user:
cognito.associateSoftwareToken({
Session,
}).promise();
The previous call is needed because there are two options and by issuing the given call, you are telling Cognito that you want your user to enable TOTP MFA (instead of SMS MFA). The Session input is the one return by the previous function call. Now, this time it will return this value:
{
"SecretCode": "...",
"Session": "..."
}
The user must take the given SecretCode and enter it into an app like "Google Authenticator". Once added, the app will start showing a 6 digit number which is refreshed every minute.
Verify the authenticator app:
cognito.verifySoftwareToken({
UserCode: '123456',
Session,
}).promise()
The Session input will be the string returned in step 5 and UserCode is the 6 digits shown on the authenticator app at the moment. If this is done successfully, you'll get this return value:
{
"Status": "SUCCESS",
"Session": "..."
}
I didn't find any use for the session returned by this object. Now, the sign-up process is completed and the user can log in.
The actual log in (which happens every time the users want to authenticate themselves):
await cognito.adminInitiateAuth({
AuthFlow: 'ADMIN_NO_SRP_AUTH',
ClientId,
UserPoolId,
AuthParameters: {
'USERNAME': email,
'PASSWORD': password,
},
}).promise();
Of course, this was identical to step 4. But its returned value is different:
{
"ChallengeName": "SOFTWARE_TOKEN_MFA",
"Session": "...",
"ChallengeParameters": {
"USER_ID_FOR_SRP": "..."
}
}
This is telling you that in order to complete the login process, you need to follow the SOFTWARE_TOKEN_MFA challenge process.
Complete the login process by providing the MFA:
cognito.adminRespondToAuthChallenge({
ChallengeName: "SOFTWARE_TOKEN_MFA",
ClientId,
UserPoolId,
ChallengeResponses: {
"USERNAME": config.username,
"SOFTWARE_TOKEN_MFA_CODE": mfa,
},
Session,
}).promise()
The Session input is the one returned by step 8 and mfa is the 6 digits that need be read from the authenticator app. Once you call the function, it will return the tokens:
{
"ChallengeParameters": {},
"AuthenticationResult": {
"AccessToken": "...",
"ExpiresIn": 3600,
"TokenType": "Bearer",
"RefreshToken": "...",
"IdToken": "..."
}
}

Email only authentication with Vue.js and Vuex on Firebase

I want user to be automatically authenticated (temporarily) on Firebase just by sending Email then be redirected to a welcome page asking to complete the auth process by following a link received by email.
The first part is ok, I can authenticate by just inserting email and generating a random password like the following (Vuex store action):
this.$store.dispatch('userSignUp', { email: this.email, password: this.generatePassword })
which is called by component method button v-on:click="userSignUp
Vuex action is like :
userSignUp ({commit}, payload) {
commit('setLoading', true)
firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password)
.then(firebaseUser => {
commit('setUser', firebaseUser)
commit('setLoading', false)
commit('setError', null)
router.push('/welcome')
})
.catch(error => {
commit('setError', error.message)
commit('setLoading', false)
})
}
So far so good, the user put the email, an helper function this.generatePassword generate a random password and the user is logged in and created on firebase.
Now this user is logged in, is on a welcome page, but it doesn't know his own random password (because I don't want to).
I want this to be one shot login and if the user want to come back, has to follow the link sent by email by Firebase.
There is a firebase function [sendPasswordResetEmail][1], which seems right for the case but I connot find the way to make it working.
I did Vuex action like before :
export const actions = {
sendPasswordReset ({commit}, payload) {
commit('setLoading', true)
firebase.auth().sendPasswordResetEmail(payload.email)
.then(firebaseUser => {
commit('setUser', firebaseUser)
commit('setLoading', false)
commit('setError', null)
router.push('/welcome')
})
.catch(error => {
commit('setError', error.message)
commit('setLoading', false)
router.push('/error')
})
},
...
which is called by component method button v-on:click="userSignUp
methods: {
userSignUp () {
this.$store.dispatch('userSignUp', { email: this.email, password: this.generatePassword })
this.$store.dispatch('sendPasswordReset', { email: this.email })
}
},
I only get response code
{
"error": {
"errors": [
{
"domain": "global",
"reason": "invalid",
"message": "EMAIL_NOT_FOUND"
}
],
"code": 400,
"message": "EMAIL_NOT_FOUND"
}
}
while the Request Payload seems ok anyway :
{requestType: "PASSWORD_RESET", email: "luca.soave#gmail.com"}
email
:
"luca.soave#gmail.com"
requestType
:
"PASSWORD_RESET"
Any idea ?
The provider you're using is called the password provider. As its name implies it is heavily dependent on the user having (and knowing) a password. Since you are looking for passwordless authentication, I'd recommend against using the email+password provider as the basis.
Instead consider implementing a custom authentication provider. While this involves a few more components, it is not as difficult as you may think. You'll need to run trusted code, which you can do either on a server you already have, or on Cloud Functions. In either of those cases, you'll use one of the Admin SDKs to implement the sensitive parts of the authentication flow.
A quick list of steps that I think you'll need:
Create an endpoint (e.g. a HTTP triggered Cloud Function) for the user to request an authentication email.
Implement the code for this endpoint to:
Generate a random one-time code in there, which you're going to send to the user. Firebase Authentication calls this the out-of-band (or OOB) code, since it's sent to the user on a different medium than your app.
Store this code and the user's email address somewhere where only your server-side code can read it, e.g. in the Firebase Database or Cloud Firestore.
Send an email to the user, with the code or a link to a page that includes the code and their email address.
Create an endpoint (e.g. again a HTTP function, or web page) where the user enters (e.g. by clicking on a link in the email) the OOB code and their email address.
Compare the code the user entered, to the one you stored before.
If the codes match, generate a custom token for the user and send it to them.
The user/app now signs into Firebase with the custom token.

auth0 - email verification - user account does not exist or verification code is invalid

Here is my problem : In auth0 dashboard, I select a user within my users list and click on send a verification email... The user receive the mail, click on the link and get an error "User account doesn't exist or verification code is invalid" But the user exists and I do not use passwordless or sms authentication , my users have to enter their password and are also stored in mongodb. Any ideas to solve this?
-- edited precision added --
#Arcseldon
I'am actually using a customDB and here is my getUser script, but I don't know what to change, could you help me?
Thank you!
function getByEmail (email, callback) {
mongo('mongodb://user:pass#dsXXXX.mlab.com:XXXX/base', function (db) {
var users = db.collection('user');
users.findOne({ email: email }, function (err, user) {
if (err) return callback(new Error("my error message"));
if (!user) return callback(null);
var profile = {
user_id: user._id,
nickname: user.username,
email: user.email,
};
callback(null, profile);
});
});
}
Ok, just re-read your question - where you state "my users have to enter their password and are also stored in mongodb." - are you referring to your own Mongo DB? Are you using an Auth0 Custom DB Connection? Confusingly, Auth0 also uses MongoDB for its own DB storage, hence the clarification. If you are using a Custom DB connection to your own DB, then this may be a misconfiguration of one of your Custom DB Scripts. If using Custom DB Script, please double-check the implementation of your GetUser.js script.
In the event, you are using an Auth0 DB (not a custom DB) then definitely check with Auth0 support team (as per comment and your reply above).

Turning off Deployd dashboard authentication

I have a Deployd application that uses the standard built-in authentication to access the "DEPLOYD DASHBOARD", the one where you enter the key that is revealed by dpd showkey.
The whole website is now secured with a username/password requirement to access it.
How do I turn off the authentication required to access the deployd dashboard?
I've tried deleting the ./.dpd/keys.json file.
I haven't yet found anything useful in the docs.
This doesn't seem like the best solution, but it does do exactly what is required:
From : http://docs.deployd.com/docs/server/
Note: If options.env is "development", the dashboard will not require authentication and configuration will not be cached. Make sure to
change this to "production" or something similar when deploying.
Example
('env': 'development' has been added):
var deployd = require('deployd')
, options = {
'port': 7777,
'db': {
'host': '127.0.0.1',
'name': 'my-database'
},
'env': 'development'
};
var dpd = deployd(options);
dpd.listen();
I won't mark this as the correct answer in case there is a solution that doesn't require doing something explicitly discouraged (ie. "make sure to change [this] when deploying").

Can I create an AWS Cognito user login programmatically?

I would like my app to allow users with a Facebook, Google, Amazon... etc... accounts to be able to login to my app. This works fine through AWS Cognito.
However, is there a way for the app to create a user login programmatically if the user does not have any of those logins?
The user would provide an id and a password and the app would send the information to the authentiation provider to create a new login/account.
I would not need to implement my own authentication mechanism and worry about how the passwords are stored, etc.
From my research I take that there is no way to do this with existing authentication providers or even other services such as OpenID.
Do you have any other options if I do not want to implement my own login storage and authentication? It would not necessarily need to integrate with AWS Cognito.
I'm a little confused by your question. If you're asking:
Can I create new usernames and passwords on Facebook / Google programatically?
Then the answer is no. You have to sign up for Facebook / Google on their site. If you're asking:
Can I create a new user with a username and password that only exists in Cognito?
Then the answer is yes. To do this, it depends on whether you're creating the user in a browser or on a server. In a browser, use the Cognito Javascript API. On a server, use the Cognito Admin Server APIs.
Here's some sample code for creating a new user on the server in Node JS (replace my strings with your own tokens, especially the ones with # signs in them):
let params = {
UserPoolId: "#cognito_pool_id#",
Username: "jhancock",
DesiredDeliveryMediums: ["EMAIL"],
ForceAliasCreation: false,
MessageAction: "SUPPRESS",
TemporaryPassword: "somePassword",
UserAttributes: [
{ Name: "given_name", Value: "John"},
{ Name: "family_name", Value: "Hancock"},
{ Name: "name", Value: "John Hancock"},
{ Name: "email", Value: "john#gmail.com"},
{ Name: "phone_number", Value: "+15125551212"}
],
};
console.log("Sending params to cognito: " + JSON.stringify(params));
let cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider({region: "us-east-1"});
cognitoIdentityServiceProvider.adminCreateUser(params, function(error, data) {
if (error) {
console.log("Error adding user to cognito: " + JSON.stringify(error), error.stack);
} else {
console.log("Received back from cognito: " + JSON.stringify(data));
}
}
One you get that working, you'll probably want to see this post about how to change the temporary password into a real one.
Hi from my previous experence in implementing of the social media authentication.
I would conclude that it is quite hard to implement.If you do not what to show web view to authenticate user in iOS you need to use iOS ACAccountStore class for this, but even this only gives opportunity to log in not to sign in.