Google+ Auth in AWS Cognito and API Gateway is unauthorized - amazon-web-services

AWS Wizards,
I've created a POC web app to test AWS Cognito and Federated Identities for a project.
I extended the POC with Google+ authentication. When a user authenticates via Google the counter for the Google Signin is increasing in Federated Identities, they are obviously being verified.
Now I am trying to use the credentials with a secured AWS API Gateway endpoint with no luck. The user will remain unauth'ed according to the gateway. I've tried the test feature in the API Gateway authorizers config, if I test with a Cognito user CognitoUserSession.idToken the test returns the claims.
If I do the same test with a Google user's idToken it is unauthenticated. After a successful Google sign in the same AWS Javascript SDK code is executed (as the Cognito user login). The credentials are updated with an identity ID and a session token but no ID token.
As I am a total noob on AWS I am probably missing something here. What should I do to get a valid idToken? Should it be done in a different way?
(GoogleLogin is a React component)
render() {
return (
<GoogleLogin
clientId="44xxx11.apps.googleusercontent.com"
buttonText="Google Login"
onSuccess={this.responseGoogle}
onFailure={this.responseGoogle}
/>
)
}
responseGoogle(response) {
console.log(response.tokenId)
cognitoIdentityCredentials(response.tokenId)
}
cognitoIdentityCredentials(idToken) {
const loginsObj = {
['accounts.google.com']: idToken
}
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: IDENTITY_POOL_ID, // your identity pool id here
Logins: loginsObj
})
AWS.config.credentials.get(function(err) {
if ( err ) {
console.log(err);
} else {
console.log("---- AWS credentials ----")
console.log(AWS.config.credentials)
// Got credentials, but no valid idToken
}
})
}

The solution to this problem is that you can't really use AWS Security the way I assumed. In AWS API Gateway a generated library can be downloaded to access that API specifically and when using the lib we found that there are more fields added to the request, and yes they are signed. All of them.
It will be a pain to use different SDK libraries for different environments and apps, so we simply took one of the SDKs and made it into a general library that we can use for all requests in all web projects. There are similar projects already on Github, but they are unfortunately only working with Cognito users, not Federated Identities.
Happy Panda again.

Related

How to avoid hard coding access key and security key values aws

In my js file, I am accessing my AWS sns, but the main issue is that currently, I am hard coding my IAM access and security keys... Is there a way to avoid doing this by using a temporary key or hiding my keys? I have posted a snippet on how I am updating the config below.
var AWS = require('aws-sdk');
AWS.config.update({
region: < 'My Hard coded region' >,
credentials: {
accessKeyId: < 'My Hard coded Access Key' >,
secretAccessKey: <'My Hard Coded Security Key'>
}
});
...
PLEASE HELP.
It's very hard to comment without knowing more about your code.
The react native is a mobile app. You will need a service to authenticate your mobile app users against the service. One such service is amazon Cognito.
Userpool
Amazon Cognito has a concept called user pool. The user pool is where the users are stored.
When you authenticate a user against a user pool, you will get a authorised token in return. Your app can store this token and send it to your backend services. The backend services can validate the token to confirm the identity of the user.
Identity pool
The identity pool is a concept where temporary AWS credentials are issued in exchange for an authorised token. This is the temporary AWS credentials, using which you can call aws services directly from the mobile app.
Hope this helps.
here is an article for you to read - https://pusher.com/tutorials/serverless-react-native-aws-amplify
Yes there are much more secure ways to handle credentials. Hard coding them is the least secure.
While it is possible to do so, we do not recommend hard-coding your AWS credentials in your application. Hard-coding credentials poses a risk of exposing your access key ID and secret access key
See https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html
Also see From Best Practices for Managing AWS Access Keys:
Don't embed access keys directly into code. The AWS SDKs and the AWS Command Line Tools allow you to put access keys in known locations so that you do not have to keep them in code.
See https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html
The most secure method is to load the credentials from AWS Identity and Access Management (IAM) roles for Amazon EC2.
Each SDK has a way to do this. Check your SDK documentation for loading credentials from IAM.
Here is a good overview for credentials best practices, with examples in Java: https://aws.amazon.com/blogs/developer/credentials-best-practices/
I came across this stackoverflow while googling how to solve this problem. Here's the approach I ended up using to solve this problem in case this helps anyone else. It will expand on the identity pool answer provided earlier.
My setup was this:
Static website hosted on s3, so no way to make AJAX calls back to the server itself to bypass hardcoding credentials.
The solution I ended up using was Cognito identity pools as mentioned. The setup involved creating an identity provider in IAM. We were using an company internal OpenID identity provider. Then the next step was to create an identity pool in cognito that pointed to that identity provider. Next was to provision an IAM role for signed in users of that identity pool to give them access to whatever resources you are trying to secure.
On the s3 website, we had already integrated with the internal OpenID identity provider, so it was just a matter of extracting the token and passing it to Cognito.
The program flow is as follows:
User logs in to open ID internal system.
This system returns a token
Code extracts token and then creates a Cognito Identity Credentials
https://docs.aws.amazon.com/cognito/latest/developerguide/getting-credentials.html
The below is a code snippet copy pasted from the docs linked above. Note that the Logins object is where we will pass the token. The key will be the "provider" field value configured in IAM.
AWS.config.region = 'us-east-1';
// Configure the credentials provider to use your identity pool
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'IDENTITY_POOL_ID',
Logins: { // optional tokens, used for authenticated login
'graph.facebook.com': 'FBTOKEN',
'www.amazon.com': 'AMAZONTOKEN',
'accounts.google.com': 'GOOGLETOKEN',
'appleid.apple.com': 'APPLETOKEN'
}
});
// Make the call to obtain credentials
AWS.config.credentials.get(function(){
// Credentials will be available when this function is called.
var accessKeyId = AWS.config.credentials.accessKeyId;
var secretAccessKey = AWS.config.credentials.secretAccessKey;
var sessionToken = AWS.config.credentials.sessionToken;
});
The call to cognito will return temporary credentials including a session token, which you can now pass to your other aws clients.

Use the token response from SAML authentication with User Pools to retrieve AWS Temporary Access keys

How do I use the token response from SAML authentication with User Pools to retrieve AWS Temporary Access keys and Make API Gateway Calls?
I have configured a Cognito User Pool with an associated App client. I have configured Okta as a 3rd Party SAML Identity provider. Using the Amazon hosted login https://[cognito domain name]/login?response_type=token&client_id=[your App client id]&redirect_uri=[your App client redirect URL] I am able to be redirected to my ReactJS application with the #access_token in the header.
I am trying to now user the #access_token to call API gateway. I have been following this guide as well as aws-amplify. To my understanding I need to use the #access_token to get AWS access keys to make the call to API gateway.
I am trying to do this with the following code:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxx-xxxx-xxxx-xxxx-xxxxxx',
Logins: {
'cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxx': #access_token
}
});
but not sure how this integrates with aws-amplify, and I have not successfully retrieved AWS temporary access keys to make API Gateway calls.
I previously had this working using users in the Cognito User Pool but now I need to include Okta as an identity provider.
I found I needed the #id_token rather than the #access_token to accomplish what I was trying to do. I enabled the #id_token by selecting the following options in my Cognito Pool App Client Settings:
I was then able to follow Cognito hosted UI.

How to get the authenticated user's email address in node.js AWS Lambda?

I have an Angular app that uses the AWS javascript SDK, and can successfully login with Facebook and Google. I am able to create the Cognito identity and I see the identity in my Federated Identity pool. I am also able to use my IAM authenticated API endpoints (I see the user's cognitoId in the Lambda I am calling).
I want to use the user's email address as the key in the database (DynamoDB), so I am trying to figure out how to access the user's email address from a trusted source, either Cognito (preferred) or the Authentication Provider's JWT.
What is the best way to access the user's email address?
Update: On the client side (javascript) I'm using Federated Identities with Google and Facebook tokens. The social login is working, and I can get authenticated with Cognito just fine, passing the Logins:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:****',
Logins: {
'accounts.google.com': authResult['id_token']
}
});
I then do AWS.config.credentials.get() and I can get things like AWS.config.credentials.identityId, but I can't find accessToken or the methods to call to get the JWT. I believe I need to call result.getAccessToken().getJwtToken(), but I can't find it.
All of the examples where they get the accessToken seem to use .authenticateUser(authenticationDetails()), but the details include a user name and password (not Federated login).
tldr; Send the accessToken post-authentication to lambda and use CognitoIdentityServiceProvider.getUser() to obtain the attributes.
I spent hours fumbling around with the ID tokens and from what I can tell, the only way to get a user's actual identity is to use a combination of the accessToken provided from a successful authentication and the getUser call from the identity provider. This only appears to be for the NodeJS SDK and nothing else.
I am using Amazon's API Gateway for my application and Lambda in the background. After a successful authentication, you can access the accessToken and feed it to a Cognito server provider. From there, you can save the details locally, but it presents a large security issue if you plan to use this information on the backend. Since it's local storage, the user can just adjust the user attributes to whatever they like.
To get around this, you can pass the accessToken to the lambda function directly and perform the same process on the server. Doing this ensures that the user attributes have not been manipulated or tampered with. Be sure to use HTTPS on your API to avoid leaking the accessToken to others who could use the AWS SDK to get the internal details.
var AWS = require("aws-sdk");
AWS.config.region = '<REGION-ID>';
exports.lambda_handler = function(event, context) {
var accessToken = event['accessToken'];
var provider = new AWS.CognitoIdentityServiceProvider();
provider.getUser({
AccessToken: accessToken
}, function(err, obj) {
context.succeed({
'user': obj
});
});
};

AWS API Gateway Authentication (Cognito Vs custom authrorizer)

I am trying to make a serverless application (ReactJS, API Gateway, AWS Lambda, DynamoDB) with federated authentication. Below is the kind of architectire that I am envisioning ( Have not added STS for brevity. Also I don not think that I understand the flow fully):
I have created API Gateway endpoints that trigger lambda functions. I want to authenticate my users with google first and if they are successful then they should be able to use API endpoints.
First step is to authenticate the user with google using standard Outh2. I have created 2 unauthenticated endpoints /signin/google and /callback/google for the purpose . Once I get the successful authentication response from google in callback lambda function I have id_token (among others) that I can use.
At this point in time I have 2 approaches that I can use for authenticating APIs.
Build a custom authorizer that I can use for API endpoints. Here is the code (https://github.com/prabhatsharma/api-gateway-custom-authorizer/). This is pretty straightforward. I can use the same id_token provided by google to authenticate API endpoints. Custom authorizer will validate that id_token is good and grant access to the endpoint. It will also cache the result so that this verification is not needed everytime. (Is this a good approaach to reuse the id_token of google in this way?) You can use the authorizer with this (https://github.com/prabhatsharma/lambda-custom-auth)
I can use AWS cognito for authentication. For this I have created a federated identity pool and have set the google app client Id to cognito console. In my /callback/google lambda function I am using AWS SDK to get the identityId, sessionToken and accessKeyId. (Source code https://github.com/prabhatsharma/lambda-cognito-auth)
// Add the Google access token to the Cognito credentials login map.
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: config.COGNITO_IDENTITY_POOL_ID, //dauth
Logins: {
'accounts.google.com': JSON.parse(body).id_token
}
});
Now I can use the credentials to get Token using following code
// Obtain AWS credentials
AWS.config.credentials.get(function () {
// Access AWS resources here.
// Credentials will be available when this function is called.
var identityId = AWS.config.credentials.identityId;
var cognitoidentity = new AWS.CognitoIdentity();
var params = {
IdentityId: identityId, /* required */
Logins: {
'accounts.google.com': JSON.parse(body).id_token
}
};
cognitoidentity.getOpenIdToken(params, function (err, data) {
if (err) console.log(err, err.stack); // an error occurred
else {
console.log(data); // successful response
res.headers = {
location: config.APPLICAION_URL + '/auth/google?' + querystring.stringify(data)
}
callback(null, res); //redirect to front end application
}
});
I can now pass the identityId and Token to my front end reactJS application.
This is where I need little help understanding the concept. I can now use the AWS SDK in browser to access AWS resources. But hold on!!! Wasn't the purpose of creating standard RESTful APIs via Gateway was to use standard javascript without reliance on any specific library? Using AWS SDK directly looks like a more apt use case for android/ios/unity apps. I would like developers in my team to be able to use the standard javascript libraries of front end that they use for this situation too. Also I do not want to use the exported API SDK for my API endpoints and really want to keep the front end app clean of AWS SDK specifics. Signing every request with v4 signature manually is redundant work. Can't we have standard token based API endpoint access using Cognito?
What is the best practice for this kind of authentication? Am I thinking in right direction?
Disclaimer - Please do not use the code in the repos as yet. It is work in progress and not yet production ready.
You can use your Custom Authorizer function with the oauth2 token. Alternatively,
you can use Cognito with the corresponding IAM roles to manage user access to your AWS resources.
The second approach, which you describe in the flow picture, does not need of an auth lambda in front of every request and lets you control access to other AWS resources from your frontend (like IoT i.e.).
Once you get the secretKey, accessKey, sessionToken and region, you only need to sign every request to APIG. You don't need the AWS SDK to do this.
If you don't want to use the exported API, you have to sign the requests yourself. It's implemented in the sigV4Client.js so it's fairly easy to copy. You are going to need few dependencies anyway, so why not use the exported API? You're using React already which is so big.

AWS Cognito HTTP authentication

I try to set up a test API with AWS API Gateway, Lambda and Cognito so secure the access. Since I am new to the AWS world, I am not sure how can I create a "logged in" post request to the AWS service with for example the request library
I guess on the client side I first have to log in via Cognito and the AWS Api and then use the informations I get to create a signed request like it is described here:http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html ?
If I am on the wrong path, please tell me and point me in the right direction :)
Preface:
I will explain the flow of Google+ integration with Cognito, it goes almost the same for others. I will use node.js (javascript), after that you can have your users authenticated from Google and authorized by IAM and Cognito to access API Gateway.
Cognito is a federated login service, it offers you to sync "configuration" of your mobile/web app. First you have to setup cognito with an identity provider, say Google+ for example. for that:
Create a Google app in your Developer console
Create a pool in cognito and add google as a provider, configure your pool with the policies and the roles (services you want to give your users access to, in this case only API Gateway).
In your web/mobile app, show the user Google+ Signin button, after the user clicks on it, google will call back a callback url with code parameter code, use that token
Use this code to get a Cognito identity for your user, in this case We trust Google:
var params = {
IdentityPoolId: setting.POOL_ID,
Logins: {
'accounts.google.com': google_token // Coming from Google OAuth2
}
}
// Get Id from Cognito
cognitoIdentity.getId(params, resolverFunction);
Get IAM temporary credentials forthat Identity IdentityId, your Google authenticated user:
var params = {
IdentityId: IdentityId,
Logins: {
'accounts.google.com': google_token // Coming from Google OAuth2
}
}
cognitoIdentity.getCredentialsForIdentity(params, resolverFunction)
Your user is now authenticated with Google and have the authorization from the IAM service (through the roles/policies you attached to your Cognito pool).
In your API Gateway, activate the IAM authorization, and use the Credentials you got from point 7.
Use the Accesskey, secretKey and the token to sign every request you make for your API built on top of API Gateway, you can you use this lib: aws-v4-sign-small
Quick notes and headsup:
All of this is Asynchronous actions, so If you are in node js, it is way better to use Promises (ES6 or Bluebird).
Pay super attention to the roles you attached (acces to dynamodb document or S3 file, etc. read more about IAM it is suer helpful and you can do a fine grained authorizations)
Hope it is clear or at least it gives you a direction to start with.
One of the benefits of using API Gateway is that you can automatically generate SDKs for your API, which easily integrate with Cognito credentials. This saves you from the trouble of implementing SigV4 auth yourself.
Here are a couple of simple examples using Cognito credentials with a generated JavaScript SDK:
https://github.com/rpgreen/aws-recipes/blob/master/app/index.html
https://github.com/awslabs/api-gateway-secure-pet-store
Cheers,
Ryan
As Ryan mentioned, the best way to do this is via the API Gateway SDK. The downside to using this stack is that it becomes harder to integrate with off the shelf front-end tools. You can no longer make direct request to your REST end-points, you will want to go through the SDK.
You definitely lose some ease of development because you can't just slap something like ngResource on top of your endpoints and call it a day. You'll have to set up the calls to each of your AWS end points in a service layer yourself.