Im building a service + client using the following Amazon technologies:
Lambda
S3
API Gateway
Cognito
Federated Identities
Im using the serverless framework to build and deploy the API.
The client is a angular 1 app hosted in a S3 bucket.
The API endpoint is secured using AWS_IAM, the issue is that the first call to the API from my JS client is unauthorized, every call after that (using the same credentials) are authorized. I can see in my log that
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
is undefined the first time I make the call.
What I hope to achieve is that to retrieve the needed credentials so the first call succeeds.
The code below is what is used to get the credentials from Amazon Cognito and call the API. Im using the generated javascript SDK from AWS API Gateway to sign the request and call the API.
var authenticationDetails = new AWSCognito
.CognitoIdentityServiceProvider
.AuthenticationDetails(authenticationData);
var deferred = $q.defer();
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
var logins = {}
logins["cognito url" + "/" + "cognito pool id"] = result.idToken.jwtToken
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: "identity pool id",
Logins: logins
});
AWS
.config
.credentials
.get(function () {
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
var config = {
accessKey: accessKeyId,
secretKey: secretAccessKey,
sessionToken: sessionToken
}
var apigClient = apigClientFactory.newClient(config);
apigClient
.endpointName(params, body, additionalParams)
.then(function (result) {
deferred.resolve(true)
})
.catch(function (result) {
deferred.resolve(false)
});
});
},
onFailure: function (err) {
alert(err);
}
});
return deferred.promise;
}
}
The problem was that I did not refresh the credentials after retrieval. For further reference the code used is below:
AWS.config.credentials.get(function() {
AWS.config.credentials.refresh(function(error) {
if (error) {
console.log(error);
// rejects promise with error message
} else {
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
console.log(accessKeyId);
console.log(secretAccessKey);
console.log(sessionToken);
}
});
});
Related
First I have searched solutions, but I am unable to pinpoint the issue.
This is app hosted on beanstalk and works as intended after deploying. I can login, do what i need to do and close the app. If the browser is refreshed you will have to log in again (as intended).
Issue: I deployed my app around 12pm and around 2pm I tried logging in and I get a "missing credentials in config" error.
I deployed again around 2:20pm and as of now (2:58pm) it is still working as intended. But if this throws same error again, what am I possibly doing wrong?
Please see below snippet of code for my authenticating route in node js. This is a beanstalk reactjs node app.
AWS.config.update({
region: process.env.Region
});
var AmazonCognitoIdentity = require('amazon-cognito-identity-js');
const poolData = { //--Moved to env variables
UserPoolId: process.env.UserPoolId, // your user pool id here
ClientId: process.env.ClientId // your app client id here
};
const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
router.post('/api/authenticateuser', (req, res) => {
var authenticationData = {
Username: val.value.user, // your username here
Password: val.value.pass, // your password here
};
// AWS.config.credentials = new AWS.CognitoIdentityCredentials({
// IdentityPoolId: 'IDENTITY_POOL_ID',
// });
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var loginParams = {
AuthFlow: 'USER_PASSWORD_AUTH',
/* required */
ClientId: process.env.ClientId,
/* required */
AuthParameters: {
'USERNAME': val.value.user,
'PASSWORD': val.value.pass
}
};
cognitoidentityserviceprovider.initiateAuth(loginParams, function(err, data) {
if (err) {
// console.log(err, err.stack); // an error occurred
res.json(err);
} else {
console.log(data); // successful response
if (data.ChallengeName === process.env.Challenge_NEW_PASS) {
res.json({
changePass: "changePass",
session: data.Session
});
} else if (data.ChallengeName === process.env.Challenge_MFA) {
// console.log(data);
res.json({
MFA: "MFA",
session: data.Session,
user: val.value.user
});
} else {
const accessToken = data.AuthenticationResult.AccessToken;
// Add the User's Id Token to the Cognito credentials login map.
const idToken = data.AuthenticationResult.IdToken;
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: process.env.IdentityPoolId,
Logins: {
[process.env.CognitoIdp]: idToken
},
LoginId: val.value.user
});
res.json({
accessToken,
idToken,
user: val.value.user,
test: data
});
}
}
});
})
Cognito session expires after 1 hour by default. Your temporary access key and secret expires along with it and so does the identity token and session token. You will have to use the refresh token every hour to get new credentials. If you want to avoid this, you can try using Amplify Authentication, which will automatically do the refreshing for you.
I have a cognito userpool and i can successfully log into my app with the following code:
const authData = {
ClientId : '2222222222222', // Your client id here
AppWebDomain : '1111111111.auth.us-east-1.amazoncognito.com',
TokenScopesArray : ['openid'],
RedirectUriSignIn : 'https://app.domain.com',
RedirectUriSignOut : 'https://app.domain.com'
};
const CognitoAuth = AmazonCognitoIdentity.CognitoAuth;
const auth = new CognitoAuth(authData);
auth.userhandler = {
/**onSuccess: <TODO: your onSuccess callback here>,
onFailure: <TODO: your onFailure callback here>*/
onSuccess: function(result: any) {
console.log("COGNITO SUCCESS!");
console.log(result);
},
onFailure: function(err: any) {
console.log("COGNITO FAIL!");
console.log(err);
}
};
auth.getSession();
const curUrl = window.location.href;
auth.parseCognitoWebResponse(curUrl);
I now have an auth object that I would like to parlay into some sort of credentials for the aws-sdk that i have so that i can list items into an S3 bucket, assuming the correct policies in my attached roles.
something like this, but realize this doesn't work:
AWS.config.credentials = auth.toCredentials(); //<== hoping for magic
const s3 = new AWS.S3();
s3.listObjectsV2(listObjectsV2Params, function(err: any, data: any) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data.Contents[0]); // successful response
});
Is this possible, and if so how do i do that?
UDPATE
Accepted answer worked and was a big help, adding some additions for clarity along the lines of trouble I ran into.
const creds = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:b111111-1111-1111-1111-1111111', // <-- This is in your Federated Identity if you have that set up, you have to "edit" the identity pool to get it, logging into cognito its a different screen.
Logins: {
"cognito-idp.us-east-1.amazonaws.com/us-east-1_BBBB1BBBBV2B": result.idToken.jwtToken // <- this login [POOL ID] is not the pool ARN, you need it in this format.
}
});
this link helped me.
This is possible and following are the steps.
To allow AWS resources access for AWS Userpool Users, it also requires to configure AWS Identity Pools registering the UserPool as a provider.
The IAM Role assigned for the authenticated user needs to have access to S3.
Using the AWS SDK for Identity Pools, UserPools JWT token can be exchanged for temporal AccessKey and SecretKey to use AWS SDK for S3.
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IDENTITY_POOL_ID,
Logins: {
[USER_POOL_TOKEN]: result.idToken.jwtToken
}
});
AWS.config.credentials.refresh((error) => {
if (error) {
console.error(error);
} else {
console.log('Successfully logged!');
}
});
Inside the AWS.config.credentials.refresh callback you can call S3 since, the method internally will handle getting temporal credentials.
all!
I am currently facing a problem to authenticate users from my Cognito UserPool using a lambda function built using ClaudiaJS+Claudia-API-Builder and exposed on API Gateway through "/api/auth". I can't seem to find a way to succeed on calling the cognitoUser.authenticateUser.
All the resources I've found so far are for calling this API via Browser, but I couldn't find any example of doing so on backend.
I've already tried to deal with the response as a regular callback, promises and object listeners, but none of them seemed to work and I always end up getting a {"message": "Internal server error"} message as response from my REST service.
It's also important to say that I'am fairly new to Node and Async programming, but would you mind please reviewing this piece of code and telling me the correct way to do so, IF this SDK was designed to be invoked from backend instead of frontend?
My App.js
var ApiBuilder = require('claudia-api-builder');
var api = new ApiBuilder();
//Routes
var usuario = require('./routes/usuario');
var auth = require('./routes/auth');
//Caminho default da API
var API_ROOT = "simc/api";
//User Routes
api.get(API_ROOT + "/f1", usuario.f1);
api.get(API_ROOT + "/f2", usuario.f2);
//Auth Routes
api.get(API_ROOT + "/login", auth.login);
module.exports = api;
My auth route
"use strict"
var AWS = require('aws-sdk');
var AWSCognito = require('amazon-cognito-identity-js');
var authAPI = {};
authAPI.login = function() {
var authenticationData = {
Username: 'xxxxxx',
Password: 'xxxxxx',
};
var authenticationDetails = new AWSCognito.AuthenticationDetails(authenticationData);
var poolData = {
UserPoolId: 'us-east-1_xxxxxx',
ClientId: 'xxxxxx'
};
var userPool = new AWSCognito.CognitoUserPool(poolData);
var userData = {
Username: 'xxxxxx',
Pool: userPool
};
//Trying to use Promises
var cognitoUser = new AWSCognito.CognitoUser(userData);
return cognitoUser.authenticateUser(authenticationDetails)
.then(function(result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
//Use the idToken for Logins Map when Federating User Pools with Cognito Identity or when passing through an Authorization Header to an API Gateway Authorizer
console.log('idToken + ' + result.idToken.jwtToken);
return result;
})
.catch(function(err) {
console.log(err);
return err;
});
//Trying to use Object Listeners
var request = cognitoUser.authenticateUser(authenticationDetails).promise();
return request.on('success', function(response) {
console.log('access token + ' + response.getAccessToken().getJwtToken());
//Use the idToken for Logins Map when Federating User Pools with Cognito Identity or when passing through an Authorization Header to an API Gateway Authorizer
console.log('idToken + ' + response.idToken.jwtToken);
return response;
}).
on('failure', function(err) {
console.log("Error!");
return err;
}).send();
};
module.exports = authAPI;
I have modified the original handler because it also doesn't seem to work properly.
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
/*Use the idToken for Logins Map when Federating User Pools with Cognito Identity or when passing through an Authorization Header to an API Gateway Authorizer*/
console.log('idToken + ' + result.idToken.jwtToken);
return 'idToken + ' + result.idToken.jwtToken;
},
onFailure: function(err) {
return err;//alert(err);
},
});
Does anyone have any idea of what I am doing wrong?
Thanks in advance,
Enrico Bergamo
var authenticationDetails = new AWS.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
Use this one to include the function.
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:2ce7b2c2-898f-4a26-9066-d4feff8ebfe4'
});
// Make the call to obtain credentials
AWS.config.credentials.get(function(){
// Credentials will be available when this function is called.
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
//var identityId = AWS.config.credentials.identityId;
return res.send({
accessKeyId: accessKeyId
});
});
All of the variables have null value. Why? What am i doing wrong? Is there another way to access it?
Also i am supposed to send a secretkey and token to retrieve a session key
UPDATE:
When i try this method, I get an error saying:
Error: NotAuthorizedException: Unauthenticated access is not supported for this identity pool.
AWS.config.credentials.get(function(err) {
if (err) {
console.log("Error: "+err);
return;
}
console.log("Cognito Identity Id: " + AWS.config.credentials.identityId);
// Other service clients will automatically use the Cognito Credentials provider
// configured in the JavaScript SDK.
var cognitoSyncClient = new AWS.CognitoSync();
cognitoSyncClient.listDatasets({
IdentityId: AWS.config.credentials.identityId,
IdentityPoolId: ""
}, function(err, data) {
if ( !err ) {
console.log(JSON.stringify(data));
}
return res.send({
data: data
});
});
});
The exception you are seeing means that you have not set up your identity pool to allow unauthenticated identities.
You are not passing any logins in the logins map when you call get credentials, which means your user is un authenticated (which is not allowed by your identity pool).
Here is some documentation describing how to authenticate using external identity providers:
http://docs.aws.amazon.com/cognito/latest/developerguide/external-identity-providers.html
I posted here on the AWS forum
I'm using the aws-js-sdk v2.2.3 with the following code. I get data back with Credentials populated. When I try to use the credentials I get the error that they are invalid. I'm using the developer authenticated identities flow. I have both roles Auth & UnAuth. My identity pool looks like it's correct. The trust relationships look like they are pointing to the correct identity pool id. There are policies attached to the Auth role for S3 & DynamoDB. I'm at a loss. Any help would be appreciated.
javascript client side:
var cognitoidentity = new AWS.CognitoIdentity({region: 'us-east-1'});
var params = {
IdentityId: user.cognito_id,
Logins: {
'cognito-identity.amazonaws.com': user.cognito_token
}
};
cognitoidentity.getCredentialsForIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data.Credentials);
});
I console.log the Id & SecretKey and they are filled in.
var aws_creds = StateService.get('user').aws_creds;
console.log(aws_creds.AccessKeyId);
console.log(aws_creds.SecretKey);
AWS.config.update({ accessKeyId: aws_creds.AccessKeyId,
secretAccessKey: aws_creds.SecretKey,
endpoint: ENV.aws_dyndb_endpoint,
region: 'us-east-1'
});
var dynamodb = new AWS.DynamoDB();
console.log("user obj: ", StateService.get('user'));
var params = {
TableName: games_table_name,
KeyConditionExpression: "Id = :v1",
ExpressionAttributeValues: {
":v1": {"N": id}
}
};
return dynamodb.query(params);
My Solution
What I came up with was to explicitly refresh the credentials versus get them lazily when I created a DynamoDb object for instance. Here's the function I use which returns a promise & resolves when the credentials are refreshed.
refresh: function() {
var deferred = $q.defer();
AWS.config.region = 'us-east-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: COGNITO_IDENTITY_POOL_ID,
IdentityId: COGNITO_ID,
Logins: 'cognito-identity.amazonaws.com'
});
AWS.config.credentials.refresh(function(error) {
if ((error === undefined) || (error === null)) {
$log.debug("Credentials Refreshed Success: ", AWS.config.credentials);
var params = {
region: 'us-east-1',
apiVersion: '2012-08-10',
credentials: AWS.config.credentials
};
$rootScope.dynamodb = new AWS.DynamoDB({params: params});
deferred.resolve();
}
else {
$log.debug("Error refreshing AWS Creds:, ", error);
deferred.reject(error);
}
});
return deferred.promise;
}
If you want to use Cognito credentials to call other AWS services, I recommend you use the high-level AWS.CognitoIdentityCredentials object from the Javascript SDK, instead of calling the service API directly.
You can find more information about how to initialize and use AWS.CognitoIdentityCredentials in the Cognito Developer Guide:
Developer Authenticated Identities
Albert
The flow is like this: You ask the CognitoIdentityCredentials for a IdentityId, the IDentityId is supposed to track users accross devices and across Identities providers like (Facebook, Google, TWitter, etc.) then you with that ID you ask for a role attached to your pole CognitoIdentity, after you get the token, you ask the STS.assumeRoleWithWebIdentity for a temporary credentials with the appropriate roles attached to your pole.
Here is an example of how I did it:
// set the Amazon Cognito region
AWS.config.region = 'us-east-1';
// initialize the Credentials object with our parameters
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:YMIDENTITYPOLEID',
});
// We can set the get method of the Credentials object to retrieve
// the unique identifier for the end user (identityId) once the provider
// has refreshed itself
AWS.config.credentials.get(function(err) {
if (err) {
console.log("Error: "+err);
return;
}
console.log("Cognito Identity Id: " + AWS.config.credentials.identityId);
params = {
IdentityId: AWS.config.credentials.identityId
}
// Other service clients will automatically use the Cognito Credentials provider
// configured in the JavaScript SDK.
// Get the Role associated with the id coming from the pool
var cognitoidentity = new AWS.CognitoIdentity();
cognitoidentity.getOpenIdToken(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
}else{
// Get temporoarly credientials form STS to access the API
var params = {
RoleArn: 'ROLE_OF_YOUR_POLE_ARN', /* required */
RoleSessionName: 'WHATEVERNAME', /* required */
WebIdentityToken: data.Token, /* required */
};
var sts = new AWS.STS()
console.log(data); // successful response
console.log(data.Token)
sts.assumeRoleWithWebIdentity(params, function(err, data) {
if (err){
console.log(err, err.stack); // an error occurred
}else{
console.log(data); // successful response
// Now we need these credentials that we got for this app and for this user
// From here we can limit the damage by
// Burst calling to the API Gateway will be limited since we now that this is a single user on a single device
// If suspicious activities we can drop this user/device
// The privileges are limited since the role attached to this is only the API GateWay calling
// This creds are temporary they will expire in 1h
var apigClient = apigClientFactory.newClient({
accessKey: data.Credentials.AccessKeyId,
secretKey: data.Credentials.SecretAccessKey,
sessionToken: data.Credentials.Token, //OPTIONAL: If you are using temporary credentials you must include the session token
region: AWS.config.region // OPTIONAL: The region where the API is deployed, by default this parameter is set to us-east-1
});
// Call the get to test
apigClient.deviceGet({}, {})
.then(function(result){
//This is where you would put a success callback
console.log(result)
}).catch( function(result){
//This is where you would put an error callback
});
}
});
}
});
});
NB: This was a test to get access to the API Gateway service, but it is not different to get access to other services, it depends on the pole you configure it and its attached services.
If you have credential for a user created in IAM you don't need the temporary token, but if you use this flow you have to include it.
Another point, limit the access to the services on your pole, keep in mind that is a publicly given key, every one can use it to get access to your stuff.
STS.assumeRoleWithWebIdentity is used because we are on the web, in the AWS JS SDK, if you use iOS or android/java or Boto, you have to use STS.assumeRole.
Hope this helps.