I’m building a React app, using API Gateway, lambda and cognito (basically starting from the https://serverless-stack.com tutorial). I would like to setup fine grained access control to my DynamoDb (i.e. through IAM policies that restrict access to DynamoDb tables based upon the logged-in user - like https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_rows.html)
AFAIK, a lambda function assumes a service role, as defined in the serverless.yml file, that has in itself nothing to do with the AIM policy that is attached to the logged in cognito user. I know that using an aim_authorizer, I can get info on the logged in user.
My question: is it possible to have the lambda do AWS calls on behalf of the given cognito user, thus honoring the IAM policies attached to that user? (a bit similar as to how the serverless-stack tutorial interacts with S3)
All suggestions welcome.
You can explicitly specify to any AWS client library which credentials to use in order to sign requests (by default they are taken from the runtime environment):
import { DocumentClient } from 'aws-sdk/clients/dynamodb';
const client = new DocumentClient({
credentials: ...
});
Those security credentials are obtain via STS. There are various scenarios how to get a hold of the user's identity to obtain credentials, but usually you would either assumeRole, if you have an arn of a role, or assumeRoleWithWebIdentity, if there is an actual user that did a flow of OpenID Connect:
import { Credentials, STS } from 'aws-sdk';
const sts = new STS();
const stsResponse = await sts.assumeRole({ RoleArn: 'can-be-cognito-group-arn' }).promise();
// or
// const stsResponse = await sts.assumeRoleWithWebIdentity({ WebIdentityToken: 'open-id-token' }).promise();
const credentials = new Credentials(
response.Credentials.AccessKeyId,
response.Credentials.SecretAccessKey,
response.Credentials.SessionToken);
Related
We have a Cognito User Pool which is connected to an AppSync API. In the graphql.schema we limit which users can access which endpoints like this:
type Mutation {
createProject(projectInput: CreateProjectInput!): Project!
#aws_auth(cognito_groups: ["StandardUsers"])
}
The AppSync endpoints fire Lambdas which get the details of the Cognito user used to authenticate like this:
const cognitoIdentity: AppSyncIdentityCognito = event.identity as AppSyncIdentityCognito
const user: User = {
id: cognitoIdentity.sub,
username: cognitoIdentity.username,
groups: cognitoIdentity.groups
}
We're authenticating using the Amplify JS library.
The user can happily hit the endpoint when part of the Cognito group, but if I remove them (via the AWS console) they can continue to hit the endpoint!!
If they logout and back in they are then denied access to the endpoint.
How can have an immediate "kill switch" to ensure users no longer have these privileges?
Cognito is a stateless authentication method by design. Once a user logs in they are given tokens for that particular session. So until these tokens expire they get all the privileges defined in these tokens. You cannot manually expire these tokens once issued, but you can blacklist them using your own implementation.
Your requirement is for stateful authentication. You will need to implement this yourself using a database and custom authorizers that check for "blacklisted" tokens.
I'm adding custom claims to Cognito's ID token using the "Pre Token Generation" trigger.
Problem
The lambda is triggered, but the issued ID Token doesn't include the claims I added. Am I missing something?
My setup
Using OAuth 2.0 with authorization code flow
My client app sits behind a load balancer (alb). The alb interacts with Cognito to get the Access + ID Tokens in the form of a ALBSessionCookie. Very similar to [0]
To get the ID Token, the client calls a custom endpoint to my backend with the ALBSessionCookie. The backend uses that cookie to return a decoded ID Token to the user. This is the ID Token that I expect should have the custom claims included.
[0] https://www.exampleloadbalancer.com/auth_detail.html
Lambda function (pre-token generation trigger)
Format taken from https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html#aws-lambda-triggers-pre-token-generation-example-1
exports.handler = (event, context, callback) => {
event.response = {
"claimsOverrideDetails": {
"claimsToAddOrOverride": {
"my-custom-claims-namespace": JSON.stringify({
"custom-claim-1": "hello",
"custom-claim-2": "hello",
})
}
}
}
callback(null, event)
}
If I can't make this work with ALB, what are some workarounds? Some ideas:
Call Cognito directly for an ID Token (somehow), hoping that will trigger the lambda to issue a JWT with the custom claims
Call Cognito via AmplifyJS
I have a feeling this is expected behavior, though seems like a limitation. Looking here:
https://www.exampleloadbalancer.com/auth_detail.html
We can see that the following steps occur:
ALB receives JWT (ID token, Access Token)
ALB to send access token
ALB receives user info(claims)
I believe the ALB is then not sending the contents of the Decoded ID token (That were manipulated by the Lambda trigger) back to the backend but instead sends the 'user info(claims)' (returned from the UserInfo endpoint) which are not effected by the Cognito trigger.
Yeah the ALB doesn't work that way, the ID Token that Lambda trigger customizes is the one you get when a user Authenticates. There are a couple of options.
Custom User Attributes
The least invasive IMO if instead of adding these attributes in the Lambda trigger, you could have them as custom attributes in Cognito, these I do believe will be in this token. You can sync these attributes at each successful Authorization. That may meet your requirements.
API GW
You could put an API GW either between your LB and your APP or infront of your LB. The API GW does give you a layer in which you can do all this stuff and more with customizing headers, tokens etc. For example you could have a Lambda Authorizer which reads this access token, and returns a context which you can reference in your integration requests back to your backend. It's a bit more involved and will add at least some latency to your app, although you can safely have a large TTL on your auth response because your LB is already doing Auth and you only want some extra attributes. You could also do a re-design and put this all in API GW and get all the bells and whistles it has but you might not need them.
But yeah probably easiest to use the first option if possible as that won't require you to do a redesign and you will just need to change your attribute names to custom:....
I have a simple question:
I am developing a website that needs full authorisation to make requests to a Google calendar. I manage to do all the requests I need with javascript from a web server and it works, but I need to be logged into my google account for it to work. This caused a problem for other users that use my website because the request doesn't work if they are not logged into MY google account.
I understand why it doesn't work, my question is How can I do for my website to get full access granted for use the google calendar without having to log into my google account, even better if nobody had to log into a Google account to perform the task??
The form of login you are currently using is called Oauth2. It requires that a user authenticate access.
What you should be using is a service account. Service accounts are pre authorized. You will need to share your personal calendar with the service account then it will be able to access it.
The only drawback is that service account authentication is not supported by JavaScript you will need to switch to a server sided language like node.js for example.
'use strict';
const {google} = require('googleapis');
const path = require('path');
/**
* The JWT authorization is ideal for performing server-to-server
* communication without asking for user consent.
*
* Suggested reading for Admin SDK users using service accounts:
* https://developers.google.com/admin-sdk/directory/v1/guides/delegation
*
* See the defaultauth.js sample for an alternate way of fetching compute credentials.
*/
async function runSample () {
// Create a new JWT client using the key file downloaded from the Google Developer Console
const client = await google.auth.getClient({
keyFile: path.join(__dirname, 'jwt.keys.json'),
scopes: 'https://www.googleapis.com/auth/drive.readonly'
});
// Obtain a new drive client, making sure you pass along the auth client
const drive = google.drive({
version: 'v2',
auth: client
});
// Make an authorized request to list Drive files.
const res = await drive.files.list();
console.log(res.data);
return res.data;
}
if (module === require.main) {
runSample().catch(console.error);
}
// Exports for unit testing purposes
module.exports = { runSample };
Code ripped from smaples jwt
These are the calls I'm making to get the access token from AWS Cognito. I am implementing the Developer Authenticated Identities workflow
where I authenticate the user on my backend. My code:
cognitoIndentityClient = Aws::CognitoIdentity::Client.new(
region: 'us-east-1',
credentials: permanent_aws_creds,
)
developerProviderName = '1.Got From Developer Provider Name under Custom in Cognito Console'
identityPoolId = 'us-east-1:Xxxxx'
resp = cognitoIndentityClient.get_open_id_token_for_developer_identity(
identity_pool_id: identityPoolId,
logins: {
developerProviderName => UniqueIdentityTokenProviderFromMYBackend
}
)
resp2 = cognitoIndentityClient.get_credentials_for_identity(
{
identity_id: resp['identity_id'],
logins: {
'cognito-identity.amazonaws.com' => resp['token']
}
}
)
My Question:
1. How can I create a user in the user pool (enable MFA and all that) after the above calls? I can see that Identities are created in my console but I'm lost after that.
Can you check that in identity pool configuration in custom tab of Authentication providers section you have the developer provider name set and it matches with the value in your code above? This might be one possible reason for the error.
To answer your other question. You do not need to implement developer authenticated identities to use the 'User Pools' feature of Cognito. These are two independent features. Cognito developer authenticated identities allows you to federate your own authentication system with Cognito identity. If you want Cognito to manage your users and allow username and password based sign-up, sign-ins and MFA for you, 'User Pools' feature will be correct choice. The user managed by User pools can also federate with Cognito identity.
I have a website that uses AWS Cognito (via Amplify) for user login. The API is on a separate stack that deploys with Serverless.
I am trying to have an API endpoint that can access the current logged-in user's Cognito User Pool data (username, email) if it is available. The only way I've been able to achieve this is by using a cognito user pool authorizer via API Gateway.
Example:
functions:
getMe:
handler: /endpoints/myService.get
events:
- http:
path: /myService
method: GET
cors: true
authorizer:
type: COGNITO_USER_POOLS
authorizerId: ${self:custom.apiGatewayAuthorizerId.${self:custom.stage}}
Where authorizerId is set to the 6-character Authorizer ID found on the AWS Console's API Gateway Authorizers page. However, this blocks all traffic that is not authenticated with Cognito. That isn't what I want, since I have a number of services that should be accessible by both anonymous and logged-in users. I just want to personalize the data for users that are logged-in.
Is there any way to allow traffic and pass the cognito user parameters through the API Gateway to Lambda if they are available?
All resources I've been able to find regarding Cognito + API Gateway + Lambda are specifically about restricting access to endpoints and not layering on data to the requests...
Based on comments above you want Anonymous and Logged-in users pass through same gateway end point ?
You can still use the same setup but remove the authentication from API Gateway and take the logic in your application.
If users try to access your services while being logged in AWS amplify will send through the Authorization header with Id token to API Gateway and API Gateway will pass this header as it is to the application. You will have to check inside your application for this Authorization header and crack open Id token passed to find the user claims/attributes and do your logic. For any other user that doesn't have this token can be considered anonymous.
You still need to Validate the token if you find one in request to make sure it's a valid token and extract claims/Attributes thereafter.