Retrieve user pool ID using only a client ID - amazon-web-services

We have a use case requiring multiple user pools in Cognito. Each pool has a client, which enables the client credentials flow.
During the client credentials flow, the user of the API sends the client ID and client secret, and we then call a user pool specific endpoint to generate an access token. What I'd like to do is retrieve the user pool ID using only the client ID given in the request, and ideally retrieve it directly from AWS. Is this possible?
I realize we can likely use a combination of this and this, where you can list all the user pools and then list all the clients belonging to those. I know we'd also be able to store the association on our end in a DB. However, it would be much better to just be able to make an SDK call to retrieve the pool ID, saving us the custom code and DB management.
Does anybody know if it's possible to do a direct lookup of the user pool ID using the client ID, whether it's in the SDK (Nodejs), their API, or using some unofficial methods?

I am fairly sure there is no cognito-idp call you can use for this, I have been working in the cognito idp api a fair amount recently and also wanted this functionality.
As an alternative you could add a tag to the user pool resource for each client, with the key being the client id and the value being the user pool id. Then make a call to get-tag-values for that tag.

Related

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).

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.

initialize amazon aws cognito client via BasicAWSCredentials vs CognitoAWSCredentials

What is the difference between these two approaches of initializing a new AmazonCognitoIdentityClient?
AmazonCognitoIdentity identityClient = new AmazonCognitoIdentityClient(
new BasicAWSCredentials("access_key_id", "secret_access_key")
);
identityClient.GetOpenIdTokenForDeveloperIdentity()
-
AmazonCognitoIdentity identityClient = new AmazonCognitoIdentityClient(
new CognitoAWSCredentials ("IDENTITY_POOL_ID", "REGION_NAME");
);
identityClient.GetOpenIdTokenForDeveloperIdentity()
Although most examples on the internet show BasicAWSCredentials being used to instantiate a CognitoIdentityClient, but the method signature in the doc says it accepts AWSCredentials class - both BasicAWSCredentials as well as CognitoAWSCredentials are subclasses of the AWSCredentials class. Hence, i am assuming both should be working normally i guess???
I am trying to understand how will this difference, impact the following:
privileges,
timeouts,
etc?
API reference for cognitoIdentityClient constructors is here: http://docs.aws.amazon.com/sdkfornet/v3/apidocs/index.html?page=CognitoIdentity/TCognitoIdentityCognitoIdentityClient.html&tocid=Amazon_CognitoIdentity_AmazonCognitoIdentityClient
In client device you should instantiate AmazonCognitoIdentityClient client using CognitoAWSCredentials. Using STS service, AWS client will obtain temporary credentials that will let the client assume the role you previously defined in your identity pool. Typically this role would have very limited access to your AWS resources. (S3 upload to a specific bucket etc.) This is like giving out to people a special type of your car key which can only turn on the music system, not the engine.
On the other hand GetOpenIdTokenForDeveloperIdentity is a special API call that needs developer credentials. You should never deploy developer credentials to any client device and you should keep it only on your server. Once you instantiate a AmazonCognitoIdentityClient using developer credentials on your server, you can expose a REST endpoint for clients to obtain OpenId token (for a given identity id or creating a new one). Let's assume that your users are logging in to your API using their username and password and you return them a custom access token which is stored in your database. After that your endpoint may implement this logic:
Fetch unique identifier for your user using custom token (user id, username, e-mail etc.)
Use LookupDeveloperIdentity and find out the identityId for the given username.
Use GetOpenIdTokenForDeveloperIdentity with found identityId and send back to the client. So they can "login" to that identityId.
If this user has not any identityId assigned to its username, create a new one and send back.
As you see developer AWS tokens are enabling a couple of sensitive API calls. Now let us assume that client had developer access token and thus has access to GetOpenIdTokenForDeveloperIdentity call. Then they would be able to generate OpenId tokens, switch to other people's identities easily and access their private data.
If you don't use developer authenticated identities (https://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html) you do not need GetOpenIdTokenForDeveloperIdentity mechanism though. If you are using only public authentication methods (Twitter, Facebook etc.) you should ignore the second paragraph and use CognitoAWSCredentials.
As described in Çağatay's answer, the BasicAwsCredentials are instantiated with your developer credentials. What he described about when to use each constructor is absolutely correct in every case other than developer authenticated identities.
Since this code will be deployed on a back end service, it's exposure is less of a concern. Mobile clients will communicate with this server and get the token from it, with which they will get credentials. The credentials are never exposed to the user in this pattern.
As you can see in the Cognito documentation, the sample has BasicAwsCredentials. An end to end sample is visible in this blog post.

How to link developer authenticated user across devices in Cognito

I'm just trying to get a handle over this framework and I want to confirm that my approach is correct.
I can authenticate my own user using the method: getOpenIdTokenForDeveloperIdentity and supplying an IdentityPoolId and a Logins key-pair with my Developer provider name and a token which I provide.
Now, from what I understand, when the user logs into a second device, in order for Cognito to understand that this is the same user, I have to provide it with an IdentityId. However, I'm not sure of the best way to get the IdentityId programmatically so that it will match up with the initial login.
The only technique I can think of is to store the IdentityId in my own DB and provide my own method for retrieving it. Is this the best way? Or should I be working with this framework differently?
I'm still a beginner to AWS in general and I'm just trying to understand the best practices for this framework.
BTW, I'm implementing the Android SDK and the PHP SDK for my backend.
When you use getOpenIdTokenForDeveloperIdentity, it returns the identity id associated with the user identifier you provided. So if the user identifier you use is the users' username, when you call getOpenIdTokenForDeveloperIdentity with that same username from the second device, it will return the associated identity id. There is no need to store the identity id unless you want to, it is provided to you each time you call getOpenIdTokenForDeveloperIdentity.
This blog post may be of further help:
http://mobile.awsblog.com/post/Tx2FL1QAPDE0UAH/Understanding-Amazon-Cognito-Authentication-Part-2-Developer-Authenticated-Ident
When you use Cognito, your user first authenticates with an Identity Provider (such as facebook, google or other Oauth provider), and the token you get back from it is sent to Cognito and is the key to tying your users information together across logins on other devices.
You don't need to store this information in a database, unless you are writing you own custom identity provider and not using one of the public ones available.

OAuth 2.0 client ids in Django/tastypie implementation

I'm trying to implement OAuth 2.0 for my API. I'm using a third party library to act as the basic OAuth provider, django-oauth2-provider,
and Tastypie as the framework. Those details shouldn't matter too much. The OAuth 2.0 works -- when a user is created, an OAuth 2 client
that manages the user's secret_key and their id is created. A customer can then supply the user ID they get back from the user creation
endpoint along with their username and password to get an access token which allows them to use API endpoints.·
Where I run into issues is retrieving the client id (which must be passed into requests for the access token). Obviously when a user is first created
I can return the client_id with the HTTP response. After that, however, there will obviously be cases where the user doesn't have their client id·
stored locally (this is a traditional user/app setup, not something like Google APIs where your client id is always visible). I want to protect
GET requests to the customer resource with OAuth, but that means I can't query the API for a given user's client ID. And it seems like the whole point
of OAuth is defeated if I can always just pass in a username and password to retrieve my client id from some oauth endpoint. Am I thinking about this wrong?
Also, from reading the OAuth specs I'm under the impression that a client id and client secret are all that should be supplied for getting granted an access token. Yet the implementation I'm using defaults to forcing the user to supply a client id, client secret, username, and password. I've overridden the implementation to require only the client id and secret, but I want to make sure that was the right call and I'm not missing something.
Edit for flup's response:
I'm dealing with a Django API as the resource server, and a user of an iPhone app as the resource owner. The iPhone app is directly associated with the server -- in other words, there are no third parties involved here and no plans to involve them in the future; all software is ours. I would think that the password flow would be what I would need in that case. Indeed, that seems to be what django-oauth2-provider supplies by default. I'd like to stay somewhat in line with what they are doing to not have to completely reinvent the wheel.
The goal of oauth2 is to let the resource owner give a client a valet key which authorizes it to access certain resources on your server on his behalf.
If there are no third parties involved, there is no client to authorize and no need to use oauth2.
Instead, you could use the standard authentication mechanisms present in tastypie.