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 });
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.
I'm developing web app based on Amazon API Gateway. Now I created Facebook login and successfully logged into website. but when I call another API, everything gone. I think I should pass Cognito token when call API everytime. am I right?
if yes, how to pass Cognito token to API? like header? or another way?
Thanks,
You are using the "Basic Authflow" from cognito identity, which means you will need to get credentials for your users by calling STS's "AssumeRoleWithWebIdentity". Here is some documentation to help: http://docs.aws.amazon.com/cognito/devguide/identity/concepts/authentication-flow/
Once you have credentials, you can instantiate the API Gateway Client:
var client = apigClientFactory.newClient({
accessKey: ACCESS_KEY,
secretKey: SECRET_KEY,
sessionToken: SESSION_TOKEN });
The keys and tokens come from the result of the "AssumeRoleWithWebIdentity" call.
If you have configured your IAM roles, and Authorizations correctly you should be able to access your API.
Here is the documentation describing how to configure the roles & authorization: http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-method-settings.html#how-to-method-settings-callers-console
Also, here is how to enable CORS - http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
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.
I'm developing web app based on Amazon API Gateway. Now I created Facebook login and successfully logged into website. but when I call another API, everything gone. I think I should pass Cognito token when call API everytime. am I right?
if yes, how to pass Cognito token to API? like header? or another way?
Thanks,
You are using the "Basic Authflow" from cognito identity, which means you will need to get credentials for your users by calling STS's "AssumeRoleWithWebIdentity". Here is some documentation to help: http://docs.aws.amazon.com/cognito/devguide/identity/concepts/authentication-flow/
Once you have credentials, you can instantiate the API Gateway Client:
var client = apigClientFactory.newClient({
accessKey: ACCESS_KEY,
secretKey: SECRET_KEY,
sessionToken: SESSION_TOKEN });
The keys and tokens come from the result of the "AssumeRoleWithWebIdentity" call.
If you have configured your IAM roles, and Authorizations correctly you should be able to access your API.
Here is the documentation describing how to configure the roles & authorization: http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-method-settings.html#how-to-method-settings-callers-console
Also, here is how to enable CORS - http://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html