Amazon Cognito Registration Confirmation - javascript

So, I am using (or at least trying to) Amazon Cognito with Lambda functions for auth.
Here's the flow: I send request, it goes to API Gateway, which directs it to a specific Lambda function.
I am using Node JS with amazon-cognito-identity-js library.
I am able to register user.
The thing is that, Cognito sends email with the confirmation code after the registration. I am unable to create another Lambda (API endpoint) function for confirmation, since it requires CognitoUser object (which you receive after registering or login). Here is the code from AWS documentation:
cognitoUser.changePassword('oldPassword', 'newPassword', function(err, result) {
if (err) {
alert(err);
return;
}
console.log('call result: ' + result);
});
So basically, it's not designed for Lambda functions, since it requires to save the state - the user after the registration.
Am I getting it wrong? Is there a way?

Oh, ok, my bad.
Apparently you can create a CognitoUser object using only username and user pool:
const poolData = {
UserPoolId : process.env.COGNITO_USER_POOL_ID,
ClientId : process.env.COGNITO_CLIENT_ID
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
...
const userData = {
Username : email,
Pool : userPool
};
and then you can call
cognitoUser.confirmRegistration(confirmationCode, true, function(err, result) {
if (err) {
alert(err);
return;
}
alert(result);
});

Related

Firebase: Email verification link always expired even though verification works

I'm trying to set up an email verification flow in my project, but I can't seem to get it right.
How my flow works now is the user enters their credentials (email and password), which are used to create a new firebase user. Then, once that promise is resolved, it sends an email verification link to the new user that was created. The code looks like this:
async createUser(email: string, password: string) {
try {
console.log("Creating user...");
const userCredentials = await createUserWithEmailAndPassword(
auth,
email,
password
);
console.log("Successfully created user");
const { user } = userCredentials;
console.log("Sending email verification link...");
await this.verifyEmail(user);
console.log("EMAIL VERIFICATION LINK SUCCESSFULLY SENT");
return user;
} catch (err) {
throw err;
}
}
async verifyEmail(user: User) {
try {
sendEmailVerification(user);
} catch (err) {
throw err;
}
}
The link is sent through fine, but once I press on it, I'm redirected to a page that says this:
Strangely, the user's email is verified after this, in spite of the error message displayed. Any idea why this is happening?
Update:
I managed to figure it out. The email provider I'm using is my university's, and it seems to be preventing the verification link from working properly. I did try with my personal email to see if that was the case, but I wasn't seeing the verification link appearing there. I eventually realized that it was because it was being stored in the spam folder. It's working on other email providers, though, ideally, I'd want it to work on my university's email provider (the emails that users sign up with are supposed to be exclusively student emails). Any ideas how I could resolve this?
I eventually figured out that the issue was with my email provider. I was using my student email, which the university provides, and I imagine they've placed rigorous measures in place to secure them as much as possible. I have no idea what was preventing it from working, but I managed to figure out a workaround.
In brief, I changed the action URL in the template (which can be found in the console for your Firebase project in the Authentication section, under the Templates tab) to a route on my website titled /authenticate. I created a module to handle email verification. Included in it is a function that parses the URL, extracting the mode (email verification, password reset, etc.), actionCode (this is the important one. It stores the id that Firebase decodes to determine if it's valid), continueURL (optional), and lang (optional).
export const parseUrl = (queryString: string) => {
const urlParams = new URLSearchParams(window.location.search);
const mode = urlParams.get("mode");
const actionCode = urlParams.get("oobCode");
const continueUrl = urlParams.get("continueUrl");
const lang = urlParams.get("lang") ?? "en";
return { mode, actionCode, continueUrl, lang };
};
I created another method that handles the email verification by applying the actionCode from the URL using Firebase's applyActionCode.
export const handleVerifyEmail = async (
actionCode: string,
continueUrl?: string,
lang?: string
) => {
try {
await applyActionCode(auth, actionCode);
return { alreadyVerified: false };
} catch (err) {
if (err instanceof FirebaseError) {
switch (err.code) {
case "auth/invalid-action-code": {
return { alreadyVerified: true };
}
}
}
throw err;
}
};
The auth/invalid-action-code error seems to be thrown when the user is already verified. I don't throw an error for it, because I handle this differently to other errors.
Once the user presses the verification link, they're redirected to the /authenticate page on my website. This page then handles the email verification by parsing the query appended to the route. The URL looks something like this http://localhost:3000/authenticate?mode=verifyEmail&oobCode=FLVl85S-ZI13_am0uwWeb4Jy8DUWC3E6kIiwN2LLFpUAAAGDUJHSwA&apiKey=AIzaSyA_V9nKEZeoTOECWaD7UXuzqCzcptmmHQI&lang=en
Of course, in production, the root path would be the name of the website instead of localhost. I have my development environment running on port 3000.
Once the user lands on the authentication page, I handle the email verification in a useEffect() hook (Note: I'm using Next.js, so if you're using a different framework you might have to handle changing the URL differently):
useEffect(() => {
verifyEmail();
async function verifyEmail() {
const { actionCode } = parseUrl(window.location.search);
if (!actionCode) return;
router.replace("/authenticate", undefined, { shallow: true });
setLoadingState(LoadingState.LOADING);
try {
const response = await handleVerifyEmail(actionCode!);
if (response.alreadyVerified) {
setEmailAlreadyVerified(true);
onEmailAlreadyVerified();
return;
}
setLoadingState(LoadingState.SUCCESS);
onSuccess();
} catch (err) {
console.error(err);
onFailure();
setLoadingState(LoadingState.ERROR);
}
}
}, []);
It first checks if there is an action code in the URL, in case a user tries to access the page manually.
The onSuccess, onFailure, and onEmailAlreadyVerified callbacks just display toasts. loadingState and emailAlreadyVerified are used to conditionally render different responses to the user.

Use Cognito User Pool Groups to Invoke a Lambda function without Federated Identities

I am a newbie and feel I am missing a connection between the IAM Role on a Cognito User Pool Group and invoking a lambda function.
Use case: I’d like to use Cognito to manage my user accounts without Federated Identities/Pools (I have no need for federation). One of the features of Cognito is a concept of Groups, users can be segmented into different Groups with different associated IAM Roles all within the same pool (i.e. "admins" "customers" etc.). Through a browser app and JavaScript (in S3) users login and get an accessToken (also an idToken and refreshToken). To this point everything works great.
My problem is I want to call functions using 'lambda.invoke' for various things (i.e. read-write to DynamoDB) and hoped to avoid the extra step of using an API Gateway.
From browser-invoke-lambda-function-example I can see how to do this using 'AWS.CognitoIdentityCredentials', but from what I can tell this requires Federated Identities.
The Lambda class documentation also documents how use an IAM accessKeyId and secretAccessKey, but I want to invoke the Lambda functions using the logged in User Credentials from the Cognito User Pool/Group->Role.
To be specific - I would be greateful if someone could advise if it is possible to use lambda.invoke from a Browser JavaScript using just User Pools and User Groups (given there is an IAM role associated with the User Group)?
If it is possible, please provide some sample code/or direction where I might find out how.
PS: I realise similar questions were posed before (i.e. Is it possible to invoke a Lambda function with a cognito userpool identity?
) but the questions were thin on detail so the answers were equally vague.
The following works if you enable an "Federated Identity Pools", use the "Cognito provider" only (as federated identities are not required), and under the provider "Authentication role selection" you select "Choose role from token" and "Role resolution" DENY.
function test() { // eslint-disable-line no-unused-vars
// Prepare to call Lambda function
let pullResults;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: <<IdentityPoolId>>,
Logins: {
"cognito-idp.<<REGION>>.amazonaws.com/<<USER POOL ID>>": <<idToken>>,
},
});
const lambda = new AWS.Lambda({
apiVersion: "2015-03-31",
});
const pullParams = {
FunctionName: "<<Lambda Function Name>>",
InvocationType: "RequestResponse",
LogType: "None",
};
// Call the Lambda Function
lambda.invoke(pullParams, function (err, data) {
if (err) {
console.log(err);
} else {
pullResults = JSON.parse(data.Payload);
console.log(pullResults);
}
});
}
While this creates a Federated Identity, it does not use anything other than the Cognito Provider, and it takes the IAM Role from the Group the User belongs to.
Effectively: Using Cognito Users from User Pools, and assigning permissions based on the User Group the user is assigned to.
I'm calling Lambda from Browser-side JavaScript. If i remember correctly, you need to create IAM role that allows your user's to access lambda. Works perfectly fine like this:
var lambda = new AWS.Lambda();
var params_lambda = {
FunctionName: 'your_function_name',
Payload: JSON.stringify(userPool.getCurrentUser().username) // data you want to send to lambda
};
lambda.invoke(params_lambda, function (err, data) {
if (err) {
console.log(err);
window.location.href = "login.html";
}
else{
console.log(JSON.parse(data.Payload)); // print out data you receive from lambda
}
});
the instance var lambda = new AWS.Lambda(); needs
accessKeyId,
sessionToken
and
region

Creating users in tenant using Microsoft Office 365 API and link it to chrome Id

I manually created a user in Azure active directory for my project and I am able to get the users. I made a chrome extension and GCM provides me a ID which I want to be linked with the microsoft account.
So for each user, I want a GCM id (got this part) and an Azure AD Id linked together.
I was doing the following:
router.route('/users')
// create a user accessed at POST http://localhost:8080/api/users)
.post(function(req, res) {
// Get an access token for the app.
auth.getAccessToken().then(function (token) {
console.log(token)
var user = new User({
officeId: token,
name : req.body.name,
email :req.body.email,
chromeId : req.body.chromeId
});
user.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'User created!' });
});
});
});
However, what this does is take the auth token id, chromeId, name and email and just adds it to my mongoose database.
What can I do differently in order to get what I want to achieve? My teammate says what I am doing is correct but I checked the Azure AD and I don't see my user authorized there.
Btw, in the front-end, I ask a user to give their microsoft email and name.
Also, I merged my code with the code found here https://github.com/OfficeDev/O365-Nodejs-Microsoft-Graph-App-only
// #name getAccessToken
// #desc Makes a request for a token using client credentials.
auth.getAccessToken = function () {
var deferred = Q.defer();
// These are the parameters necessary for the OAuth 2.0 Client Credentials Grant Flow.
// For more information, see Service to Service Calls Using Client Credentials (https://msdn.microsoft.com/library/azure/dn645543.aspx).
var requestParams = {
'grant_type': 'client_credentials',
'client_id': config.clientId,
'client_secret': config.clientSecret,
'resource': 'https://graph.microsoft.com'
};
// Make a request to the token issuing endpoint.
request.post({url: config.tokenEndpoint, form: requestParams}, function (err, response, body) {
var parsedBody = JSON.parse(body);
if (err) {
deferred.reject(err);
} else if (parsedBody.error) {
deferred.reject(parsedBody.error_description);
} else {
// If successful, return the access token.
deferred.resolve(parsedBody.access_token);
}
});
return deferred.promise;
};
If you want to create use in your AAD, you can leverage the Microsoft Graph API: Create User, which is not implemented in your code or the graph.js code at github repository.
You need to implement the function yourself like:
Additionally, it seems that we have to generate the access token in Authorization Code Grant Flow to complete the operation. As in my test, I got the Authorization_RequestDenied error when I use the app-only flow access token to authorize the operation, and the graph server returned me the message:
"message": "Insufficient privileges to complete the operation."
you can refer to https://github.com/OfficeDev/O365-Nodejs-Microsoft-Graph-Connect/ for the sample.

Meteor's createUser running on client and server

I'm fairly new to Meteor and trying to grasp its concepts. I have a client code below that triggers Meteor method to create new user:
Template["signup-team"].onRendered(function(){
var validator = $('.signup-team-form').validate({
submitHandler: function(event){
var email = $('[name=email]').val();
var password = $('[name=password]').val();
Meteor.call('addNewUser', email, password, "team-captain", function(error, result) {
if (error){
return alert(error.reason);
}
Router.go("complete-signup");
});
}
});
});
The method is defined to run on both client and server. When run on the server I want it to create user and add role to account. On the client side I want to sign user in.
Meteor.methods({
addNewUser: function(email, password, role) {
check(email, String);
check(password, String);
if(Meteor.isClient){
Accounts.createUser({
email: email,
password: password,
profile: {
completed: false
}
}, function(error){
if(error){
console.log(error); // Output error if registration fails
} else {
console.log(Meteor.userId());
}
});
} else {
var id = Accounts.createUser({
email: email,
password: password,
profile: {
completed: false
}
});
console.log(id);
Roles.addUsersToRoles(id, role);
}
}
});
The server part runs fine and new user is created but on client side I get error Error: No result from call to createUser and user isn't signed in automatically.
I assume the problem is I dont need to run createUser on the client and use Meteor.loginWithPassword instead but I would really like to know the theory behind this. Thanks
Don't do this. You are rewriting core code and creating security issues needlessly.
Instead of using your addNewUser method, just call Accounts.createUser on the client. Have a onCreateUser callback handle adding the role.
In your code, you are sending the users password to the server in plaintext. When you call Accounts.createUser, the password is hashed before being sent to the server. It also takes care of logging in the new user for you.
One gotcha with adding the role though, you will not be able to use Roles.addUsersToRoles(id, role) in the onCreateUser callback, as the user object has not yet been added to the database, and does not have an _id. However you can directly add the role to the proposed user object like this:
Accounts.onCreateUser(function(options, user) {
user.roles = ['team-captain']
return user;
})
Then again, maybe you don't want all users to be team captains!

Passport-Google-OAuth Callback Not working when used in Web Service

I Have used Passport-Google-OAuth in Node.js web service project. I am using OAuth2Strategy.
The process i have used is i call the web service method to authenticate user from his Gmail account. Initially i serve the Raw HTMl which i receive from calling the Passport-google-OAuth. Which works fine.
Then i login with valid Gmail accounts. Once the Callback Url is called by google the server goes into infinite loop and calls the callback url again and again after fixed interval of time.
My Passport strategy configuration for Google is like this:
// Use the GoogleStrategy within Passport.
// Strategies in Passport require a `verify` function, which accept
// credentials (in this case, an accessToken, refreshToken, and Google
// profile), and invoke a callback with a user object.
passport.use(new GoogleStrategy({
clientID : "948630708036-2t6mestiv81gtv0s9n6iptoava4o1cpa.apps.googleusercontent.com",
clientSecret : "omugRnr7nad2yMmefiZdBaLL",
callbackURL : "http://localhost:4000/api/auth/google/callback"
},
function(token, refreshToken, profile, done) {
console.log('Inside global callback.');
// make the code asynchronous
// User.findOne won't fire until we have all our data back from Google
process.nextTick(function() {
// try to find the user based on their google id
User.findOne({ 'google.id' : profile.id }, function(err, user) {
if (err)
return done(err);
if (user) {
// if a user is found, log them in
return done(null, user);
} else {
// if the user isnt in our database, create a new user
var newUser = new User();
// set all of the relevant information
newUser.google.id = profile.id;
newUser.google.token = token;
newUser.google.name = profile.displayName;
newUser.google.email = profile.emails[0].value; // pull the first email
return done(null, newUser);
}
});
});
}));
Then i am calling the Passport from the endpoint in the service project:
passport.authenticate('google', { session:false,scope : ['profile', 'email'] });
And the Callback URL contains the following code where i am sending the returned Google account details of the user in JSON format to the client which accessed the web service intially.
function(req, res) {
console.log('Callback by Google:'+res.body+' || '+ res.headers);
console.log('Response Object:'+util.inspect(res));
passport.authenticate('google', { session : false }),function(req,res){
console.log('Callback authenticated.User: +req.user);
res.json(req.user);
}
In the Log i am getting "Callback by Google: undefined || undefined".
I am disabling sessions since this will be the API Server feeding data to various clients.
I dont know what mistake i am doing. Kindly point out any resource or example where the Passport-Google-OAuth(OAuth2Strategy) is used in a API(Web Service) server. Do i need to follow some other way. Thanks for ur help in advance.
There may be a problem in your routes. Look at the tutorial here
https://scotch.io/tutorials/easy-node-authentication-google
It's the best I have seen. And I have implemented something similar.

Categories

Resources