I am playing around with Amazon Cognito and after reading some of the docs and creating a user pool I am running into some issues. I believe that a cognito user pool can be used with OpenId to redirect the user to a hosted UI for user authentication (without federating out to another provider). I have tried to use the authentication options in DotNetCore 2 to do this as this is something I have done previous with other providers.
I have the following:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.ResponseType = "code";
options.MetadataAddress = $"https://cognito-idp.{authOptions.AwsRegion}.amazonaws.com/{authOptions.PoolId}/.well-known/openid-configuration";
options.ClientId = authOptions.ClientId;
options.ClientSecret = authOptions.ClientSecret;
});
but everytime I try it always returns
{"code":"BadRequest","message":"The server did not understand the operation that was requested.","type":"client"}
Just wondering if anyone had any experience with this please? I have tried to create the user pool in different regions just to make sure that it is not only supported in certain regions but always get the same.
I used to have the same problem. Configured my pool and code according to this tutorial. The crucial part was
Another configuration that may be important is the App integration > Domain name. It allows us to configure what will be the domain of the sign-in and sign-up pages.
After I configured domian name everything worked fine.
Related
I'm trying to get some basic analytics for a Cognito user pool. It seemed simple enough to do, I created a project in Pinpoint, then I went to my user pool, went to General settings > Analytics, clicked the Add Analytics button, choose the Amazon Cognito app client that my app uses, pointed to the Pinpoint project I just created, checked the "Share user profile data" checkbox (though I assume that's not absolutely necessary), and it had the message telling me if would use the IAM role and so on. Clicked Save, got no error, I assumed at this point I would start seeing Analytics in Pinpoint, but there's absolutely nothing showing up.I do have a message saying I haven't enabled any features yet, but I don't see any features I'd need to enable. I don't care about the campaigns as of now, and then under Application analytics it seems geared to you manually updating your mobile or web app to send something, but I thought that was if you need to customize something. Am I mistaken? Will this integration only work if you change your web app to explicitly send things to Pinpoint? I just assumed if I connected Cognito and Pinpoint the analytics would show since Cognito obviously knows people are logging in without you needing to manually make some extra request.
From my research, I found out that since you are using a Web Application without using AWS Amplify framework, you need to add additional lines of code to your application in order to send Cognito authentication analytics data to your Amazon Pinpoint project.
If you are using the Javascript SDK you may add the initate-Auth property code snippet to your front-end application:
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
var params = {
AuthFlow: "USER_PASSWORD_AUTH",
ClientId: 'STRING_VALUE', /* the client ID attached to the Pinpoint project */
AuthParameters: {
'USERNAME': 'STRING_VALUE',
'PASSWORD': 'STRING_VALUE'
},
AnalyticsMetadata: {
AnalyticsEndpointId: 'STRING_VALUE' /* the Pinpoint project ID */
},
};
cognitoidentityserviceprovider.initiateAuth(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
In the above code snippet, the Pinpoint project/application ID is added as part of the "AnalyticsMetadata" parameter when the client makes the API calls (e.g. sign-in, sign-up, etc.) to the Cognito user pool. Therefore, the API calls will have the pinpoint project ID attached to them and Cognito can use that information to send the data to your Pinpoint project. Without this crucial step, analytics data will not be sent to Pinpoint and will result in the behavior you have described.
If using CLI (for verification/testing purpose), you may execute the following AWS CLI initiate-auth command below :
$ aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH --auth-parameters USERNAME=STRING_VALUE,PASSWORD=STRING_VALUE --client-id STRING_VALUE --analytics-metadata AnalyticsEndpointId=STRING_VALUE
The take away point is that :
Amazon Cognito integration with Amazon Pinpoint only works for apps (clients) that are using Cognito SDK for Mobile Apps (AWS Mobile SDKs or JavaScript - AWS Amplify JavaScript library).
Just a note to syumaK's response, yes you need to update your code's initAuth call to include the AnalyticsMetadat property. However, I found out that the AnalyticsEndpointId should NOT be the Pinpoint project ID.
I believe that since you've configured Cognito already to integrate with the pinpoint project, it knows which pinpoint project you are sending metrics to already. The AnalyticsEndpointId should be a uniquely identifier for the login.
The documentation is definitely flaky here. The cli one from syumaK's seems to describe it differently than Cognito API Reference
What happened to me was that I used the pinpoint project id, and when I log in with multiple users, it thinks it's the same one. The daily active users remains at one. AWS Support clarified that it should be a unique identifier for each user. So after changing the id to be the user's email, I am starting to get accurate numbers for the daily active users.
It also seems like this is just about as useful as it is going to be for not using the Cognito SDK for Mobile Apps or Amplify. You can only get information like daily/monthly active users, authentication metrics. You don't have the ability to further break down by segments, etc.
Quick Overview: I have a ruby app that runs nightly and does something with a user's google calendar. The user has already given access via a separate react app. I'm having trouble getting the ruby app to access the user's calendar with the authorization code from the react app.
Details: I have a React front-end that can sign in a user using gapi and subsequently sign the user into Firebase. Here is how I configure the gapi obj:
this.auth2 = await loadAuth2WithProps({
apiKey: config.apiKey, // from firebase
clientId: config.clientId, // from gcp
// ....
access_type: "offline", // so we get get authorization code
})
Here is sign in:
doSignInWithGoogle = async () => {
const googleUser = await this.auth2.signIn();
const token = googleUser.getAuthResponse().id_token;
const credential = app.auth.GoogleAuthProvider.credential(token);
return this.auth.signInWithCredential(credential);
};
The user's next step is to grant the app offline access to their calendar:
doConnectGoogleCalendar = async () => {
const params = {scope:scopes};
const result = await this.auth2.grantOfflineAccess(params);
console.log(result.code); // logs: "4/ygFsjdK....."
};
At this point the front end has the authorization code that can be passed to a server-side application to be exchanged for access and refresh tokens. I haven't been able to find a good way to use a user supplied auth-code to make calls to available scopes. This is how I've configured the oauth client:
auth_client = Google::APIClient::ClientSecrets.load(
File.join(Rails.root,'config','client_secrets.json') // downloaded from GCP
).to_authorization
^ I'm using the same GCP Credentials on the backend that I'm using for the frontend. It is a "OAuth 2.0 Client ID" type of credential. I'm unsure if this is good practice or not. Also, do I need to define the same config that I do on the frontend (like access_type and scope)?.
Next I do what the docs say to get the access and refresh tokens(click Ruby):
auth_client.code = authorization_code_from_frontend
auth_client.fetch_access_token!
---------
Signet::AuthorizationError (Authorization failed. Server message:)
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
Is there something I'm missing in setting up a separate backend application that can handle offline access to a user granted scope? There is so much different information on these libraries but I haven't been able to distill it down to something that works.
UPDATE
I found this page describing the "one-time-code flow" which I haven't found anywhere else is all of the docs I've gone through. It does answer one of my minor questions above: Yes, you can use the same client secrets as the web application for the backend. (see the full example at the bottom where they do just that). I'll explore it more and see if my bigger problem can be resolved. Also going to update the title to include one-time-code flow.
After a good amount of digging through code samples and source code, I have a clean working solution. Once I found the page in my "update" it led me to finding out that ClientSecrets way I was doing things had been deprecated in favor of the google-auth-library-ruby project. I'm glad I found it because it seems to be a more complete solution as it handles all of the token management for you. Here is code to setup everything:
def authorizer
client_secrets_path = File.join(Rails.root,'config','client_secrets.json')
client_id = Google::Auth::ClientId.from_file(client_secrets_path)
scope = [Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY]
redis = Redis.new(url: Rails.application.secrets.redis_url)
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: redis)
Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, "postmessage")
end
and then this is how I use the authorization code:
def exchange_for_token(user_id,auth_code)
credentials_opts = {user_id:user_id,code:auth_code}
credentials = authorizer.get_and_store_credentials_from_code(credentials_opts)
end
after calling that method the library will store the exchanged tokens in Redis (you can configure where to store) for later use like this:
def run_job(user_id)
credentials = authorizer.get_credentials(user_id)
service = Google::Apis::CalendarV3::CalendarService.new
service.authorization = credentials
calendar_list = service.list_calendar_lists.items
# ... do more things ...
end
There is so much info out there that it is difficult to isolate what applies to each condition. Hopefully this helps anyone else that gets stuck with the "one-time-code flow" so they don't spend days banging their head on their desk.
I am having a hard time getting this to work by following along with Amazon's Alexa documentation. I'm running aground on Account Linking because I can't figure out how to get Login with Amazon (LWA) to ask for alexa::skills:account_linking scope.
I've included the Amazon API library in my application and set that all up correctly and I'm invoking the process using the (globally available) amazon object as follows (typescript):
const options: any = {};
options.scope = ['profile', 'alexa::skills:account_linking'];
options.scope_data = {
profile : {essential: false}
};
options.response_type = 'code';
const self = this;
amazon.Login.authorize(options, (response) => {
if (!response || !response.code) {
throw { error: response };
}
// ... send the response code to my server
// ... to be exchanged for bearer and refresh tokens
});
What I would expect to happen from that is a popup Amazon login process to be spawned which (1) has the user log in to Amazon, and (2) collects the user's consent to link their Amazon account to my Alexa skill (i.e. linked to my credentialed hosted service), so that we get back (in the browser) an authorization code that we can exchange (on our server) for bearer and refresh tokens to act on behalf of the user.
The problem is, that code above immediately fails and never pops up a process. The message that is thrown says: "An unknown scope was requested". If I remove the 'alexa::skills:account_linking' string from the options.scope array, I get to an Amazon login screen, and if I log in to Amazon, my server does get an authorization code, etc. But no Account Linking has taken place, so I'm stuck.
I've tried to reconcile this documentation (which also talks about including a Skill ID somehow), with this documentation but I'm just not seeing how to make it work. Can anyone please help point me in the right direction about what I'm doing wrong here? It must be something pretty fundamental.
If your goal is to use Login with Amazon for account linking only for the skill and to not store the tokens on your own server, you can set up the skill and Login with Amazon with the below configurations. The advantage of this approach is that you don't need to stand up your own web server to just handle the LwA flow. This approach also handles all the flow out of the box, including refreshing tokens.
If you're using these tokens for another purpose, you may want to look into something like AWS Cognito to simplify the process.
Skill Account Linking Configuration
Replace Your Client ID with the LwA Client ID, replace Your Secret with the LwA Client Secret, and copy your redirect URIs
LwA Configuration
Paste your Alexa redirect URLs here. These will be specific to your vendor account so it's important to have the right ones.
Source: This is what I do for my Aberto Sonorus skill: https://www.amazon.com/WBPhoto-Aberto-Sonorus/dp/B078W199Z3 (edited screenshots attached)
In my Android project, I use the AWS SDK to register users thanks to Cognito and call APIs in API Gateway. In my Cognito user pool, I created a user pool group. The purpose of that group is to only allow the users in that group to call a specific API.
In order to make it work, I try to follow this tutorial (especially the video). So I created a Cognito authorizer, added it to my method request in API Gateway, and try to call the API from my app using the AWS SDK:
#Service(endpoint = "https://abcdefghig.execute-api.eu-central-1.amazonaws.com/staging")
public interface AwsdemoapiClient
{
#Operation(path = "/test-api", method = "GET")
Empty testApiGet();
}
The problem is: whether the user is authenticated or not, and in the group or not, I always get the following error, when I call testApiGet() in my app: 401 Unauthorized, even if I have the right authorization in my IAM roles. After some research, it looks like the id token is missing, which could be the reason why I get that error.
But isn't it supposed to be automatically managed by the AWS SDK for Android? How can I fix that?
Thanks for your help.
Sending the id token in the header actually solved the problem:
#Service(endpoint = "https://abcdefghig.execute-api.eu-central-1.amazonaws.com/staging")
public interface AwsdemoapiClient
{
#Operation(path = "/test-api", method = "GET")
Empty testApiGet(#Parameter(name = "Authorization", location = "header") String idToken);;
}
You can get the id token by calling the following function:
CognitoUserPool pool = new CognitoUserPool(context, userPoolId, clientId, clientSecret, region);
pool.getCurrentUser().getSessionInBackground(...);
There is a setting I want to change via Python SDK reguarding AWS Cognito. I can change the setting in the AWS Web Console via "Cognito -> User Pools -> App Client Settings -> Cognito User Pool" (See image)
Here is my code
client = boto3.client('cognito-idp')
client.update_user_pool_client(
UserPoolId=USER_POOL_ID,
ClientId=user_pool_client_id,
SupportedIdentityProviders=[
'CognitoUserPool'
]
)
The error I am receiving is
An error occurred (InvalidParameterException) when calling the
UpdateUserPoolClient operation: The provider CognitoUserPool
does not exist for User Pool xxxxxx
It is unclear what string values I should pass for SupportedIdentityProviders. The only hint I have seen is from https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html
--supported-identity-providers '["MySAMLIdP", "LoginWithAmazon"]'
I am not even 100% sure if the SupportedIdentityProviders relates to the setting I am trying to change, but can't find any clarification in the docs.
The correct value to pass is COGNITO
client.update_user_pool_client(
UserPoolId=USER_POOL_ID,
ClientId=user_pool_client_id,
SupportedIdentityProviders=[
'COGNITO'
]
)
I only discovered this by reviewing source code of someone else CloudFormation Custom resource https://github.com/rosberglinhares/CloudFormationCognitoCustomResources/blob/master/SampleInfrastructure.template.yaml#L105
I can not find the correct soluion to this from offical AWS Docs/Boto3 docs. If anyone knows where the possible values for SupportedIdentityProviders are documented please comment.
For SAML/ OIDC, the array of provider names can be passed as SupportedIdentityProviders when update user pool client.
In order to update the existing SupportedIdentityProviders in user pool client, first fetch the existing SupportedIdentityProviders using describeUserPoolClient function.
Then you can push your provider name to exisiting SupportedIdentityProviders and update the user pool client with this value.