Amplify-js: How to get protected data from S3 Storage?

Created on 27 Nov 2018  Â·  22Comments  Â·  Source: aws-amplify/amplify-js

* Which Category is your question related to? *
Storage

* What AWS Services are you utilizing? *
https://aws-amplify.github.io/docs/js/storage#get

* Provide additional details e.g. code snippets *

On the storage documentation page, it has this listing:

Storage.get('test.txt', { 
    level: 'protected', 
    identityId: 'xxxxxxx' // the identityId of that user
})
.then(result => console.log(result))
.catch(err => console.log(err));

How/where do you get the identityId of a user that isn't the current logged-in user? What does this refer to?

Thanks!

Storage pending-close-response-required

Most helpful comment

@mwarger I think there might be a bit of a documentation gap here that I will address, and the other issues that @manueliglesias pointed are slightly unrelated to your original question. For clarity, if you enable UnAuthenticated access on your Identity Pool then when you call Auth.currentCredentials an IdentityId for the device w/ UnAuthenticated credentials will be retrieved. However, you do not need to specify the key as using Storage.configure({level:''}) will do this for you and use the underlying ID. For example:

```import Amplify, { Auth, Storage } from 'aws-amplify'
Amplify.configure(config)
const getCreds = async () => {
let creds = await Auth.currentCredentials() //This will give unauthenticated credentials object
creds.identityId;
}
getCreds();

Storage.configure({ level: 'private' });
Storage.put('test.txt', 'Hello') //Key will use the UnAuthenticated IdentityId from above
.then (result => console.log(result))
.catch(err => console.log(err));
```

The example that you pointed out in the documentation shows how you would "get other users’ objects" when using the protected level.

All 22 comments

Hi @mwarger

That identityId is the one from the user that created/uploaded the file to the protected area.

You can find more details in the File Access Levels section in the docs:

Files with protected access level are readable by all users but writable only by the creating user. In S3, they are stored under protected/{user_identity_id}/ where the user_identity_id corresponds to a unique Amazon Cognito Identity ID for that user.

@manueliglesias I understand how the access is structured. In my question above, I asked how/where do you get the identityId of a user that isn't the current logged-in user? What does this refer to?

It appears to be a pattern of the region:some-GUID - where the GUID comes from somewhere - is this the user sub or some other identifier?

Thanks!

@mwarger Thanks for the clarification.

What does this refer to?

The identityId is the Id that a user is assigned through the Identity Pool

how/where do you get the identityId of a user that isn't the current logged-in user

In Amplify there is currently no way to get another user's info. This could come from your UI or an API (e.g. you have an app that shows photos uploaded by other users, you might call an api like listProtectedPhotos that could return you the bucket/region/key/identityId for each photo).

I hope this makes some sense

In my situation, I'm using the default cognito identity pool setup. In that case, is it going to be the user sub that is the Identity ID?

If that's true - how do I derive the identity ID for use as the S3 bucket path? The paths in the format region:GUID don't appear to directly correspond to the user sub. Is there an AWS admin api that I could use in my version of the listProtectedPhotos to retrieve the correct identityID for a given user?

@mwarger

There are a couple of options to achieve this now, issue #54 has a lengthy discussion with suggestions and a feature-request for Cognito.

This comment in particular might help you:
https://github.com/aws-amplify/amplify-js/issues/54#issuecomment-434401406

@manueliglesias I will give that a look - thank you for your time.

@mwarger I think there might be a bit of a documentation gap here that I will address, and the other issues that @manueliglesias pointed are slightly unrelated to your original question. For clarity, if you enable UnAuthenticated access on your Identity Pool then when you call Auth.currentCredentials an IdentityId for the device w/ UnAuthenticated credentials will be retrieved. However, you do not need to specify the key as using Storage.configure({level:''}) will do this for you and use the underlying ID. For example:

```import Amplify, { Auth, Storage } from 'aws-amplify'
Amplify.configure(config)
const getCreds = async () => {
let creds = await Auth.currentCredentials() //This will give unauthenticated credentials object
creds.identityId;
}
getCreds();

Storage.configure({ level: 'private' });
Storage.put('test.txt', 'Hello') //Key will use the UnAuthenticated IdentityId from above
.then (result => console.log(result))
.catch(err => console.log(err));
```

The example that you pointed out in the documentation shows how you would "get other users’ objects" when using the protected level.

@undefobj The example I posted is what I meant - the documentation says that you can get other users objects, but this is not true without the user's identityId. I finally got this to work, but by saving off the individuals identityIDs when they login as part of their user object, and fetching that for when I want to retrieve an image that they have uploaded. Thank you @undefobj and @manueliglesias for your help with this.

Quick clarification on this as I was planning on implementing the same method as @mwarger.

Is it acceptable practice to save the other users' identityIDs in the database so anyone can view it? In other words, should identityID be publicly accessible like that?

Thank you.

@mwarger - For what I understood, the identityIdis the Cognito Identity Pool (or Cognito Federated Identities) on the other hand is a way to authorize your users to use the various AWS services. This article explains it well - https://serverless-stack.com/chapters/cognito-user-pool-vs-identity-pool.html

@wizawuza - once the user signs up on my app, I store the identityId together w/ other attributes on my DinamoDB via GraphQL where @auth(rules: [{ allow: owner }]), So only the owner can see it and Admin can access it.

Then I created a second GraphQL API for ADMIN, but I also cannot access the users images/objects. I even created a role fro the Cognito Pool to have full access for all S3 buckets, but still not luck:
AWSS3Provider - list error AccessDenied: Access Denied

How did you do it @mwarger ?

Thanks

@rfdc When any user logs in, I store the identityId from their credentials into a dynamodb table. I also use this table to store their username and other information I want to show when displaying their info on the page. I can grab their name, the URL to the photo, and the identityId to allow me to make the call through amplify to display the photo.

@mwarger do you know if it's safe to store the identityId like that? Or would a random person having access to a user's identityId allow that random person to use that information in a malicious manner?
edit: pardon my ignorance on cognito stuff like this, but thank you in advance for any help you can provide.

In general, I'm not sure. The way my app is setup, there are multiple barriers to entry before you would be able to get the info. You have to have an account, and the account information is not queryable directly, only through other parts of the appsync schema I'm using. Plus, besides marking all pictures/urls as public, I don't see any other way around this issue. If they aren't public, and by design of the amplify library when they're set as protected, you MUST have the identity ID to access the URL and retreive the media (in this case, a user's profile picture). The only more secure method is to set the media as private, but that obviously won't do for a situation like mine where you want users to be able to see others users profile photos.

If there is another more secure way to do this, I'm all ears. And, as you can see from the thread above, there don't appear to be any other alternatives.

there don't appear to be any other alternatives.

Yeah I was afraid so. _Can anyone on the AWS team comment?_

Otherwise, I don't believe there's another method of doing S3 storage via Amplify so that people can upload while limiting alterations (e.g., delete/remove) by 3rd parties. But, obviously, if exposing identityId's is a huge security risk....

@wizawuza identityId is just a way to identify a user (unique id), you cannot do anything without aws credentials from that user.

@mwarger I created PR #2910 that supports list/get objects from protected prefix, with that you can list all the objects under that prefix.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@elorzafe @undefobj I am facing the same issue. I'm not sure if I'm missing something obvious here. The design seems to make it impossible to access a protected item from another user without compromising the secrecy of identity id's and making unneeded updates every time on login. The identity id is meant to be private from what I understand and should remain within Cognito.

Why not simply store using the "sub" instead of identity id? Sounds like the quickest and most forward solution. Please let us know before we go further with development.

@rawadrifai I believe you also referenced this question here: https://github.com/aws-amplify/amplify-cli/issues/1847

You must use the IdentityID to set per-user policies on an S3 object, as this is the only control that IAM gives for S3 policies at this level. The JWT controls that are available in AppSync (or API Gateway for that matter) are not available at runtime in an IAM policy on a bucket.

IdentityIDs aren't considered private information to my knowledge, but perhaps you have a separate concern. Amplify creates three folders which have appropriate scopes for this policy here: https://aws-amplify.github.io/docs/js/storage#file-access-levels

If you're looking to have a place where users can upload data and others can read, I would look at either the public or protected folders. If you're concerned about doing list operations on the protected bucket you could potentially write a Lambda function with a GraphQL resolver using the @function directive, and have that function return results to the caller as appropriate and have it run a Storage.get() on a returned key.

@undefobj Playing along the same scenario of a slack-like messenger. Take the use case where you have a private channel, with images uploaded to that channel. Now you want to restrict access to the members of the channel only... so a dynamic set of Cognito users that assume a certain role. How would you go about that?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

This issue has been automatically closed because of inactivity. Please open a new issue if are still encountering problems.

I have an app where users upload images that need to be readable by all other users. So access level = protected.

When I want to read an image from storage I need to provide the identityId of the user that created the image.
In my case I have a table where I store the image S3 key and the identityId of the owner.
With that information I do a Storage.get of the protected image.

const signedUrl = await Storage.get(fullImagePath, {
          level: "protected",
          identityId: identityId,
        });
Was this page helpful?
0 / 5 - 0 ratings

Related issues

shinnapatthesix picture shinnapatthesix  Â·  3Comments

ddemoll picture ddemoll  Â·  3Comments

karlmosenbacher picture karlmosenbacher  Â·  3Comments

josoroma picture josoroma  Â·  3Comments

guanzo picture guanzo  Â·  3Comments