Point of using the AWS Cognito Identity - ios

Is the point of using the following code so that I can access other AWS tools directly with my ios app?
AWSCognitoCredentialsProvider *credentialsProvider = [[DeveloperAuthenticationProvider alloc] initWithRegionType:AWSRegionUSEast1 identityPoolId:#"poolId"];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;
__block NSString *cognitoId = nil;
// Retrieve your Amazon Cognito ID
[[credentialsProvider getIdentityId] continueWithBlock:^id(AWSTask *task)
{
if (task.error)
{
NSLog(#"Error: %#", task.error);
}
else
{
// the task result will contain the identity id
cognitoId = task.result;
}
return nil;
}];
I then use AWS Lambda with an API gateway to get user identities.

Cognito is required in order to provide an execution context (authentication) when accessing an AWS resource. What that means, is that nothing is truly anonymous on AWS - even if you don't have your users "log in", they still have a unique identifier associated with their device.
What this means is that some random person outside of your app cannot simply hit your AWS resources (S3, Lambda, etc) and execute code.
This also means you can, and must, assign execution permissions to your Lambda to allow your Cognito group to execute.
Another thing to note: You do not need to use API gateway in order to execute Lambdas on iOS. You can invoke natively. I prefer doing it thusly - less configuration.
http://docs.aws.amazon.com/mobile/sdkforios/developerguide/lambda.html
Hope that answers your question.

Related

Download File from AWS S3 Access Denied

I am trying to use Amazon web service to store files for my iOS App.
Here's the code I am using to download the file stored on AWS S3
I added the following to the appDelegate
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc]
initWithRegionType:AWSRegionUSEast2
identityPoolId:#"us-east-2:3764e0f9-khu97-4844-b9f7-57defdfjv8b8b"];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast2 credentialsProvider:credentialsProvider];
[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
Then in my class I used the following to download the object. Kindly note that no encryption is applied to the object and all permissions are unblocked for the bucket
- (void)downloadImageToAWS{
// AWS Configurations
AWSS3DownloadHelper *aws = [[AWSS3DownloadHelper alloc] init];
aws.bucket = #"my-sample-bucket-002";
aws.key = #"photo-sam-002.jpg";
// AWS progress block
aws.progressBlock = ^(AWSS3TransferUtilityTask *task, NSProgress *progress) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"progress.fractionCompleted %f", progress.fractionCompleted);
});
};
// AWS completionHandler
[self addAWSDownloadComplitionHandler:aws];
// Update UI if job / task can upload a file on AWS S3
[self successfulDownloadOfAWSS3ByCompletionHandler:aws.completionHandler withProgressBlock:aws.progressBlock];
[aws downloadAWSFile];
}
- (void) addAWSDownloadComplitionHandler:(AWSS3DownloadHelper *)aws {
// Create instance to View Controller
NSLog(#"addAWSDownloadComplitionHandler");
aws.completionHandler = ^(AWSS3TransferUtilityDownloadTask *task, NSURL *location, NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Unsuccessfully downloaded error %li", (long)[error code]);
NSLog(#"Unsuccessfully downloaded error %#", [error description]);
}
else if (data) {
NSLog(#"data %#", data);
}
});
};
}
- (void) successfulDownloadOfAWSS3ByCompletionHandler:(AWSS3TransferUtilityDownloadCompletionHandlerBlock)completionHandler withProgressBlock:(AWSS3TransferUtilityProgressBlock)progressBlock {
NSLog(#"successfulDownloadOfAWSS3ByCompletionHandler");
AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
[transferUtility enumerateToAssignBlocksForUploadTask:nil downloadTask:^(AWSS3TransferUtilityDownloadTask * _Nonnull downloadTask, AWSS3TransferUtilityProgressBlock _Nullable __autoreleasing * _Nullable downloadProgressBlockReference, AWSS3TransferUtilityDownloadCompletionHandlerBlock _Nullable __autoreleasing * _Nullable completionHandlerReference) {
NSLog(#"taskIdentifier %lu", (unsigned long)downloadTask.taskIdentifier);
*downloadProgressBlockReference = progressBlock;
*completionHandlerReference = completionHandler;
dispatch_async(dispatch_get_main_queue(), ^{
});
}];
}
Although I have put all permissions to allow. Block all public access is turned off, I keep getting the following error:
Domain=com.amazonaws.AWSS3TransferUtilityErrorDomain Code=2 "(null)" UserInfo={Server=AmazonS3, Error={
Code = AccessDenied;
HostId = "SJABFLSKBtprmLRaHLjjockzjfubejlakhipjaKDNSAFJLB4ViE=";//Changed
Message = "Access Denied";
RequestId = jabduw2dhC6WCT;//changed
}, Transfer-Encoding=Identity, Content-Type=application/xml, Date=Wed, 29 Dec 2021 09:33:06 GMT, x-amz-request-id=2TJSHCJ2ASTC6WCT, x-amz-id-2=Fbg2cDXwU5wLgQLHbGtprmLRaHLjjocvZzCcUNfMrSpT5Oiwl3LjEkpPQ2OBzLmBrKXnrwq4ViE=}
I have applied all suggestions in this video:
Why am I getting an Access Denied error from the Amazon S3 console while I modify a bucket policy? - YouTube
Trying the following command on the cloud shell didn't show any error:
aws s3 cp s3://<bucket name>/<key> /tmp/localfile
Firstly, let's rule out coding. Try to access the file natively using the AWS CLI. Just a straight up aws s3 cp s3://<bucket name>/<key> /tmp/localfile
If this works, you've got a code issue, and we continue checking. If it does not work, you may have a permissions issue.
The 2nd thing to check - are you using encryption? Chances are (from my experience) that while you may have permissions to access the S3 bucket, you may not have permissions to access the encryption key, so the access denied you're getting is actually coming from KMS. You may need to allow the right to decrypt the KMS key.
It seems you are using Cognito as the credentials provider. As I understand it, even for public buckets, if you access through API, you need to setup proper IAM roles and permissions. That means assigning the IAM roles with S3 access to the authenticated identities inside the identity pool in Cognito.
Or, you have to pass the no-sign-request. At least in CLI you have to do so. https://docs.aws.amazon.com/cli/latest/reference/.
But if you access using URL, with public buckets, you will be able to get the objects.
In cognito Identity pool you can specify two IAM Roles (even more, but lets focus on default two):
Unauthenticated role - the role assigned to user which is not logged in to Identity Provider
Authenticated role - the role assigned to authenticated user
Default roles created by Cognito don't provide access to S3 buckets - you have to modify policy and add missing permissions (i.e. s3:PutObject, s3:GetObject, s3:ListBucket)
If your bucket is encrypted with KMS key, your role policy or key polisy must allow kms actions (i.e. kms.Encrypt, kms:Decrypt, kms:generateKeyData)
Try to invoke sts getCallerIdentity action in your app and check returned identity, it should return Authenticated role ARN.
If so, add missing permissions to access S3 bucket and KMS key to returned role policy.

iOS Facebook sign in with AWS Federated Identities issues

I am using AWS Cognito Federated ID with my iOS app for social providers (Facebook, Google). After sign in with Facebook, user is getting error 'Token is not from a supported provider of this identity pool'. If I allow unauthenticated ids in Fed IDs, then that user gets unauthenticated user ids (linked logins = 0) in Identity browser and gets session key etc.
Can anyone help on resolving this issue?
Please don't mark this question as duplicated as I have seen all the similar questions and followed the solutions there, but to no avail!
Brief about code:
I have added Facebook in Authenticated Providers for Federated ID.
I have also added Facebook in Identity Providers for User Pool and done attributes mapping (mapped Id and Email).
I have created a social ID provider class which implements AWSIdentityProviderManager protocol. This class contains logins method as given:
(AWSTask<NSDictionary<NSString *, NSString *> *> )logins {
FBSDKAccessToken fbToken = [FBSDKAccessToken currentAccessToken];
if(fbToken){
NSString *token = fbToken.tokenString;
return [AWSTask taskWithResult: #{ AWSIdentityProviderFacebook : token }];
} else {
NSLog(#"FB Token is not found in AWSSocialIdentityProvider class");
return [AWSTask taskWithError:[NSError errorWithDomain:#"facebook"
code:-1
userInfo:#{#"error":#"No current Facebook access token"}]];
}
}
My Sign In View Controller does FB sign in. It creates fbCredentialsProvider by passing the socialIDProvider class as an identityProvider in it's construction, implemented as below:
self.fbCredentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1 identityPoolId:#"us-east-1:XXXXXXXXXXXX" identityProviderManager:self.socialIDProvider];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:self.fbCredentialsProvider];
[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
//wipe cached credentials
[self.fbCredentialsProvider clearCredentials];
//For calling fb logins
[self.fbCredentialsProvider credentials];
The above code is called in didCompleteWithResult method of FB Login Button.
That's it.

AWSAPIGatewayClient always results in unauthorised

When using the amazon generated code for AWSAPIGatewayClient I always get
message = Unauthorized;
as a response.
What could be the cause of this?
AppDelegate
AWSCognitoIdentityUserPool *pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:#"UserPool"];
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
identityPoolId:CognitoPoolId
identityProviderManager:pool];
AWSServiceConfiguration *serviceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:CognitoIdentityUserPoolRegion
credentialsProvider:credentialsProvider];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = serviceConfiguration;
AWSCognitoIdentityUserPoolConfiguration *configuration = [[AWSCognitoIdentityUserPoolConfiguration alloc] initWithClientId:CognitoIdentityUserPoolAppClientId
clientSecret:CognitoIdentityUserPoolAppClientSecret
poolId:CognitoIdentityUserPoolId];
[AWSCognitoIdentityUserPool registerCognitoIdentityUserPoolWithConfiguration:serviceConfiguration
userPoolConfiguration:configuration
forKey:#"UserPool"];
ViewController
[[[AWSPrjctRtClient defaultClient] suggestionsGet] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) {
NSLog(#"%#", task.error);
return nil;
}];
Results in
2017-04-07 16:02:24.386 xxxx[38051:1025018] Error Domain=com.amazonaws.AWSAPIGatewayErrorDomain Code=1 "(null)" UserInfo={HTTPBody={
message = Unauthorized;
The response looks like your API Gateway resource is configured to use Cognito user pools for authorization, but your code actually uses Cognito Federated Identities. In turn, Federated Identities requires API Gateway to use AWS_IAM authorizers, using IAM roles to manage access to your resources.
I would suggest you go through the following steps:
Follow this guide. Basically, within Cognito Federated Identities, configure an Identity Pool to use your User Pool as (one of) its Authentication provider(s). (You may have already done this)
Check the Authorization of your API Gateway resource(s) under Method Requests/Settings/Authorization. Set it to AWS_IAM. Don't forget to redeploy the newly configured API, and export a new SDK.
Your Identity Pool will require two IAM roles, for both unauthenticated and authenticated access to AWS services. You will have to add a policy to your role(s) to specify access to your AWS services, in this case you will need to grant "execute-api:Invoke" access to (presumably only) your authenticated role. I recommend using the policy generator for this, and make sure you get set the ARN for the policy to be only for the resource(s) you want to grant access to, otherwise all of your API Gateway resources may be accessed.
As for configuration on the iOS SDK side, make sure you use the code from the guide (shown below), it seems yours is slightly different. I have found that getting this wrong can induce a whole range of confusing errors that could have you look in all sorts of wrong directions for a solution.
Add to AppDelegate
AWSServiceConfiguration *serviceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1
credentialsProvider:nil];
AWSCognitoIdentityUserPoolConfiguration *userPoolConfiguration = [[AWSCognitoIdentityUserPoolConfiguration alloc] initWithClientId:#"YOUR_CLIENT_ID"
clientSecret:#"YOUR_CLIENT_SECRET"
poolId:#"YOUR_USER_POOL_ID"];
[AWSCognitoIdentityUserPool registerCognitoIdentityUserPoolWithConfiguration:serviceConfiguration
userPoolConfiguration:userPoolConfiguration forKey:#"UserPool"];
AWSCognitoIdentityUserPool *pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:#"UserPool"];
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc]
initWithRegionType:AWSRegionUSEast1
identityPoolId:#"YOUR_IDENTITY_POOL_ID"
identityProviderManager:pool];
One important addition though! I've found this particularly confusing at first, but in the above code you initialize an AWSServiceConfiguration with credentialsProvider set to nil in order to register your AWSCognitoIdentityUserPool. However, you will need to initialize a new AWSServiceConfiguration that references your credentialsProvider to assign to your AWSServiceManager.defaultServiceManager.defaultServiceConfiguration. Like so:
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:CognitoUserPoolRegion
credentialsProvider:credentialsProvider];
The above described steps ultimately led me to successfully integrating Cognito User Pools with Federated Identities to allow access to API Gateway resources. The process involved some confusion about what services do what exactly, and piecing together pieces of code from different guides. I hope this helps!
Note that you can also probably do without Federated Identities and leave your API to be authorized using the User Pool directly. But I haven't been successful in that approach. Also, Federated Identities will allow you to add other authorizers at a later stage if you please to do so.

Calling AWS API Gateway using AWS Cognito

We use AWS V4 signature mechanism to make calls to our API Gateway endpoint from our iOS app. We had embedded the access key id and secret key in the code, which was working fine. Obviously this is not a secure way and the recommended way is to use AWS Cognito.
I wanted to know how do we use the temporary access key and secret (and session key probably as well) that we get from the AWSCredentials object in my Objective-C iOS code to make secure requests to our API Gateway endpoint.
We tried to use the temporary access key and secret retrieved from Cognito to generate the V4 signature in place of account access key and secret, but this does not seem like the correct approach. The API Gateway method is enabled with AWS_IAM as the authorization setting.
This is the error we get:
{ status code: 403, headers {
Connection = "keep-alive";
"Content-Length" = 69;
"Content-Type" = "application/json";
Date = "Fri, 13 Jan 2017 10:26:38 GMT";
Via = "1.1 .....cloudfront.net (CloudFront)";
"X-Amz-Cf-Id" = "...";
"X-Cache" = "Error from cloudfront";
"x-amzn-ErrorType" = UnrecognizedClientException;
"x-amzn-RequestId" = "...";
} }
The IdentityPoolId used is from the Identity Pool created under federated identities in AWS Cognito
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1 identityPoolId:#"us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"];
We are using unauthenticated role since we do not need any form of user specific authentication. And this role has the following policies:
AmazonAPIGatewayInvokeFullAccess
AmazonAPIGatewayPushToCloudWatchLogs
CloudFrontFullAccess
AmazonCognitoDeveloperAuthenticatedIdentities
AmazonAPIGatewayAdministrator
CloudFrontReadOnlyAccess
IAMReadOnlyAccess
AmazonCognitoPowerUser
Can you please help here as to how can I use Cognito to generate V4 signatures or completely bypass the process.
It looks you are getting UnrecognizedClientException as response, but API Gateway doesn't return UnrecognizedClientException. Do you have the request id that was getting the error?
Just in case you might forget to register the configuration, you need to register the configuration to service manager.
AWSCognitoCredentialsProvider *creds = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1 identityPoolId:your_cognito_pool_id];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:creds];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;
For your unauthenticated role policy, I think you are granting too powerful permission to the unauthenticated users. if you want them to be able to invoke your API, you can just give them AmazonAPIGatewayInvokeFullAccess or even scope down to method level.
Sending an HTTP header "x-amz-security-token" with the value of variable sessionKey obtained from the AWSCredentials object solved the problem:
[request setValue:sessionToken forHTTPHeaderField:#"X-Amz-Security-Token"];
The AWSCredentials object is retrieved using:
[[credentialsProvider credentials] continueWithBlock:^id(AWSTask<AWSCredentials *> *task) {
if (task.error) {
DDLogCError(#"failed getting credentials: %#", task.error);
}
else {
AWSCredentials *credentials = task.result;
}
return nil;
}]
And yes, have trimmed the policies to just one - AmazonAPIGatewayInvokeFullAccess.
Thanks you for your feedback.

How do I use an iOS app's bundle identifier to 'authorize' upload to Google Cloud Storage?

Our service is using Google App Engine as our backend, and we're now implementing an upload-function for images etc.
Using the answers from several different questions here on stack, I have made it working, but not completely as I want. We are not using the built-in OAuth etc, and for now we want the storage to be public, but not entirely public. We would like to limit it to users of our own app (I.E no authentication). In the Cloud-console we can create an API-key for iOS. When doing this, we copy the API-key to the app, and pass it along with every upload-request. This is currently working, when the bucket-permission is set to allUsers - WRITE
However, inside the API-key, we can supply our app's own Bundle Identifier, so that, supposedly, only requests from our app is allowed. (App Store ID/URL is also permitted, apparently).
Adding this bundle-id does nothing as long as the bucket has the permission allUsers - WRITE. If I change the bundle-id to not match the actual bundle-id, it still works. So which permission should it use for the bucket to make the bundle-id in the API-key apply? And what should be sent along in the upload-code on iOS (acl?)?.
If I remove the allUsers-permission, and use something else, I get this error when trying to upload:
{message:"There is a per-IP or per-Referer restriction configured
on your API key and the request does not match these
restrictions. Please use the Google Developers Console
to update your API key configuration if request from this
IP or referer should be allowed." data:[1] code:403}}
This is how I'm using it right now (though I have tried several different things, all picked up from different questions/answers):
GTLServiceStorage *serv = [[GTLServiceStorage alloc] init];
serv.additionalHTTPHeaders = [NSDictionary dictionaryWithObjectsAndKeys:
#"[my project id]", #"x-goog-project-id",
#"application/json-rpc", #"Content-Type",
#"application/json-rpc", #"Accept", nil];
serv.APIKey = #"[my iOS API key, gotten from console, (linked to bundle-id?)]";
serv.retryEnabled = YES;
GTLStorageBucket *bucket = [[GTLStorageBucket alloc] init];
bucket.name = #"[my bucket]";
GTLUploadParameters *params = [GTLUploadParameters uploadParametersWithFileHandle:fileHandle MIMEType:#"image/jpeg"];
GTLStorageObject *storageObject = [[GTLStorageObject alloc] init];
storageObject.name = #"testFile.jpg";
//I have no idea what I'm doing with the following stuff, but I've tried several things:
GTLStorageObjectAccessControl *objAccessControl
= [GTLStorageObjectAccessControl new];
//This is working
objAccessControl.entity = #"allUsers";
objAccessControl.email = #"[my app-id]#project.gserviceaccount.com";
objAccessControl.role = #"OWNER";
//If I try this instead, it is not working.
//objAccessControl.domain = #"[my app-id].apps.googleusercontent.com";
//objAccessControl.role = #"WRITER";
//Probably because it's bullshit, I have no idea what I'm doing.
storageObject.acl = #[objAccessControl];
[...] //Bucket and upload and stuff. It seems like it's the ACL-thing above that's not working..
It seems like I have to connect the permissions on the bucket to the iOS API Key somehow, but I don't know if it's even possible.
What I want: All users to be able to use the cloud, given that they are requesting it from my iOS app.
As this question never got an answer I'll add one here, based on the information currently in the post.
The reason you got the error 'There is a per-IP or per-Referer restriction..' when calling the GCS API with the iOS API Key is simply because the GCS API doesn't work with API Keys for private data, only Bearer Tokens (ie. using OAuth). There isn't anything you could have done to make the API Key work with the GCS API directly with private data. The reason it worked when you had 'allUsers - WRITE' set as the ACL is simply because that ACL allows public access.
To access the private data without user intervention requires a Service Account, however the Google APIs Objective-C Client only supports OAuth2 Client IDs. The rationale being that Service Accounts are intended for server-side authentication only. Using a Service Account in a client would involve distributing the private key along with the app, which could easily be compromised. For reference, here's a sample of how you might authorize the GCS service using OAuth:
NSString *keychainItemName = #"My App";
NSString *clientID = <your-client-id>;
NSString *clientSecret = <your-client-secret>;
// How to check for existing credentials in the keychain
GTMOAuth2Authentication *auth;
auth = [GTMOAuth2WindowController authForGoogleFromKeychainForName:kKeychainItemName
clientID:clientID
clientSecret:clientSecret];
...
// How to set up a window controller for sign-in
NSBundle *frameworkBundle = [NSBundle bundleForClass:[GTMOAuth2WindowController class]];
GTMOAuth2WindowController *windowController;
windowController = [GTMOAuth2WindowController controllerWithScope:kGTLAuthScopeStorageDevstorageFullControl
clientID:clientID
clientSecret:clientSecret
keychainItemName:kKeychainItemName
resourceBundle:frameworkBundle];
[windowController signInSheetModalForWindow:[self window]
completionHandler:^(GTMOAuth2Authentication *auth,
NSError *error) {
if (error == nil) {
self.storageService.authorizer = auth;
}
}];
...
// Initialize service with auth
GTLServiceStorage *serv = [[GTLServiceStorage alloc] init];
serv.authorizer = auth;
All of this was taken from the storage sample on the google-api-objectivec-client GitHub page, so you can refer to it for a complete example with context.
This still leaves the question of how to implement access to GCS from iOS without user authorization. The short answer to this is that the iOS Key can be used to restrict access to your own backend API hosted on Google Cloud Endpoints, and that backend application can authorize against GCS using a Service Account (usually the Application Default Service Account). The Cloud Storage Client Library Examples page has samples using the default credentials for different languages.
Further details on how to implement an Endpoints API for this purpose are probably getting outside of the scope of this question, but this should serve as a good starting point.

Resources