I am writing an Ember app where I want to allow users to upload pdf files, and I am using AWS S3 for file storage. Because I don't want to hard code my AWS creds, I am using AWS Cognito to create temporary credentials to authenticate users to S3 when they want to upload/download files. I created an Identity Pool on AWS for the users, and have configured the associated IAM roles for authenticated and unauthenticated users (authenticated users get read only access to one of my S3 buckets). I am using my Rails backend (which uses Devise) as the authentication provider for my identity pool. For my frontend authentication I am using Ember Simple Auth with the provided Devise authenticator, so the user must be logged in in order to request a token. Here are the steps I take to obtain the temporary credentials:
1) Get an AWS Cognito Identity Id and Token from my backend (using get_open_id_token_for_developer_identity method from Ruby AWS SDK on my backend)
2) Create a new CognitoIdentityCredentials object using the Identity Id and Token to obtain temporary credentials, and store the creds in a cookie (using js-cookie).
Code:
var AWS = window.AWS;
AWS.config.region = "us-west-2";
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-west-2:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
IdentityId: response.identity_id,
Logins: {
'cognito-identity.amazonaws.com': response.token
}
});
AWS.config.credentials.get(function() {
var date = new Date(AWS.config.credentials.expireTime);
Cookies.set("cognito_creds", { accessKeyId: AWS.config.credentials.accessKeyId, secretAccessKey: AWS.config.credentials.secretAccessKey, sessionToken: AWS.config.credentials.sessionToken }, { expires: date } );
});
I understand that storing important information such as AWS credentials in a cookie is a big no-no, and is definitely not secure. But keep in mind, I am using HTTPS, the credentials expire after an hour, and the IAM role associated with the credentials only gives read-only access to one of my S3 buckets.
My question is: Is it worth the security risk to store these creds as cookies so that I don't have to get a token from my backend every page refresh, or does this approach leave me too vulnerable?
EDIT: To clarify, the alternate solution that I am thinking of would be to store the credentials as properties on an Ember service on the frontend, and get a token from my backend every time the page reloads, but this doesn't seem very efficient.
This previous question has some similar aspects, it might be worth a read. The short version is, either should be alright. It sounds like you've scoped down the credentials perfectly, so you've limited the blast radius if you opt to save them.
If you want to keep the latency down, it isn't a bad way to go. If you're willing to sacrifice that latency or are concerned with the privacy of what could be read from S3, you could go for getting them remotely.
Related
In my js file, I am accessing my AWS sns, but the main issue is that currently, I am hard coding my IAM access and security keys... Is there a way to avoid doing this by using a temporary key or hiding my keys? I have posted a snippet on how I am updating the config below.
var AWS = require('aws-sdk');
AWS.config.update({
region: < 'My Hard coded region' >,
credentials: {
accessKeyId: < 'My Hard coded Access Key' >,
secretAccessKey: <'My Hard Coded Security Key'>
}
});
...
PLEASE HELP.
It's very hard to comment without knowing more about your code.
The react native is a mobile app. You will need a service to authenticate your mobile app users against the service. One such service is amazon Cognito.
Userpool
Amazon Cognito has a concept called user pool. The user pool is where the users are stored.
When you authenticate a user against a user pool, you will get a authorised token in return. Your app can store this token and send it to your backend services. The backend services can validate the token to confirm the identity of the user.
Identity pool
The identity pool is a concept where temporary AWS credentials are issued in exchange for an authorised token. This is the temporary AWS credentials, using which you can call aws services directly from the mobile app.
Hope this helps.
here is an article for you to read - https://pusher.com/tutorials/serverless-react-native-aws-amplify
Yes there are much more secure ways to handle credentials. Hard coding them is the least secure.
While it is possible to do so, we do not recommend hard-coding your AWS credentials in your application. Hard-coding credentials poses a risk of exposing your access key ID and secret access key
See https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html
Also see From Best Practices for Managing AWS Access Keys:
Don't embed access keys directly into code. The AWS SDKs and the AWS Command Line Tools allow you to put access keys in known locations so that you do not have to keep them in code.
See https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html
The most secure method is to load the credentials from AWS Identity and Access Management (IAM) roles for Amazon EC2.
Each SDK has a way to do this. Check your SDK documentation for loading credentials from IAM.
Here is a good overview for credentials best practices, with examples in Java: https://aws.amazon.com/blogs/developer/credentials-best-practices/
I came across this stackoverflow while googling how to solve this problem. Here's the approach I ended up using to solve this problem in case this helps anyone else. It will expand on the identity pool answer provided earlier.
My setup was this:
Static website hosted on s3, so no way to make AJAX calls back to the server itself to bypass hardcoding credentials.
The solution I ended up using was Cognito identity pools as mentioned. The setup involved creating an identity provider in IAM. We were using an company internal OpenID identity provider. Then the next step was to create an identity pool in cognito that pointed to that identity provider. Next was to provision an IAM role for signed in users of that identity pool to give them access to whatever resources you are trying to secure.
On the s3 website, we had already integrated with the internal OpenID identity provider, so it was just a matter of extracting the token and passing it to Cognito.
The program flow is as follows:
User logs in to open ID internal system.
This system returns a token
Code extracts token and then creates a Cognito Identity Credentials
https://docs.aws.amazon.com/cognito/latest/developerguide/getting-credentials.html
The below is a code snippet copy pasted from the docs linked above. Note that the Logins object is where we will pass the token. The key will be the "provider" field value configured in IAM.
AWS.config.region = 'us-east-1';
// Configure the credentials provider to use your identity pool
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'IDENTITY_POOL_ID',
Logins: { // optional tokens, used for authenticated login
'graph.facebook.com': 'FBTOKEN',
'www.amazon.com': 'AMAZONTOKEN',
'accounts.google.com': 'GOOGLETOKEN',
'appleid.apple.com': 'APPLETOKEN'
}
});
// 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;
});
The call to cognito will return temporary credentials including a session token, which you can now pass to your other aws clients.
How to create a web application that takes user inputs and stores image files uploaded by authorized users to AWS? The users who have permission to upload files are web users, not AWS IAM users. Ideally, they access this web from a url with query parameter representing this authorized user. I am thinking about creating a static website in S3, storing user input and image files in a S3 bucket subfolder. Each authorized user only have read\write permission to its own subfolder. However, since S3 is serverless, I need details on how to use lambda and API Gateway or other AWS services to process request from web form and upload files to S3. It seems like I can use Drupal or WordPress Webform plugin to create the webform and upload files to S3. In this case, I need a EC2 instance for Drupal\WordPress instead of a S3 static website. I am looking for suggestions on the best architecture for this project. Any tutorials, instructions, and sample codes are welcomed. Thanks in advance.
If you are require your users to authenticate against you application, then you need to store their credentials in some database (i.e DynamoDB). To do that, you need some back-end service that will perform authentication process - store users' credentials in a DB, retrieve users's credentials from DB, verify credentials.
You can either write your own server, or go with serverless Lambda approach with API Gateway. But the important thing here is that you need to have this back-end logic. There is no way to achieve it using only static S3 web hosting (but it can be part of the overall system).
AWS IAM is used in this scenario only to create role that allows service - EC2/Lambda - to perform read/write operation on the S3 bucket and DynamoDB. Your application users have nothing to do with IAM.
Flow of the application would be:
user signs up to your application
your application creates a record in a DB with the user credentials and pointer to some S3 resource that will be unique to this user
when signed in user tries to upload image the data is sent to your application
your application retrieves requester's record from DB, looks for the S3 pointer and upload the image there
From the architectural perspective, there is a lot of factors to take into consideration when designing such system - amount of traffic, requirements on CI-CD an such.
If you want something pretty simple, then you can create your REST API using API Gateway, where each resource-method will have appropriate lambda function assigned to it (these function will perform the above mentioned logic). You can use a custom authentication processes or you can leverage AWS Congnito. Finally, you can host your webpage (assuming that it is static in this setup) on S3 making ajax calls against your API gateway.
The idea is to use AWS Cognito. Cognito Federated Identities allows to vend temporary access tokens to authenticated users. It integrates with Login With Amazon, Facebook, Google or anything that speaks OpenID or SAML.
Your Singe Page Web app can access Cognito through the AWS SDK for Javascript.
So the idea is to propose an authentication on your web page. I am using Login with Amazon or similar. You can also choose to use Cognito User Pool (where Cognito stores user profiles and provides signin, signup, password recovery etc)
Once authenticated, your Javascript code can call refresh() on AWS.Config.credentials, passing the access token received from the authentication step above. refresh() will, in turn, call Cognito's API to receive a temporary and user specific access token.
function initializeCognito(access_token) {
return new Promise((resolve, reject) => {
// Initialize the Amazon Cognito credentials provider
AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:cognito pool id', // < -- insert your cognito pool ID here
Logins: {
'www.amazon.com': access_token
}
});
console.log("Calling Cognito to refresh AWS Credentials");
// get AWS credentials
AWS.config.credentials.refresh(function (err) {
if (err) {
console.log("Error when calling Cognito");
console.log(err, err.stack);
reject(err);
} else {
console.log("Cognito credenials received ");
resolve(AWS.config.credentials);
}
});
});
}
Once this is done, the rest of your code can call any AWS SDK API to call backend services, incl S3, DynamoDB ...
Here is an example for DynamoDB :
function insertIntoDDB(profile, text) {
return new Promise((resolve, reject) => {
var dynamodb = new AWS.DynamoDB();
var params = {
Item: {
"cognitoid": { //hash key
S: profile.cognito_id
},
"userid": {
S: profile.user_id
},
"text": {
S: text
},
"creationtime": { // sort key
N: Math.round(new Date() / 1000).toString()
},
"expirationtime": { // 1 month later ?
N: (Math.round(new Date() / 1000) + (1 * 60 * 60 * 24 * 30)).toString()
}
},
TableName: "my_table"
};
dynamodb.putItem(params, (err, data) => {
if (err) {
console.log("Error when calling DynamoDB");
console.log(err, err.stack); // an error occurred
reject(err);
} else {
// console.log(data); // successful response
resolve(data);
}
});
});
}
The AWS SDK manages the authentication for you, as you can see, there is no code involved to pass an access key, secret key. The access key and secret key is generated for that specific user by Cognito and stored in AWS.config.credential object. The access key and secret key is limited in scope and limited in time.
To limit the scope, on the Cognito console, you define the IAM role to associate with authenticated users, granting them the permissions to access only the services and operations required by your code. In addition, S3 and DynamoDB allows for fine grained permission, allowing you to restrict write access to specific keys (folder) in your bucket or row in your table.
You can see this technique in action at https://alexademo.ninja/skills/myteacher/index.html I wrote this to work with the "My Pronunciation Teacher" Alexa Skill.
There is also a full tutorial that will show you how to use the AWS SDK for JavaScript to upload pictures from a web form to S3 in the AWS Documentation. This code also leverage's Cognito and it's quite close to what you want to achieve.
Using this technique, no backend server is required.
I have an Angular app that uses the AWS javascript SDK, and can successfully login with Facebook and Google. I am able to create the Cognito identity and I see the identity in my Federated Identity pool. I am also able to use my IAM authenticated API endpoints (I see the user's cognitoId in the Lambda I am calling).
I want to use the user's email address as the key in the database (DynamoDB), so I am trying to figure out how to access the user's email address from a trusted source, either Cognito (preferred) or the Authentication Provider's JWT.
What is the best way to access the user's email address?
Update: On the client side (javascript) I'm using Federated Identities with Google and Facebook tokens. The social login is working, and I can get authenticated with Cognito just fine, passing the Logins:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:****',
Logins: {
'accounts.google.com': authResult['id_token']
}
});
I then do AWS.config.credentials.get() and I can get things like AWS.config.credentials.identityId, but I can't find accessToken or the methods to call to get the JWT. I believe I need to call result.getAccessToken().getJwtToken(), but I can't find it.
All of the examples where they get the accessToken seem to use .authenticateUser(authenticationDetails()), but the details include a user name and password (not Federated login).
tldr; Send the accessToken post-authentication to lambda and use CognitoIdentityServiceProvider.getUser() to obtain the attributes.
I spent hours fumbling around with the ID tokens and from what I can tell, the only way to get a user's actual identity is to use a combination of the accessToken provided from a successful authentication and the getUser call from the identity provider. This only appears to be for the NodeJS SDK and nothing else.
I am using Amazon's API Gateway for my application and Lambda in the background. After a successful authentication, you can access the accessToken and feed it to a Cognito server provider. From there, you can save the details locally, but it presents a large security issue if you plan to use this information on the backend. Since it's local storage, the user can just adjust the user attributes to whatever they like.
To get around this, you can pass the accessToken to the lambda function directly and perform the same process on the server. Doing this ensures that the user attributes have not been manipulated or tampered with. Be sure to use HTTPS on your API to avoid leaking the accessToken to others who could use the AWS SDK to get the internal details.
var AWS = require("aws-sdk");
AWS.config.region = '<REGION-ID>';
exports.lambda_handler = function(event, context) {
var accessToken = event['accessToken'];
var provider = new AWS.CognitoIdentityServiceProvider();
provider.getUser({
AccessToken: accessToken
}, function(err, obj) {
context.succeed({
'user': obj
});
});
};
If a user can only access his or her dataset, why is IdentityId a field which has to be set on this API call (ref). Couldn't the IdentityId be inferred from the AWS credentials? Or if they don't have to match, then technically wouldn't that mean that any malicious user could access that dataset, if he or she had the origin user's Cognito ID? Or does the call to ListDatasets re-validate the Cognito ID against the identity provider's token? In which case, what should I cache in my website as a cookie, so that a user won't have to log in every time?
Right now I am caching the CognitoId, but I am worried that if exposed that data will be compromised (or brute force attack), and if it isn't a security hole, then will I have to have the user re-authenticate against their provider each time they want to sync their data? What is the solution here?
Any help would be appreciated.
Edit 1:
I think what I have found is a caching problem with CognitoSync being used in the web browser and this question which may point to the same issue. Here is the auth flow I am expecting:
Login to Web Federated Auth Provider (OIDC, such as Google) => returns temporary token.
Use token to get temporary credentials and IdentityId from Cognito.
Cache Something.
Activate CognitoSync Manager to synchronize.
Some Time Later:
Using the cached something, rerun synchronization.
Problems:
If I cache the OIDC provider token, I can regenerate the AWS credentials, except it expires after 1 hour.
If I cache the AWS credentials, I can reissue Sync, except the credentials als expire.
If I cache the IdentityId I can't synchronize, So what do I cache?
Conclusion:
If AWS Credentials are needed to issue synchronization call, why does the listDatasets require IdentityId again in the API call, can't AWS CognitoSync reverse lookup the credentials to determine what the IdendityId assigned to those credentials are?
Does the [cognito sdk](https://github.com/aws/amazon-cognito-js) actually work?
Am I forced to use DynamoDB directly to do synchronization, if I want to avoid the Auth flow every time a user wants to synchronize?
There is a [Caching Provider](http://docs.aws.amazon.com/AWSAndroidSDK/latest/javadoc/com/amazonaws/auth/CognitoCachingCredentialsProvider.html) for Android SDK, does something like this not exist for the browser? (Does that even work?)
[Developer docs](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CognitoIdentityCredentials.html) reference refreshing the token, this can only be done when the current token is still active :(.
It's not quite either. Cognito will validate the identity id given against the identity id for which the AWS credentials were created. So, for your concern about spoofing, it would not allow user A to get user B's sync data just by knowing their identity id.
There seems to be two issues:
Cognito validates the AWS Credentials against the IdentityId, so there is no security hole. However it still doesn't make sense for the API to contain that as a parameter, Amazon probably will needed to update that API.
The problem that you are running into is that identity provider should provide a way to get the necessary access token for each request authorization without requiring the user to grant permissions everytime. Authorize with the provider and then take the access token (or id_token) and send it Cognito as the Login token for that provider each time you want to synchronize. You actually don't need to cache anything.
For Google the request looks like:
var nonce = createNonce();
setCookie('GoogleAuthNonce', nonce, 1);
var paramString = $.param({
response_type:'id_token',
scope: 'openid', //email, profile',
client_id: GOOGLE_CLIENT_ID,
nonce: nonce,
login_hint: 'service_email_address',
prompt: 'none',
redirect_uri: 'http://service.com'
});
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?${paramString}`;
I have an iOS app that is authenticating using Facebook & Cognito. I am able to make calls to a local ReST service with the following AWS credentials from my iOS app extracted from Cognito:
accessToken: {a_token}
secretToken: {a_token}
sessionToken: {a_token}
I want to get the current Cognito identity from the AWS credentials so I can use that as a key in my DynamoDB table and handle authorization (I don't want to use IAM roles for this). I know I can directly invoke DynamoDB from the iOS app but I do not want my app directly calling my data storage (in case I want to change data storage, add caching, etc...). Is it possible to get the current Cognito identity from the current AWS credentials?
I do not want to pass the identity id with the request, as is defeats the purpose of passing the tokens.
I do not want to use AWS API Gateway either.
From your credentialsProvider you can call getIdentityId() and in continueWithBlock' you can accesscredentialsProvider.identityId`
I am sorry but there is no way to get an identity id from AWS credentials.
Is there a reason you do not want to call Amazon DynamoDB from the device directly using credentials vended by Cognito Identity? Using IAM roles you can restrict the usage for an identity to be able to write to only their records and this is the approach we recommend currently.
You can refer to our blog about fine grain access with DynamoDB using Cognito.