How to Assume a Cross-Account Role for Cognito? - amazon-web-services

I have a Cognito userpool on AWS account acc-1, and a Java code running on acc-2, which authenticates using "adminInitiateAuth", and for some reasons, I cannot use clientInitiateAuth.
I have created a cross-account role on acc-1, to be assumed by my Java code on acc-2
Question: How can I assume the role when I am sending an authentication request to Cognito? Is it possible to use withRoleArn()?
I came across this page, which explains how to "Configure cross-account Amazon Cognito authorizer for a REST API using the API Gateway console". But it is not what I am trying to do.
My Code:
protected AdminInitiateAuthRequest createInitialRequest(String username, String password) {
Map<String, String> authParams = new HashMap<>();
authParams.put("USERNAME", username);
authParams.put("PASSWORD", password);
return new AdminInitiateAuthRequest()
.withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH)
.withAuthParameters(authParams)
.withClientId(whoAmIService.getCognitoClientId())
.withUserPoolId(whoAmIService.getCognitoPoolId());
}
protected boolean isAuthenticatedByCognito(String username, String password) {
AWSCognitoIdentityProvider awsCognitoIDPClient = createCognitoIDPClient();
AdminInitiateAuthRequest authRequest = createInitialRequest(username, password);
try {
AdminInitiateAuthResult authResponse = awsCognitoIDPClient.adminInitiateAuth(authRequest);
AuthenticationResultType authenticationResultType = authResponse.getAuthenticationResult();
String cognitoAccessToken = authenticationResultType.getAccessToken();
whoAmIService.setCognitoAccessToken(cognitoAccessToken);
Map<String, String> challengeParams = authResponse.getChallengeParameters();
String cognitoUserIdForSrp = challengeParams.get("USER_ID_FOR_SRP");
String cognitoUserAttributes = challengeParams.get("userAttributes");
logger.debug("Cognito authenticated user ID: {} with user attributes: {}"
, cognitoUserIdForSrp, cognitoUserAttributes);
return true;
} catch (NotAuthorizedException nae) {
logger.error("Invalid Cognito username/password provided for {}", username);
return false;
} catch (AWSCognitoIdentityProviderException acipe) {
logger.error("Base exception for all service exceptions thrown by Amazon Cognito Identity Provider", acipe);
return false;
}
}

I found how to do it using STS. Change this line:
AWSCognitoIdentityProvider awsCognitoIDPClient = createCognitoIDPClient();
to:
String roleARN= "YOUR_CROSS_ACCOUNT_ROLE_ARN";
String roleSessionName = "GIVE_A_SESSION_NAME";
AWSSecurityTokenService stsClient = AWSSecurityTokenServiceClientBuilder
.standard()
.withCredentials(new ProfileCredentialsProvider())
.build();
AssumeRoleRequest roleRequest = new AssumeRoleRequest()
.withRoleArn(roleARN)
.withRoleSessionName(roleSessionName);
AssumeRoleResult roleResponse = stsClient.assumeRole(roleRequest);
Credentials sessionCredentials = roleResponse.getCredentials();
BasicSessionCredentials awsCredentials = new BasicSessionCredentials(
sessionCredentials.getAccessKeyId(),
sessionCredentials.getSecretAccessKey(),
sessionCredentials.getSessionToken());
AWSCognitoIdentityProvider cognitoIPCB = AWSCognitoIdentityProviderClientBuilder
.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();

Related

DynamoDB client with auto refresh credentials

I'm creating DynamoDB client using IAM credentials obtained via STS assume role.
#Provides
public DynamoDbEnhancedClient DdbClientProvider() {
final AWSSecurityTokenServiceClientBuilder stsClientBuilder = AWSSecurityTokenServiceClientBuilder.standard()
.withClientConfiguration(new ClientConfiguration());
final AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest().withRoleSessionName("some.session.name");
assumeRoleRequest.setRoleArn("arnRole");
final AssumeRoleResult assumeRoleResult = stsClientBuilder.build().assumeRole(assumeRoleRequest);
final Credentials creds = assumeRoleResult.getCredentials();
final AwsSessionCredentials sessionCredentials = AwsSessionCredentials.create(creds.getAccessKeyId()
, creds.getSecretAccessKey(), creds.getSessionToken());
final AwsCredentialsProviderChain credsProvider = AwsCredentialsProviderChain.builder()
.credentialsProviders(StaticCredentialsProvider.create(sessionCredentials))
.build();
final DynamoDbClient ddbClient = DynamoDbClient.builder().region(Region.US_EAST_1)
.credentialsProvider(credsProvider).build();
final DynamoDbEnhancedClient ddbEnhancedclient =
DynamoDbEnhancedClient.builder().dynamoDbClient(ddbClient).build();
return ddbEnhancedClient;
}
The main lambda handler looks like below:
public void LambdaMainHandler {
DynamoDbEnhancedClient ddbClient;
#Inject
public LambdaMainHandler(final DynamoDbEnhancedClient client) {
this.ddbClient = client;
}
public LambdaResponse processRequest(final LambdaRequest request) {
QueryResponse queryResponse = client.query("...")
return LambdaResponse.builder().setContent(queryResponse.getByteBuffer()).build();
}
}
I'm using the DDB client in LambdaMain constructor.
Since this is running in Lambda behind APIGateway, how do I make sure the credentials are refreshed when they expire while executing LambdaMain handler?

AWS Cognito SDK for for USER_PASSWORD_AUTH flow

Cognito IDP allows below 3 AWS CLI to Signup, Confirm and Generate ID Token:
aws cognito-idp sign-up --region ap-south-1 --client-id xxx --username xx#xx.com --password xxx
aws cognito-idp confirm-sign-up --client-id xxxx --username xxx#xx.com --confirmation-code xxx
aws cognito-idp initiate-auth --auth-flow USER_PASSWORD_AUTH --client-id xx --auth-parameters USERNAME=xx,PASSWORD=xx
I'm able to initiate Step 1 and Step 2 via AWSCognitoIdentityProvider.signUp() & AWSCognitoIdentityProvider.confirmSignup(). However the Step 3 fails irrespective of valid creds.
Below is the code used:
public String initiateAuth(String username, String password) {
String idToken = null;
AWSCognitoIdentityProvider cognitoClient;
Map<String, String> authParams = new HashMap<String, String>();
authParams.put("USERNAME", username);
authParams.put("PASSWORD", password);
InitiateAuthRequest initiateAuthRequest = new InitiateAuthRequest();
initiateAuthRequest.setAuthFlow("USER_PASSWORD_AUTH");
initiateAuthRequest.setClientId(CLIENT_ID);
initiateAuthRequest.setAuthParameters(authParams);
/*
// I have tried enabling as below
initiateAuthRequest.setAuthFlow(AuthFlowType.USER_PASSWORD_AUTH);
initiateAuthRequest.addAuthParametersEntry("USERNAME", username);
initiateAuthRequest.addAuthParametersEntry("PASSWORD", password);
*/
try {
cognitoClient = getAmazonCognitoIdentityClient();
InitiateAuthResult result = cognitoClient.initiateAuth(initiateAuthRequest);
idToken = result.getAuthenticationResult().getIdToken();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
idToken = "false";
} finally {
System.out.println("AWS COgnito Status = " + idToken);
}
return idToken;
}
Try this way. Where identityProvider is IAmazonCognitoIdentityProvider and UserPool is CognitoUserPool
public string SignIn(string username, string password, CancellationToken token)
{
try
{
var cUser = new CognitoUser(username, CognitoUserPoolAppId, UserPool, identityProvider);
var authRequest = new InitiateSrpAuthRequest()
{
Password = password
};
var response = await cUser.StartWithSrpAuthAsync(authRequest);
return response.AuthenticationResult.IdToken;
}
catch(Exception exc)
{
return string.Empty;
}
}

How to google oauth to an api? My example is not working

I am trying to do this article for google cloud build
https://cloud.google.com/endpoints/docs/openapi/service-account-authentication
I am guessing to use the service account email I generated the key from in that example AND for Audient, I put "" (which is probably the reason it's not working?). I have no idea and can't find what in the world to put for audience.
In addition to code below, I tried setting audience to 'https://cloudbuild.googleapis.com' which also did not work
My code is the following...
public class GenToken {
public static void main(String[] args) throws IOException {
Duration d = Duration.ofDays(365);
String tok = generateJwt("/Users/dean/workspace/order/java/googleBuild/orderly-gcp-key.json",
"mycloudbuilder#order-gcp.iam.gserviceaccount.com", "", d.toSeconds());
System.out.println("tok="+tok);
URL url = new URL("https://cloudbuild.googleapis.com/v1/projects/order-gcp/builds");
makeJwtRequest(tok, "GET", url);
}
public static String generateJwt(final String saKeyfile, final String saEmail,
final String audience, final long expiryLength)
throws FileNotFoundException, IOException {
Date now = new Date();
Date expTime = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiryLength));
// Build the JWT payload
JWTCreator.Builder token = JWT.create()
.withIssuedAt(now)
// Expires after 'expiraryLength' seconds
.withExpiresAt(expTime)
// Must match 'issuer' in the security configuration in your
// swagger spec (e.g. service account email)
.withIssuer(saEmail)
// Must be either your Endpoints service name, or match the value
// specified as the 'x-google-audience' in the OpenAPI document
.withAudience(audience)
// Subject and email should match the service account's email
.withSubject(saEmail)
.withClaim("email", saEmail);
// Sign the JWT with a service account
FileInputStream stream = new FileInputStream(saKeyfile);
ServiceAccountCredentials cred = ServiceAccountCredentials.fromStream(stream);
RSAPrivateKey key = (RSAPrivateKey) cred.getPrivateKey();
Algorithm algorithm = Algorithm.RSA256(null, key);
return token.sign(algorithm);
}
/**
* Makes an authorized request to the endpoint.
*/
public static String makeJwtRequest(final String signedJwt, String method, final URL url)
throws IOException, ProtocolException {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod(method);
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Authorization", "Bearer " + signedJwt);
InputStreamReader reader = new InputStreamReader(con.getInputStream());
BufferedReader buffReader = new BufferedReader(reader);
String line;
StringBuilder result = new StringBuilder();
while ((line = buffReader.readLine()) != null) {
result.append(line);
}
buffReader.close();
return result.toString();
}
}
The orderly-gcp-key.json has these attributes in it
{
"type": "service_account",
"project_id": "myproj",
"private_key_id": "xxxxxxxx",
"private_key": "-----BEGIN PRIVATE KEY-----\nasdfsd\n-----END PRIVATE KEY-----\n",
"client_email": "build-ci-mine#myproj.iam.gserviceaccount.com",
"client_id": "1167333552",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/build-ci-mine%40myproj.iam.gserviceaccount.com"
}
oops, my edit didn't get posted :(. Here is the error
Exception in thread "main" java.io.IOException: Server returned HTTP response code: 401 for URL: https://cloudbuild.googleapis.com/v1/projects/orderly-gcp/builds
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1919)
at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1515)
at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:250)
at com.orderlyhealth.auth.websecure.GenToken.makeJwtRequest(GenToken.java:71)
at com.orderlyhealth.auth.websecure.GenToken.main(GenToken.java:26)
I hope that I better understood!!
When you try to reach a Google API, you have to use an access token. I have 2 code snippets for you.
Use Google Http client
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
HttpRequestFactory factory = new NetHttpTransport().createRequestFactory(new HttpCredentialsAdapter(credentials));
HttpRequest request = factory.buildGetRequest(new GenericUrl("https://cloudbuild.googleapis.com/v1/projects/gbl-imt-homerider-basguillaueb/builds"));
HttpResponse httpResponse = request.execute();
System.out.println(CharStreams.toString(new InputStreamReader(httpResponse.getContent(), Charsets.UTF_8)));
Use pure java connection
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
HttpURLConnection con = (HttpURLConnection) new URL("https://cloudbuild.googleapis.com/v1/projects/gbl-imt-homerider-basguillaueb/builds").openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Authorization", "Bearer " + credentials.refreshAccessToken().getTokenValue());
InputStreamReader reader = new InputStreamReader(con.getInputStream());
BufferedReader buffReader = new BufferedReader(reader);
String line;
StringBuilder result = new StringBuilder();
while ((line = buffReader.readLine()) != null) {
result.append(line);
}
buffReader.close();
System.out.println(result.toString());
You can rely on the platform environment. In local, perform a gcloud auth application-default login to set your credential as default default credential. On GCP, the component identity (the default service account or the service account that you define when you create the component), is used thanks to the method GoogleCredentials.getApplicationDefault();
Your dependency management need this (here in maven)
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>0.20.0</version>
</dependency>
Does this solve your issue?

AWS Cognito Email Verification

I am trying to utilize AWS Cognito User Pools in my cross platform Xamarin app. I correctly begin registration for the user in the user pool (The user shows up in the user pool and the email with the verification code is sent). I can't seem to figure out the correct way to verify the users email to confirm them in the user pool. I keep getting NotAuthorizedException.
--------EDIT: The code blocks below have been updated to my latest attempts--------
Code for registering user:
public async Task<Exception> RegisterUserInUserPool(String sUsername, String sPassword, String sEmail)
{
AmazonCognitoIdentityProviderClient oClient = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), Amazon.RegionEndpoint.USEast2);
CognitoUserPool oUserPool = new CognitoUserPool(sUserPoolID, sClientID, oClient);
try
{
await oUserPool.SignUpAsync(sUsername, sPassword, new Dictionary<string, string> { { "email", sEmail } }, null);
return null;
}
catch (Exception e)
{
return e;
}
}
My latest attempt at verifying the user:
public async Task<Exception> VerifyEmail(String sUsername, String sVerificationCode)
{
CognitoAWSCredentials oCreds = new CognitoAWSCredentials(sIdentityPoolID, Amazon.RegionEndpoint.USEast2);
AmazonCognitoIdentityProviderClient oClient = new AmazonCognitoIdentityProviderClient(oCreds, Amazon.RegionEndpoint.USEast2);
CognitoUserPool oUserPool = new CognitoUserPool(sUserPoolID, sClientID, oClient);
CognitoUser oCognitoUser = new CognitoUser(sUsername, sClientID, oUserPool, oClient);
try
{
await oCognitoUser.ConfirmSignUpAsync(sVerificationCode, false);
return null;
}
catch (Exception e)
{
return e;
}
}
EDIT: The updated code above for confirming user verification is returning a NotAuthorizedException exception that says "Unauthenticated access is not supported for this identity pool."
What are the correct settings for the user pool to allow this kind of confirmation? Is my code missing any steps?
Any help or clarification is appreciated!
CognitoIdentityServiceProvider SDK:
Use the confirmRegistration() or adminconfirmSignUp() functions.
Example Code
I am using the following code and it works well
AmazonCognitoIdentityProviderClient providerClient = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), AWSSettings.AWS_REGION);
ConfirmSignUpRequest confirmRequest = new ConfirmSignUpRequest()
{
Username = username,
ClientId = AWSSettings.AWS_CLIENT_ID, //use your own client id
ConfirmationCode = code
};
return await providerClient.ConfirmSignUpAsync(confirmRequest);
Also, the AWS Cognito client app should not have the secretId and ADMIN_NO_SRP_AUTH should not be marked.
Thanks to everyone who took the time to answer. A combination of things led me to working code. I want to post the code that works for me as well as a few tips that I could've used. Hopefully it can help someone!
Register the user in the user pool:
public async Task<Exception> RegisterUserInUserPool(String sUsername, String sPassword, String sEmail)
{
AmazonCognitoIdentityProviderClient oClient = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), Amazon.RegionEndpoint.USEast2);
CognitoUserPool oUserPool = new CognitoUserPool(sUserPoolID, sClientID, oClient);
try
{
await oUserPool.SignUpAsync(sUsername, sPassword, new Dictionary<string, string> { { "email", sEmail } }, null);
return null;
}
catch (Exception e)
{
return e;
}
}
Confirm the user's email:
public async Task<Exception> VerifyEmail(String sUsername, String sVerificationCode)
{
AmazonCognitoIdentityProviderClient oClient = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), Amazon.RegionEndpoint.USEast2);
CognitoUserPool oUserPool = new CognitoUserPool(sUserPoolID, sClientID, oClient);
CognitoUser oCognitoUser = new CognitoUser(sUsername, sClientID, oUserPool, oClient);
try
{
await oCognitoUser.ConfirmSignUpAsync(sVerificationCode, false);
return null;
}
catch (Exception e)
{
return e;
}
}
A few tips:
Know the difference between AWS user pools and identity pools.
Check the spam folder for the verification code. (seems simple, but this had me going for a while)
The .NET AWS docs are useful for some things. (A little lacking overall in my opinion)
The next step to authenticate users and allow them to access AWS resources is to call StartWithSrpAuthAsync on the CognitoUser model.
Keep in mind this is all utilizing the AWSSDK.Extensions.CognitoAuthentication Nuget package.
I just wanted to add extra info, as this is the first stackoverflow option from google, for anyone struggling with cognito email verification.
If you are registering a user but NOT getting email verification links, check that you have set up an email forwarder.
On the Cognito User Pool Page goto:
App Integration > Domain Name: Enter a domain prefix here to allow verification emails to be sent.
This is the code that I have used to register a user and send a confirmation link.
public async Task<SignUpResponse> SignupUserAsync(CognitoUser user)
{
var region = "eu-west-2";
var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(),
RegionEndpoint.GetBySystemName(region));
var signupRequest = new SignUpRequest
{
ClientId = _clientId,
Username = user.Email,
Password = user.Password
};
AttributeType emailAttribute = new AttributeType
{
Name = "email",
Value = user.Email
};
signupRequest.UserAttributes.Add(emailAttribute);
var newUser = provider.SignUpAsync(signupRequest);
return await newUser;
}
CognitoUser is a custom class that inherits from IdentityUser, found on a tutorial, I just copied it.
public class CognitoUser : IdentityUser
{
public string Password { get; set; }
public UserStatusType Status { get; set; }
}
Onto the next problem which I am sure isn't too far in the future. Aha
Hope it helps!

Simple DB policy being ignored?

I'm trying to use AWS IAM to generate temporary tokens for a mobile app. I'm using the AWS C# SDK.
Here's my code...
The token generating service
public string GetIAMKey(string deviceId)
{
//fetch IAM key...
var credentials = new BasicAWSCredentials("MyKey", "MyAccessId");
var sts = new AmazonSecurityTokenServiceClient(credentials);
var tokenRequest = new GetFederationTokenRequest();
tokenRequest.Name = deviceId;
tokenRequest.Policy = File.ReadAllText(HostingEnvironment.MapPath("~/policy.txt"));
tokenRequest.DurationSeconds = 129600;
var tokenResult = sts.GetFederationToken(tokenRequest);
var details = new IAMDetails { SessionToken = tokenResult.GetFederationTokenResult.Credentials.SessionToken, AccessKeyId = tokenResult.GetFederationTokenResult.Credentials.AccessKeyId, SecretAccessKey = tokenResult.GetFederationTokenResult.Credentials.SecretAccessKey, };
return JsonConvert.SerializeObject(details);
}
The client
var iamkey = Storage.LoadPersistent<IAMDetails>("iamkey");
var simpleDBClient = new AmazonSimpleDBClient(iamkey.AccessKeyId, iamkey.SecretAccessKey, iamkey.SessionToken);
try
{
var details = await simpleDBClient.SelectAsync(new SelectRequest { SelectExpression = "select * from mydomain" });
return null;
}
catch (Exception ex)
{
Storage.ClearPersistent("iamkey");
}
The policy file contents
{ "Statement":[{ "Effect":"Allow", "Action":"sdb:* ", "Resource":"arn:aws:sdb:eu-west-1:* :domain/mydomain*" } ]}
I keep getting the following error...
User (arn:aws:sts::myaccountid:federated-user/654321) does not have permission to perform (sdb:Select) on resource (arn:aws:sdb:us-east-1:myaccountid:domain/mydomain)
Notice that my policy file clearly specifies two things
region should be eu-west-1
allowed action is a wild-card, ie, allow everything
But the exception thrown claims that my user doesn't have permission to us-east-1
Any ideas as to why I'm getting this error?
Ok figured it out.
You have to set the region endpoint on your call to the service from the client.
So
var simpleDBClient = new AmazonSimpleDBClient(iamkey.AccessKeyId, iamkey.SecretAccessKey, iamkey.SessionToken, Amazon.RegionEndpoint.EUWest1);