Accessing current authenticated users info - amazon-web-services

I have a lambda function that is triggered whenever a user uploads an image to an S3 bucket. I'm trying to write the generated url of that image to a DynamoDB database along with the email of the user who uploaded said image, which should be the user that is currently logged in.
I've gotten these attributes before by doing
event.request.userAttributes.email
But that was done in a Cognito triggered post-confirmation lambda function, so that information was stored in the event parameter of the handler function. In this scenario, I'm not sure if that information is sent along in the event. Any idea how I'd get access to information like that? I've been reading up JWT ID Tokens, but I haven't figured out how to access that or if that's the correct and safe approach.

I am afraid you will have to handle it yourself. One option which you may like is to use custom object metadata to store the information about the uploading user:
https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html
You can then retrieve the metadata together with the object and continue from that point.

Related

Is there a way to trigger a Google Cloud Storage download object event?

Whenever an object is read/downloaded I want to trigger a Google Cloud Storage event to be listened by the Google Cloud functions to handle this event calling an auth endpoint to check if the user has access to the requested file, if so, the user receives the file.
Use case: When a user requests a stored file I want to first check if the user is allowed to download that specific file by calling an auth endpoint to authenticate and authorize the user access to the file. And only if he/she is allowed the file will be downloaded.
I've only found 4 event types supported by Google Cloud Storage Triggers: finalize, delete, archive and metadataUpdate.
https://cloud.google.com/functions/docs/calling/storage#event_types
Answering your question: "Is there a way to trigger a Google Cloud Storage download object event?"
Basically you are asking how to "download" an object. But under your use case would be: How to let some user download an specific object.
A solution for that would a Signed URL, which can let you provide the user with an URL to download the object for a limited time. If you redirect the user directly to that URL the download would start immediately.
You can use signed URL which essentially can be attached to a file is a URL which permits access to private objects stored on GCS. It is a means of keeping objects secure, yet grant temporary access to a specific object. Note there is no concept of SIgned URL for a directory of files.
It is created via a hash calculation based on the object path, expiry time and a shared Secret Access Key belonging to an account that has permission to access the GCS object. As such, each signed URL is unique.
Essentially, anyone who has this signed URL can do any ops which this URL is allowed to do. The only way to secure this further in case of any breach would be to rotate signing keys or changing the signing keys.

How to add user to own database during registration in a 'transactional' way?

I am trying to use AWS Cognito for my app.
I can register the users successfully to AWS Cognito, but I want to be able to also store them in my database (with my own API), too, as part of the sign-up process.
I saw that I could use a trigger function like Post confirmation, but as far as I understand, if saving the user to my database fails, then the user will still be created in the Cognito user pool.
I want to treat the sign-up process in a transactional way, so that if saving the user in my database fails, then the entire process fails.
So, it would look something like this:
Aws Cognito Sign Up -> Send_to_database_trigger ----fails----> user is not created
\---success--> user is created
Is there any way to achieve what I want? Thanks.
You can use the Pre Sign-up Lambda Trigger.
It gets the username and all the user attributes in the event and if it fails the user won't be signed up in Cognito.
In your case, you will save your user to your database in this trigger (after other possible validations) and will fail if saving to your database has failed. This way you can be sure that if a user exists in Cognito it also exists in your database.
If you need to save the sub then you can implement both Pre Sign-up and Post Confirmation and update your database record with the sub in the Post Confirmation trigger.

AWS Cognito custom auth - sending metadata to a challenge lambda functions

I'm developing a custom passwordless auth to sign into a Cognito user pool. I will describe what I'm trying to implement in case anything is silly. I want a user to enter their email address, then receive a magic login link via email, and when they click on that be taken back to the site and be logged in.
This uses custom auth lambda functions to define/create a challenge with a time based password and send it to the user in an email. I am having a couple of problems:
Problem 1)
When the user returns with the code they might not be in the same browser/device and certainly won't be in the same tab so they don't have the session, meaning I need to call cognitoUser.initiateAuth again. This goes through the define/create challenge lambdas again so a second email gets sent even though at this point the user is coming from the email link so already has the code. Note: the session id is not available in the event object when the challenge is created, also I've read these sessions only last 3 minutes and my time based passwords will last ~15minutes, so I don't think I can include the session id in the email.
Problem 2)
You can login from a few places (browser, android app, etc) and I would like to be able to include the url or at least protocol as a parameter to control what gets sent in the email, e.g. if you entered your email address in the android app then the email you get would be myapp://login?code=xxx and if you did it on the web it would be https://example.com/login?code=xxx
It seems like I would be able to implement both of these to work properly if only I could find some way to send custom metadata through to the DefineChallenge and CreateChallenge lambda such that it would appear in the event object. I thought adding ValidationData to the AuthenticationDetails object would do this, but that information doesn't appear in the event object in the Lambda fns.
The workaround I've found is to create a new client id for every situation - one for initiating auth, one for redeeming token, and repeat for each different protocol. But that is a lot of client ids quickly - a pain to mantain and clumsy.
So tl;dr is: I want to send custom metadata from my cognitoUser.initiateAuth(...) call in JS and have it available in my Define/Create Challenge lambda fns.
You can split the authentication process into multiple custom auth challenges. This allows custom auth state to be supplied via the challenge response as client metadata.
Auth session state must be persisted in a database in order to be shared between devices.
Your custom login flow will probably have two challenge steps: the first prompts for auth type and the second prompts for the secret code. The action taken by the "Create Auth Challenge" Lambda will depend on the auth type. If the auth type is "Email" then the secret code and magic link are generated, stored in DynamoDB and emailed. If the auth type is "MagicLink" then the secret is loaded from DynamoDB. Clicking on a Magic link will initiate a new auth session and automatically supply all the challenge answers.
There are a few other things to consider:
Your magic link needs to encapsulate the Cognito username as well as the one-time secret and probably some other session id that is used as a key in dynamodb.
You probably should not put app-specific links into your emails. Instead associate your domain with your app and/or leverage the redirect URI parameter of your web-based login page.
You can also access custom Cognito user attributes from the Lambda function which can be used to indicate user login preferences (eg Email vs SMS for login codes).

Save AWS Cognito Users in DynamoDB

I recently started experimenting with AWS AppSync but I had some questions around AWS Cognito.
I would like for users to be able to authenticate with Facebook but I need their profile picture, name and email as data for my public user profiles in my app. So far, I noticed Cognito integrates with Facebook Auth but it does not allow access to the user information and this info does not get saved in a DynamoDB table.
My question is, how can I create a new User in DynamoDB when Cognito receives a new sign in, or return an existing user/id when the user already exists in the db.
I was trying to achieve the same a few weeks ago.
After reading the docs for hours, I realised that Cognito may not help us in regards to the data that comes back from FB or how to save it.
I ended up doing the following:
(1) Using FB-SDK, pulled in the user data.
(2) Invoked a Lambda function that saved this data (like FB_id,etc) to DynamoDB.
(3) If user logged in again, their FB_id (or email) was used to check against DynamoDB entries to retrieve their data.
If Cognito is able to help us and I missed it somehow, I would love to know.
Happy Coding!
You could use custom attributes and federating user from Facebook in your user pool to achieve this. Here are the steps at high level to do this.
You will first have to define custom attributes for the profile information you want to save in each user profile.
Define attribute mapping to link the custom attributes to Facebook attributes you want to save.
Build you application using Cognito hosted pages and federation to allow your users to log in using Facebook.
After this, on each new user log in in your app a new user is created in your user pool with all the attributes that were defined in attribute mapping and values which Cognito gets in the Facebook token. Your app will get these attribute values in the IDToken issued after authentication and you app can use these.
Additionally, if you want to store these attribute values outside of Cognito user pools profile, like your own DynamoDB table, you can configure a PreSignUp trigger in the pool which will be invoked on all new user creations. You can export the user attributes from this trigger to any database of your choice.
Hope this helps.
AWS AppSync allows you to access information in the GraphQL resolver which you can choose to store in a DynamoDB table. In your case for data coming from a Facebook profile you could pass this as arguments to a GraphQL mutation or in a header to AppSync which you can then access in the resolver via $ctx.request.headers.NAME where NAME is your header name. Then you could simply choose which attributes you want to write to DynamoDB for that user as part of the mutation. More information is in the reference guide here: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html
Since you also asked that you'd like to do a check first to see if the user is already in the DDB first you could just do an existence check first:
{
"version": "2017-02-28",
"operation": "PutItem",
"key": {
"userId": $util.dynamodb.toDynamoDBJson($ctx.identity.username),
},
"attributeValues": $util.dynamodb.toMapValuesJson($ctx.args.input),
"condition": {
"expression": "attribute_not_exists(userId)"
},
}
This checks against the username from Cognito User Pools. If you were using the Cognito Federated Identities feature it would be ctx.identity.cognitoIdentityId. If the record is already there the response that comes back will tell you which means the user is already present. You could also transform the returned message in the response mapping template by looking at $ctx.result with a conditional statement and either building the JSON response by scratch or using one of the $util.error() methods in the guide above.
Finally as you mentioned that you'll have public profile data, you might want to mark this on certain records for control. In AWS AppSync you can filter GraphQL responses on authorization metadata such as this. You would just have an attribute (aka column) on the DynamoDB record marked 'public' or 'private. Then your response template would look like so:
#if($context.result.public == 'yes')
$utils.toJson($context.result)
#else
$utils.unauthorized()
#end
You can see more examples of this here: https://docs.aws.amazon.com/appsync/latest/devguide/security-authorization-use-cases.html#public-and-private-records

Retrieve user profile in Cognito Federated Identities

I'm currently exploring the AWS stack and am therefore building a simple web app. I plan on using:
S3 to host the static contents
DynamoDB to store user data
Lambda + API Gateway for backend logic
Cognito Federated Identities to authenticate users
I currently have a small tracer bullet application working that allows the user to authenticate with Google (through Cognito) and retrieve some data through a Lambda from DynamoDB. Now I want to extend it.
The next thing I want to do (and am failing to achieve) is to actually store the user name and e-mail of the authenticated user. Storing it shouldn't be a big problem, but retrieving it is. I know I initially got the data from Google because when I inspect the ID token (on JWT.io) I got from Google, I can clearly see my e-mail and name. This is the token I sent to AWS Cognito in exchange for a Cognito token.
I was expecting to be able to access this data again in my Lambda function, but I fail to figure out how actually. I understand Cognito performs a one way hash on the retrieved ID token, but I would expect some options to actually retrieve relevant user data from the token. After all, by authentication through Google (or any other IdP) a user already consented to sharing some personal data.
I feel I fail to see something obvious. Is there any feature in AWS that solves this? Is there a moment (not on the client side) where I can inspect the ID token and do some magic with it? Or should I solve this in some different way?
If the latter is the case: what would be the preferred way? I don't want users to tell me their personal data, because then I would also need some way to validate it.