aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.43k stars 2.13k forks source link

How to find the bidirectional map between Cognito identity ID and Cognito user information? #54

Open baharev opened 6 years ago

baharev commented 6 years ago

Given the Cognito identity ID, I would like to programmatically find the user name, e-mail address, etc. For example, one issue is that each user gets his/her own folder in S3 (e.g. private/${cognito-identity.amazonaws.com:sub}/ according to the myproject_userfiles_MOBILEHUB_123456789 IAM policy) but I cannot relate that folder name (S3 prefix) to the user attributes in my user pool. The closest thing that I have found is this rather complicated code:

AWS Lambda API gateway with Cognito - how to use IdentityId to access and update UserPool attributes?

Is it my best bet? Is it really this difficult?

(As a workaround, I would be happy with a post confirmation lambda trigger that creates for example a ${cognito-identity.amazonaws.com:sub}/info.txt file in some S3 bucket, and in the info.txt file it could place the user sub from the user pool. I am not sure that this is feasible at all, it was just an idea.)

alexofob commented 4 years ago

Amplify is full of surprises. Easy things are not simple as they say. This issue should not be open till now.

annjawn commented 4 years ago

This problem has come back to bite me in my 5th project so far with Amplify. It makes no sense that there is no bi-directional mapping between User Pool and the Identity Pool identities. Especially, in case of Federated user pool. I have federated User pool with SAML, Google, and Facebook and mapping which user is the identity in the identity pool has been a pain in the behind unless we do some hacks like reading the JWT idToken to get the Identity ID and then store it somewhere for later reference.

This also makes it super painful to use the Storage module especially the protected level since protected levels needs the identity ID to access a user's file. This is fine for the current user and they can access their "own" file(s) from the private and protected prefixes in S3, but what about accessing (GetObject) other user's files from the protected prefix since the definition of protected says you can read any other user's protected file but not write to it. This is painful because now the current user needs to know ALL the other user's identity ID in advance of making use of the Storage.get API. This to me sounds more like an issue for AWS Cognito team to solve than for Amplify.

ghost commented 4 years ago

Hmm, this is an early sign of AWS abandoning Amplify. I would recommend anyone to move away from Amplify whenever possible. There have been so many promises in the documentation at-least with the way it was written but then the product manager like me end up burning my dev's time just to hit into a dead end in future.

undefobj commented 4 years ago

@babus Amplify is not being abandoned by any means, in fact we invest into it more every day. We are in discussions on this issue with the Cognito team.

annjawn commented 4 years ago

I think Amplify has had some major improvements since AWS Mobile Hub days, but I think specific to Cognito as a service, there's also much work to be done. I am eager and excited to see those improvements coming up in the future.

ghost commented 4 years ago

@undefobj That's glad to hear. But let's solve it first and then talk. This issue is basically a very root issue with not so ever understanding of how cognito and storage work together and it's been here for the past 2 years.

To the downvoters, people are paying to AWS by using Amplify and this isn't just a open source library where I cannot be toxic. This is a paying customer expecting some basic level of customer satisfication from the vendor. Even their business level technical support team doesn't have any clue when this would be solved.

peterschwarz21 commented 4 years ago

I can't believe this thread is still open.... I'll just mention that I am facing the same problem albeit different.

I am using the CognitoID as the UID for users in my own DB. No problems except when the project scope changed to allow 'admin' users to add other users to their application, there is no way of getting this ID when creating the user in the UserPool.

Tee88 commented 4 years ago

Just here to say that this issue is 855 days old. Way to go Cognito team!

CasperAlant commented 4 years ago

@undefobj do you have any feedback from the Cognito team please?

louisholley commented 4 years ago

I tried the solution @djkmiles provided to no avail. Worked out it's because I'm using AWS_IAM as my appsync authentication type rather than cognito...

BUT I was inspired to check what treasures were hidden inside $ctx.identity, logging the whole thing in lambda gives the following:

{
  accountId: ----,
  cognitoIdentityAuthProvider: ----,
  cognitoIdentityAuthType: 'authenticated',
  cognitoIdentityId: ----, <--- FUCK YEAH
  cognitoIdentityPoolId: ----,
  sourceIp: ----,
  userArn: ----,
  username: ----,
}

sure enough, storing and then using that cognitoIdentityId lets me access users protected files on S3.

thanks to @djkmiles for getting me out of this hell hole - hope this proves useful to others

tomeraz commented 4 years ago

Whoever gets here, maybe this bit helps, if you're inside a lambda function accessed by an authenticated user:

https://serverless-stack.com/chapters/mapping-cognito-identity-id-and-user-pool-id.html

kapillx commented 4 years ago

Without having bidirectional mapping between users and their identities in cognito, how are we supposed to find out the dangling identities(i.e identities with no user in userpool). Now we have to do extra work to store the mapping elsewhere(in db or userpool attribute) so we are able to delete the dangling identities.

cemozerr commented 4 years ago

How is this issue still not solved? Is there some technical debt in the background that makes this impossible to solve? Makes Amplify useless for me (because I will need to duplicate information elsewhere) and surely many others.

phillipsnick commented 4 years ago

Just come back to an older Cognito project, hit this problem over a year ago. Can't believe there isn't any official way of resolving this still.

sveins12 commented 4 years ago

How can I find the IdentityId from a cognito sub or the other way around, without a user session? I have a table with information only linked to IdentityId, but I don't know which cognito-user they are linked to. Is it possible figure out the cognito users somehow?

peterschwarz21 commented 4 years ago

that's the problem in this discussion. I ended up building a mapping table to match them.

On Wed, May 27, 2020 at 6:49 PM sveins12 notifications@github.com wrote:

How can I find the IdentityId from a cognito sub or the other way around, without a session user session? I have a table with information only linked to IdentityId, but I don't know which cognito-user they are linked to. Is it possible figure out somehow?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/aws-amplify/amplify-js/issues/54#issuecomment-635023937, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADCEXAOT22MO4EEE5HP4SYDRTWYJZANCNFSM4EHSZZ5Q .

dtelaroli commented 4 years ago

+1

iofluxdev1 commented 4 years ago

Sitting with the same problem now. We have an iOS client that associated users with cognito identity IDs and the project lambdas use the cognito identity ID. Now we want to add a web client that uses oidc and cognito user pool authorizers which as far as I can tell does not expose the cognito identity ID. I now need to figure out how to get the cognito identity ID from the info that is in the request context authorizer claims, otherwise I am going to have a hell of a time refactoring the whole API and iOS client to support the new web client or have to create new lambda versions for the web client API.

dtelaroli commented 4 years ago

Another way to get the identity id inside the lambda with cognito auth:

https://gist.github.com/dtelaroli/78f8a17afceed3e7d398929f0fbbf950

kennedysam97 commented 4 years ago

Is there any timeline we can get on when this basic functionality will be available? On my services backend we are creating many user accounts, importing from json.. but we need to store the IdentityId of each account at the time of creation. For some reason getId only supports unauthenticated access.. when I supply the Login params with a proper token, the IdentityId that's returned is not the same as a user gets when they log in using amplify..

Is anyone from the service team aware of this issue? @undefobj it's been "in discussion" and kicked around for years

henryouang commented 4 years ago

After looking around in the Amplify files in Swift, I found that one can retrieve the identityId by fetching the current auth session, casting the result.get() as AuthCognitoIdentityProvider, and running .getIdentityId().get(). Same process as retrieving the userSub. It seems this functionality may have finally been added!

This solution works for me.

danielblignaut commented 4 years ago

hey guys,

just to re-iterate that this is a really big problem with Cognito and really needs to be addressed. It's clear that there's multiple use cases associated with this that are all important and with only allowing this value to be managed on the client side, there's a massive security flaw in every single project that requires the cognito Identity Id on the backend. Ultimately, if a user knew of this issue and knew the only work around is to set the cognitoIdentityId through a front end API request with no backend validation (essentially setting it to any value), I'm pretty sure there's a big risk of malicious attacks at worst and at best, breaking an application.

By the looks of it, the only bi-directional map that exists is between the IdToken and the cognito Identity pool and perhaps the technical debt in either adding a new mechanism without making it a "Login" in the federated Identity object is too great but even if that is the issue, it's been nearly 3 years.... make it ugly if you need to, patch over it need be, but please please please just fix this issue or document and supply code for a work around that is reasonable

iofluxdev1 commented 4 years ago

What we did to resolve this in our use case was drop OICD and user pool authorizers and went with https://github.com/aws/aws-aspnet-cognito-identity-provider. We then exchange the token for IAM credentials and call our lambdas (API gateway) in the same way our iOS app does. It required a lot of hammering to play nicely with Blazor server and flat out failed with Blazor WASM. It was an very painful exercise.

ahtokca commented 4 years ago

Save public so just lookup by identityId

  Auth.currentUserInfo()
    .then(info => {
      Storage.put(
        identityId + '_userInfo.json',
        JSON.stringify(info),
        { level: 'public', contentType: 'application/json' }
      );
    });

It seems like a very bad advise from security point of view. As @baharev explains above this mapping must be done purely from your backend

ahtokca commented 4 years ago

At react-native client code I pass JWT token to IAM authorized lambda

Promise.all([Auth.currentSession(), Auth.currentCredentials()])
    .then((v) => {
      jwtToken = v[0].getIdToken().getJwtToken(); // holds user info like sub, IdP User Id
      credentials =  Auth.essentialCredentials(v[1]);
      const lambda = new Lambda({ credentials: credentials, region: ... });
      return = lambda.invoke({
        FunctionName: '...',
        Payload: JSON.stringify({ myPayload: {....},  jwtToken: jwtToken }),
      }).promise();
    })

At my IAM authorized lambda I do make sure that the passed JWT token resolves to the same Cognito Identity as in the call context.

exports.handler = async (event, context) => {
  cognitoIdentity.getId(
      {
        AccountId: '12345676890',
        IdentityPoolId: context.identity.cognitoIdentityPoolId,
        Logins: {
          `cognito-idp.${region}.amazonaws.com/${userPoolId}`: event.jwtToken,
        },
      },
    )
      .promise()
      .then((d) => assertIdsAreTheSame(d.IdentityId, context.identity.cognitoIdentityId))
      .catch((e) => check_id.e = e);
....
}

This way I know the user Cognito Identity and the user info like email, sub, IdP UserId which is enough to find user at UserPool or my dynamo DB

harrysolovay commented 3 years ago

@ffxsam had it right, depending on the complexity of your use case (for instance, specially-permissioned S3 operations):

The SDK should handle these sorts of lower level operations for us.

@baharev please provide a bit more clarity on your comment:

There is a misunderstanding here: I need this bidirectional map purely on the backend. The user must not be able to access it exactly for security reasons.

^ as of today, we make this info (name, email, etc.) accessible to the authenticated client. This isn't going to change.

Amplify gives you the tools you need to provision endpoints that utilize the Cognito API's admin endpoints. The JS library itself is geared towards front-end developers though. I'd advise...

Just to reiterate the solutions:

  1. Sign the user in with Amplify's Auth category.
  2. Send an authenticated request to an endpoint where you've implemented that mapping.
  3. On the endpoint, use the AWS SDK to send a GetUser request, whose response contains the data for which you're looking.

The user-specific S3 bucket discussion seems to be another issue unto itself. Please open a new issue for that if you'd like.

baharev commented 3 years ago

@harrysolovay I stopped using Amplify an year ago; this issue was one of the many reasons why. As far as I am concerned, you could have closed this issue an year ago. As a side note, I find it quite amusing how the Amplify team "solved" this issue, which is the most commented and oldest open issue, in almost 3 years time.

harrysolovay commented 3 years ago

@baharev I understand your frustration. If you ever decide to try Amplify again, please tag me personally in any issue. I hope you’ll give the framework another chance.

sammartinez commented 3 years ago

@baharev If you'd like to chat with myself our @mauerbac, please feel free to reach out to us on our Discord channel or through DMs.

baharev commented 3 years ago

@harrysolovay @sammartinez I re-wrote everything I needed an year ago (S3, Lambda, API Gateway, DynamoDB, Cognito, CloudFront, CloudWatch, Kinesis Firehose, SES, SNS, CloudFormation and a deployment script in Python). The only non-trivial piece was the Cognito authentication (SRP and handling the tokens); all the other services are well-documented and have well-thought-out REST API and they are, for the most part, pleasant to work with. At this point I don't see any reason why I would want to use Amplify. I apologize if that was harsh but that's how it is.

thecloudslayer commented 3 years ago

Not sure If i'm over simplifying this but this worked for me to map my Cognito identity ID path for S3.

const [test, setTest] = useState(["Loading.."]); 

let test = await Auth.currentUserInfo();
setTest(test.id);

<h1>{test}</h1>

wvidana commented 3 years ago

Is this still a thing? This issue is so old that I'm not sure what was solved and what not.

Can we trust the event that generates API Gateway if an authenticated user makes a call? From a backend in Lambda, triggered by API Gateway (REST API created with the amplify-cli), within the event I can get these two:

event['requestContext']['identity']['cognitoIdentityId']
event['requestContext']['identity']['cognitoAuthenticationProvider']

The cognitoAuthenticationProvider string has the sub from the user at the end after a colon (like cognito-idp.eu-west-1.amazonaws.com/eu-west-XXXXXXXX,cognito-idp.eu-west-1.amazonaws.com/eu-west-XXXXXXXX:CognitoSignIn:1234asdf-12ab-12ab-12ab-123456asdfgh). You can also get the User Pool id from here.

michaelbrewer commented 3 years ago

A non hacky solution would be great, especially when building things like S3 event triggers on a file upload and using the key of the file to fine the related user on a cognito user pool.

runcible commented 3 years ago

@baharev

I stopped using Amplify an year ago; this issue was one of the many reasons why.

Can I ask what you are using as an alternative to Cognito which still plays nicely with IAM roles, or are you still using that service and have just dropped Amplify? I've been using both for a couple of years but time's running out for Cognito to mature in my opinion.

It seems crazy that the only suggested way to visibly link a UserPool User Id with an Identity Id is to store it as a new custom attribute on the user profile. This is particularly galling considering there would then be no way to query the UserPool for the user with that particular custom attribute value (https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ListUsers.html#API_ListUsers_RequestSyntax), meaning I would need to loop through every user to find the one with a particular Identity Id.

baharev commented 3 years ago

@runcible I restructured my code, and implemented something similar to what @lestephane proposed in his comment here. As a bonus, it was fairly easy to implement that a user can share his/her data with another user but only with that other user. I don't know how to implement it with IAM at scale.

Note that lestephane's approach is not without drawbacks. You are essentially implementing yourself what IAM would do for you for free. If the access patterns are simple enough, it is not a big deal. In my case I had to restructure the whole application to make the access patterns simpler, but it was worth it. However, it is not a free lunch, it is not a silver bullet.

There is one mind-blowing thing about it: my front-end JavaScript code does not contain the aws-sdk or Amplify. It completely eliminated the issue with the bundle size, see for example https://github.com/aws-amplify/amplify-js/issues/122

I call API Gateway using fetch, and I put the ID token into the Authorization header of the request. I am using the Cognito Authorizer of API Gateway. If the token is invalid or expired, API Gateway will reject the request, and I don't have to write any code, and my Lambda function won't even run in this case. If the token is valid, I get the claims in the event argument of the Lambda function, and I know that I can trust those claims because the cryptographic signature has been checked by API Gateway. No coding on my side, no extra costs either. No library or framework in the client-side JavaScript either.

I store everything about the users in my own DynamoDB table. If you don't want to go back to the DynamoDB table for each request, put the most often used data into the token in the pre-token generation trigger of Cognito. The data will be in the claims which you can trust.

Otherwise, I use Cognito as little as possible, and collect everything about the user in my own DynamoDB table.

If you need further help, please drop me an email.

JesseDavda commented 3 years ago

@baharev I'm not sure if this is a new feature in Cognito, but I was running into the same issue you originally did. We are using Amplify to put, list and delete objects in the bucket and by default the base path it would store the documents in, was private/${identity id}/... and the assumed authenticated role would allow access to that folder by specifying the federated id in the resource: arn:aws:s3:::bucket-name/private/${cognito-identity.amazonaws.com:sub}. The original solution we had was to pass the generated JWT token to our back end and call the getId method from the CognitoIdentity SDK and then storing the token from the response in our DynamoDB table so that we could map backwards from federated id to user attributes.

After doing some more research we found that you can use user attributes for access control so instead of using the federated id as the users folder name, you can specify a custom attribute mapping (we mapped cognitoId to sub) using principal tags, and in your policy you can dynamically reference resources using these tags:

{
  "Sid": "ReadWriteDeleteYourObjects",
  "Effect": "Allow",
  "Action": [
    "s3:GetObject",
    "s3:PutObject",
    "s3:DeleteObject"
  ],
  "Resource": [
    "arn:aws:s3:::bucket-name/private/${aws:PrincipalTag/cognitoId}",
    "arn:aws:s3:::bucket-name/private/${aws:PrincipalTag/cognitoId}/*"
  ]
}

This video explains the process.

Although this doesn't solve the issue of mapping between the federated id and user attributes I hope it will help anyone who runs into the original issue.

baharev commented 3 years ago

@JesseDavda Interesting, thanks for the info! I haven't tried it, but it seems to solve the original issue. However, it does not solve the issue of ad-hoc sharing between two users, see my above comment. In any case, thanks for the info!

dorontal commented 3 years ago

@JesseDavda Thank you very much!!!

I just tried your solution. It is so simple to implement. It works 100%.

[ I learned something new in the process about a powerful new dynamic IAM controls, too. Thanks again. ]

As far as I can see you are the first person to fully solve this problem -- the problem of using the user's sub (instead of using identityId) in S3 paths and how to set that up in our cloudformation templates --

I have tried every one of the other proposed solutions - either these proposals did not work at all or had major drawbacks. Your solution to this problem is extremely simple, takes a few minutes to configure, and it works.

KeKs0r commented 3 years ago

@JesseDavda Unfortunately I am not 100% following the solution. Does this mean that I configure my storage not to private and construct the path manually from the userId (=sub) in the client? And then use your IAM code to ensure the access is handled properly?

I am also using the amplify CLI, so I don't even know where exactly I would put those IAM rules.

rezab777 commented 3 years ago

@KeKs0r Well that's one of the drawbacks about Amplify, you can't have a fully custom solution. If you would like to go JesseDavda's way, you might need to do things manually.

So in your S3 buckets, you would have directories in the following format bucket-name/private/${aws:PrincipalTag/cognitoId} or just simply bucket-name/${aws:PrincipalTag/cognitoId}

and then you would just attach the policy mentioned to your IAM role.

dorontal commented 3 years ago

@KeKs0r I implemented the solution mentioned by @JesseDavda as follows:

1) First, update the trust relationship (trust policy) for the Auth role - go to IAM console and edit the Trust policy of the Auth role to add the sts:TagSession action: (Following https://docs.aws.amazon.com/cognito/latest/developerguide/using-attributes-for-access-control-policy-example.html)

From
```json
   "Action": "sts:AssumeRoleWithWebIdentity",
```
to
```json
      "Action": [
        "sts:AssumeRoleWithWebIdentity",
        "sts:TagSession"
      ],
```

2) Set up attributes for access control in Cognito console. (Following https://docs.aws.amazon.com/cognito/latest/developerguide/using-afac-with-cognito-identity-pools.html)

* Go to Cognito console
* Click on 'Manage Identity Pools' - there should be only one, click on it
* Click on 'Edit identity pool'
* Expand 'Authentication providers'
* Under 'Attributes for access control', choose 'Use custom mappings'
* Edit the mappings to only show one mapping: `cognitoId` --> `sub`
* Click 'Update Trust relationship'
* Scroll to bottom anc click 'Save changes'

3) Now you need to edit the storage cloudformation template, in current example it is in amplify/backend/storage/s3/s3-cloudformation-template.json

In it, global replace
```
    ${cognito-identity.amazonaws.com:sub}
```
with
```
    ${aws:PrincipalTag/cognitoId}
```
in `amplify/backend/storage/s3/s3-cloudformation-template.json` (it should tell you that four occurrences got replaced).

* After you make a change to cloudformation you need to do another `amplify push`.

4) In the client code, you will need to specify the identityId field by supplying sub to it - this is how you use storage in client code:

            this.amplifyService.storage().put(
                s3key,                      // s3 key
                blob,                       // data to put in s3
                {
                    level: 'protected',
                    identityId: sub         // user's sub value is provided here, not the identityId
                }
            )
peterschwarz21 commented 3 years ago

Thank you so much!

I'm not the original requester but I have been following this thread for years. YEARS! And it's nice to finally have a solution.

On Thu, Jul 8, 2021, 9:50 AM Doron Tal @.***> wrote:

@KeKs0r https://github.com/KeKs0r I implemented the solution mentioned by @JesseDavda https://github.com/JesseDavda as follows:

1.

First, update the trust relationship (trust policy) for the Auth role

  • go to IAM console and edit the Trust policy of the Auth role to add the sts:TagSession action: (Following https://docs.aws.amazon.com/cognito/latest/developerguide/using-attributes-for-access-control-policy-example.html )

    From (OLD):

    { ... "Action": "sts:AssumeRoleWithWebIdentity", ...

    To (NEW):

    { ... "Action": [ "sts:AssumeRoleWithWebIdentity", "sts:TagSession" ], ...

    2.

    Set up attributes for access control in Cognito console. Follow https://docs.aws.amazon.com/cognito/latest/developerguide/using-afac-with-cognito-identity-pools.html

  • Go to Cognito console

    • Click on 'Manage Identity Pools' - there should be only one, click on it
    • Click on 'Edit identity pool'
    • Expand 'Authentication providers'
    • Under 'Attributes for access control', choose 'Use custom mappings'
    • Edit the mappings to only show one mapping: cognitoId --> sub
    • Click 'Update Trust relationship'
    • Scroll to bottom anc click 'Save changes' 3.

    Now you need to edit the storage cloudformation template, in current example it is in amplify/backend/storage/s3/s3-cloudformation-template.json

    In it, global replace

    ${cognito-identity.amazonaws.com:sub}

    with

    ${aws:PrincipalTag/cognitoId}

    in amplify/backend/storage/s3/s3-cloudformation-template.json (it should tell you that four occurrences got replaced).

  • After you make a change to cloudformation you need to do another amplify push. 4.

    In the client code, you will need to specify the identityId field by supplying sub to it - this is how you use storage in client code:

       this.amplifyService.storage().put(
           USER_DEFAULTS.s3PictureBackupFileName,
           blob,
           {
               level: 'protected',
               identityId: this.attributes.sub
           }
       )

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/aws-amplify/amplify-js/issues/54#issuecomment-876455852, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADCEXAMEYIJLSOG3RDNML73TWWUJ7ANCNFSM4EHSZZ5Q .

efleurine commented 3 years ago

Thanks for elaborating on this solution, @dorontal

I was lost this afternoon about this aspect of amplify.

So this means if we create a new env, we have to replicate the manual workaround?

Thank you

v1pz3n commented 3 years ago

@KeKs0r I implemented the solution mentioned by @JesseDavda as follows:

  1. First, update the trust relationship (trust policy) for the Auth role - go to IAM console and edit the Trust policy of the Auth role to add the sts:TagSession action: (Following https://docs.aws.amazon.com/cognito/latest/developerguide/using-attributes-for-access-control-policy-example.html) From

      "Action": "sts:AssumeRoleWithWebIdentity",

    to

         "Action": [
           "sts:AssumeRoleWithWebIdentity",
           "sts:TagSession"
         ],
  2. Set up attributes for access control in Cognito console. (Following https://docs.aws.amazon.com/cognito/latest/developerguide/using-afac-with-cognito-identity-pools.html)

    • Go to Cognito console
    • Click on 'Manage Identity Pools' - there should be only one, click on it
    • Click on 'Edit identity pool'
    • Expand 'Authentication providers'
    • Under 'Attributes for access control', choose 'Use custom mappings'
    • Edit the mappings to only show one mapping: cognitoId --> sub
    • Click 'Update Trust relationship'
    • Scroll to bottom anc click 'Save changes'
  3. Now you need to edit the storage cloudformation template, in current example it is in amplify/backend/storage/s3/s3-cloudformation-template.json In it, global replace

       ${cognito-identity.amazonaws.com:sub}

    with

       ${aws:PrincipalTag/cognitoId}

    in amplify/backend/storage/s3/s3-cloudformation-template.json (it should tell you that four occurrences got replaced).

    • After you make a change to cloudformation you need to do another amplify push.
  4. In the client code, you will need to specify the identityId field by supplying sub to it - this is how you use storage in client code:
           this.amplifyService.storage().put(
               s3key,                      // s3 key
               blob,                       // data to put in s3
               {
                   level: 'protected',
                   identityId: sub         // user's sub value is provided here, not the identityId
               }
           )

thank you very much, it worked for me.

You can use the default attributes and you don't need to map it, the change is that the policy would be like this

${aws:PrincipalTag/cognitoId}

to

${aws:PrincipalTag/username}

wvidana commented 3 years ago

@v1pz3n what changes you implemented on the client side? Is there a way of changing the identityId globally for all storage operations? I mean something like a global config instead of a parameter for the put operation.

Bonus points if anyone can confirm this can be implemented on iOS and Adroid

dorontal commented 3 years ago

@wvidana, the only change on the client side is Step 4 (see my first comment on this issue above, where I spell out the way I implemented JesseDavda's solution) -- just add a flag to each of your amplifyService.storage().get() or .put() calls. The flag is { identityId: sub } where sub is the user's current sub value that you provide. This flag is given to the functions in the configuration object (last argument).

v1pz3n commented 3 years ago

@wvidana I used it in the same way mentioned by @KeKs0r , directly in the put method.

I only have one screen that sends to S3, I didn't see the need to do it globally

But in your case, take a look using the StorageProvider https://docs.amplify.aws/lib/storage/getting-started/q/platform/js#using-a-custom-plugin https://github.com/aws-amplify/amplify-js/blob/a047ce73/packages/storage/src/Storage.ts

hkjpotato commented 3 years ago

@JesseDavda @v1pz3n Thank you for providing the go around. I am from Amplify and I am thinking of supporting this approach directly in our tool. At this moment, we are trying to understand more about the use cases to ensure we don't miss some edge cases.

What you are proposing this, is the newly launched attribute-based access control (ABAC), which bridges the gap of "what we know about user"(e.g. user pool attribute) and "what the resource is identified by"(in IAM policy access control).

Can you (or anyone interested in this topic) give me a little more information about the use cases that you are trying to solve? Historically, we are using identityId as identifier for resource (not just S3), and it is because ${cognito-identity.amazonaws.com:sub} is the recognized variable by IAM.

And yes there is no API to get identityId from a user attribute. You can call getId with the logins:idToken. This is what we do in amplify when set the AWS credentials.

Another potential solution is to make your cognito user pool as a developer idp, then you can use LookupDevleoperIdentity, but it is not very convenient.

As mentioned above, one solution is to store identityId as a custom attribute, but I am not sure how that helps address the use case of "given an identity Id, I want to find the related user" easily because according to the listUser api you cannot filter based on custom attribute.

My primary concern on using user pool attribute as identifier is the guest user use case. As you probably noticed, user pool does not support guest user, and currently we rely on identity pool to give us guestIdentity which can be used that as an identifier for the guest resource. Is this the use case you want to support as well?


after reading more on the past comments so far these are the use cases I know

  1. for a given resource under certain identifier, I want to know who is the user (from user pool) that owns it.
  2. for a given user, I want to find its related resource.
  3. in certain lambda function I want to be able to access/relate the user with the given context.

I am not sure if "sub" is a good replacement for identity. Given a "sub" how do you normally find the user from user pool?

dorontal commented 3 years ago

The main use case for me is simple: it does not make sense to keep track of more than one id per user.

I much prefer to only use one ID everywhere per user. Having to deal with only one ID per user makes the code much clearer, neater and simpler.

One reason for this preference is cost - it costs more to keep two IDs per user. Cost = more development time and complexity, more points of failure in the system, more money if your app has many users - and that's something that probably many app developers dream about.

Also, developers by nature often are concerned about efficiency - so having to keep track of more than one ID per user is going to cause more developers to be more disgruntled with the inefficiencies and costs forced upon them by the library.

My use case for S3 authentication is exactly as it was originally intended and described, with the three levels private / public / protected -- you have done a nice job IMO with that setup -- it's just nicer and cheaper not to have to deal with two IDs -- for me it's a simple as that.

So, to answer your question, there's no major new use case for me.

To answer other question "Given sub, how do we find the user from the user pool?":

I keep a User model in the GraphQL schema whose primary key is "sub", as follows

    type User
      @auth(
        rules: [
          { allow: owner, ownerField: "sub" }
        ]
      )
      @key(fields: ["sub"]) {
      sub: String!
      ...
    }

in other words there is not the usual id: ID primary key for the user but instead I call it "sub" -- if you use any @auth directives in this User model now, you will need to specify the ownerField to be that sub.

Now if I need any info about the user I just retrieve it via sub from the DB...

With this setup, there is never a need to retrieve the user from the user pool -- all the info you'll ever need about the user is in the DB.

hkjpotato commented 3 years ago

@dorontal Thank you, and I agree with you on maintaining single source of truth. If I understand it correctly, you are maintaining a separate database (other than the user pool itself), for accessing user object? Will it be easy for you to also keep track of identityId in that database?