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.
Related
I have the following setup:
REST API Gateway containing one endpoint with a proxy Lambda integration and secured using the AWS_IAM authorizer. End users retrieve temporary AWS credentials from a Cognito Identity Pool in exchange for tokens retrieved from a Cognito User Pool and then call the API using these credentials.
When users call the API Gateway, I want the invoked lambda function to run with the same permissions as the invoking user. For example, if the user only has the permission to read items from DynamoDB with a PK of CUSTOMER1234, I want the lambda function to also only have that permission. Different users will have different permissions so I can't "hard-code" that into my Lambda permissions.
This seems like a fairly common use case, but I was unable to find any documentation on how to achieve something like this.
One possibility would be to send the AWS credentials (i.e. access key and secret key) of the user with every request and then construct a new session using these credentials in the Lambda, but this probably is a terrible idea (it also increases function runtime by 5x).
It doesn't seem like a good idea to send AWS credentials over the network, as there is the chance that they could be intercepted by an attacker and used to access your resources until they expired.
It could be possible to use the JWT from your Cognito User Pool to get the AWS credentials in the lambda function e.g. using:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'IDENTITY_POOL_ID',
Logins: {
'cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>': 'ID_TOKEN'
}
});
This would mean that your lambda's API calls would run using the role mapped to in your identity pool, and you could use the LeadingKeys condition to ensure that your web identity could only access their own data:
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["${cognito-identity.amazonaws.com:sub}"]
}
}
However, this would add processing time because you are making extra API calls (CognitoIdentityCredentials() makes 2 API calls behind the scenes) on every request to get the credentials. Plus, you couldn't use the AWS_IAM authorization on your API Gateway method because you won't have got the credentials yet. So you are then talking about having a two authorization setups. One to allow your web identity to call the API Gateway method, and a second for your Lambda function to run as your user.
So perhaps your choices are:
Make your API calls direct from your client
Instead of going via API Gateway, you could make the DynamoDB API calls directly from the client. This would work well with your Identity Pool setup, as you can make the CognitoIdentityCredentials() call just once when the user logs in to get the AWS credentials. The subsequent DynamoDB calls would then be made with your user permissions and you could use the LeadingKeys condition to ensure that your user only accesses their own data.
Accept that your lambda function will always run with the same execution role
If you want to use API Gateway backed with Lambda, then you could just accept that you will use Lambda or Cognito Authorizers to authorize your users have permissions to execute the API method, and then some further application-level authorization in the Lambda function to ensure they only call their own partition space. In fact, this way you might not need the Identity Pool, but rather just use the sub claim from the User Pool JWT token. After all, you will have validated the JWT containing the sub hasn't been tampered with, and that it was issued by the correct user pool and application client, so you can trust its contents.
You could integrate API Gateway directly with DynamoDB
Depending on your required logic, rather than have your API method backed by a lambda function you could integrate your API Gateway method directly with DynamoDB as shown in this amazon tutorial. Again, you would use the sub from the validated JWT as the partition key to enforce the correct data access.
I would like my client application to insert records in my dynamoDb instance using API gateway secured with Cognito user pools.
I have created my user pool and added it as an authorizer to my API gateway method call. Using AWS Cli I ran the following command which gave me my access token:
aws cognito-idp initiate-auth ...
My infrastructure seems to be working, now which direction do I need to go to pragmatically achieve signing-in as my user in the user pool, grabbing the token and calling my API method?
Well it's not difficult. You need to follow certain steps.
Create an user in Cognito user pool. Confirm it, by the means of activation message you have chosen. It can be sms or email as per the user pool settings.
After you confirm the user, you need to call the login API from Cognito SDK. Since I am comfortable in NodeJS, let me grab the method name - https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html. Set AuthFLow to ADMIN_NO_SRP_AUTH .The response from this APi will have one idToken, one accessToken and one refreshToken. Since you need these credentials at your client, write an API in your preferred language, expose it your client and return the tokens.
Use the idToken to make API calls to your API Gateway Authorizer. This is how you pass the token using Postman -
You can replicate the same using any client. If you face any error, It'd be better if you show me your APIG authorizer configuration.
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.
Im playing around with Lambda trying to use it to authenticate a web app. Im using lambdAuth as a starter to get things going. https://github.com/danilop/LambdAuth
I want to have an api-gateway service that first authorizes a member, returning the token from cognito. All the subsequent services in api-gateway then somehow needs to accept what was returned from cognito to allow access to the service, and fail without it. Im kinda confused with how to use cognito. Im assuming you restrict your api-gateway services by adding the AWS_IAM tag to the Authorization of your service, but I dont know how to then call that service...?
In the current implementation of LambdAuth, it does all of this client side (in the browser), calling the lambdas directly. It gets the AWS.config.credentials, adds the IdentityId and Logins that came back from cognito to it and then calls the lambda function that requires you to be logged in. How will this work when calling api-gateway instead of lambda. How do i take what came back from cognito, and add it to my service call in order to pass that AWS_IAM authorization?
Any help will be appreciated, or if im missing the boat completely thats also possible...
For the lambda functions handling auth behind API Gateway, you would need them to be unauthorized, as your users have not logged in yet.
For the lambda functions behind API Gateway that ARE authorized, you will need to pass in the credentials you acquired from Cognito when instantiating your client.
It looks like you are doing developer authentication, so when you get a Cognito Token from your backend/lambda functions, in your app you will need to get credentials still:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'IDENTITY_POOL_ID',
IdentityId: 'IDENTITY_ID_RETURNED_FROM_YOUR_PROVIDER',
Logins: {
'cognito-identity.amazonaws.com': 'TOKEN_RETURNED_FROM_YOUR_API'
}
});
Then, from your credentials you will need the access key, secret key, and session key to instantiate your API Gateway Client:
Instantiating your API Gateway Client:
var client = apigClientFactory.newClient({
accessKey: ACCESS_KEY,
secretKey: SECRET_KEY,
sessionToken: SESSION_TOKEN });
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.