Amazon Cognito with iOS: Access to Identity Forbidden - ios

Thanks for the help in advance!
I am having some trouble getting Amazon Cognito to store/synchronize data properly.
On the dataset.synchronize() line (which does not store the data in Cognito), I get a large output error (with ID starred out) such as:
AWSCredentialsProvider.m line:429 | __73-[AWSCognitoCredentialsProvider
getCredentialsWithCognito:authenticated:]_block_invoke | GetCredentialsForIdentity
failed. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain
Code=10 "(null)" UserInfo={__type=NotAuthorizedException, message=Access to
Identity '*****' is forbidden.}]
The cognitoID is not nil, and returns properly (and matches the values I can read online)
For instance, after authenticating with Facebook, I perform the following:
if (FBSDKAccessToken.currentAccessToken() != nil)
{
let fbCognitoToken = FBSDKAccessToken.currentAccessToken().tokenString
credentialsProvider.logins = [AWSCognitoLoginProviderKey.Facebook.rawValue: fbCognitoToken]
// Retrieve your Amazon Cognito ID
credentialsProvider.getIdentityId().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("Error: " + task.error!.localizedDescription)
}
else {
// the task result will contain the identity id
let cognitoId = task.result
//checking if cognito was successful, if true, sets success condition to true to prepare for segue into app
if cognitoId != nil{
print (cognitoId)
cognitoSuccess = true
let syncClient = AWSCognito.defaultCognito()
let dataset = syncClient.openOrCreateDataset("User_Data")
dataset.setString("test#test.com", forKey:"Email")
// credentialsProvider.refresh()
dataset.synchronize()
} }return nil}}
I can read data from Facebook correctly, and all authentication occurred correctly from what I can tell. I suspect there is something simple that is at the root here, but after spending several days, I cannot figure it out! Using the IAM checker in the AWS portal returns all "green checks" for Cognito functions, so I am sure this not a permissions issue on the server-side, either.
Thanks again for any insight you might have!
Edit:
Before the chunk of code above, I call:
let credentialsProvider = self.initializeCognito()
which runs (identity pool ID starred out):
func initializeCognito () -> AWSCognitoCredentialsProvider
{
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: AWSRegionType.USEast1, identityPoolId: "******")
let defaultServiceConfiguration = AWSServiceConfiguration(
region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
return credentialsProvider
}

That exception can be thrown when you're trying to get credentials for an authenticated id without giving any provider token linked to it. Cognito requires at least one to be given.
Can you check that you're including the facebook token during the GetCredentialsForIdentity call that's failing? If not, I'd guess that's your issue.
Edit:
Since you are using AWSCognito.defaultCognito(), it might help to follow the example on this docs page to make sure the sync client uses the right credentials provider:
let configuration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration

Ended up figuring out the answer-- when I first set up AWS and was following some of Amazon's guides, I had placed code to create a new credentialsProvider in the application's App Delegate. I forgot about it, and then was trying later on to initialize another credentialsProvider. This confusion created the issues, and removing the initialization in App Delegate fixed the authentication problems.

Related

Process for uploading image to s3 with AWS Appsync || iOS image uploading with Appsync

I'm working on a new project that requires uploading attachments in the form of images. I'm using DynamoDB and AppSync API's to insert and retrieve data from database. As we are new to the AppSync and all the amazon services and database we are using for the app i'm little bit confused about the authentication process. Right now we are using API key for authentication and I have tried these steps to upload image to s3.
1 Configue the AWSServiceManager with static configuration like :-
let staticCredit = AWSStaticCredentialsProvider(accessKey: kAppSyncAccessKey, secretKey: kAppSyncSecretKey)
let AppSyncRegion: AWSRegionType = .USEast2
let config = AWSServiceConfiguration(region: AppSyncRegion, credentialsProvider: staticCredit)
AWSServiceManager.default().defaultServiceConfiguration = config
2 Uploading picture with this method : -
func updatePictureToServer(url:URL, completion:#escaping (Bool)->Void){
let transferManager = AWSS3TransferManager.default()
let uploadingFileURL = url
let uploadRequest = AWSS3TransferManagerUploadRequest()
let userBucket = String(format: "BUCKET")
uploadRequest?.bucket = userBucket
let fileName = String(format: "%#%#", AppSettings.getUserId(),".jpg")
uploadRequest?.key = fileName
uploadRequest?.body = uploadingFileURL
transferManager.upload(uploadRequest!).continueWith(executor: AWSExecutor.mainThread(), block: { (task:AWSTask<AnyObject>) -> Any? in
if let error = task.error as NSError? {
if error.domain == AWSS3TransferManagerErrorDomain, let code = AWSS3TransferManagerErrorType(rawValue: error.code) {
switch code {
case .cancelled, .paused:
break
default:
print("Error uploading: \(String(describing: uploadRequest!.key)) Error: \(error)")
}
} else {
print("Error uploading: \(String(describing: uploadRequest!.key)) Error: \(error)")
}
completion(false)
return nil
}
_ = task.result
completion(true)
print("Upload complete for: \(String(describing: uploadRequest!.key))")
return nil
})
}
3 And finally i'm able to see the uploaded image on the S3 bucket
But i'm concerned about how to save the url of the image and how to retrieve the image because when i have to make the buket PUBLIC to retrieve the image and i don't think that's a good approach, plus is it necessary to have a Cognito user pool because we aren't using Cognito user pool yet in our app and not have much knowledge about that too and documents are not helping in practical situations because we are implementing ti for the first time so we need some little help.
So two question : -
Proper procedure to use for uploading and retrieving images for S3 and AppSync.
Is it necessary to use Cognito user pool for image uploading and retrieving.
Thanks
Note: Any suggestion or improvement or anything related to the AppSync, S3 or DynamoDB will be truly appreciated and language is not a barrier just looking for directions so swift or objective-c no problem.
You need per-identity security on the bucket using Cognito Federated Identities which gives each user their own secure bucket. You can leverage the AWS Amplify to set this up for your project with $amplify add auth and selecting the default config, then $amplify add storage which configures that bucket and pool with appropriate permissions to use private uploads.
For more info checkout the repo: https://github.com/aws-amplify/amplify-cli

AWS Cognito iOS. Unauthenticated error after logout

I'm using AWS Cognito, integrated with iOS app through MobileHub to login via Facebook.
Everything works fine until I logout of existing identity and try to login with another FB account (or even with the same).
In this case I get this error, every time I call any AWS Lambda:
AWSiOSSDK v2.4.9 [Error] AWSCredentialsProvider.m line:577.
[AWSCognitoCredentialsProvider credentials].
Unable to refresh. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=8 "(null)" UserInfo={__type=NotAuthorizedException, message=Unauthenticated access is not supported for this identity pool.}]
But if I terminate and restart the app, everything works correct again.
This error originates here:
From reading AWS SDK code I see, that it happens because cognito identity doesn't get logins from credential provider here:
I guess this is expected, since I've logged out. But the problem is that even after logging in to FB AWS still considers me Unauthorized. From similar questions on StackOverflow I see, that in past people set logins dictionary on AWSCognitoCredentialsProvider manually. But now this property is deprecated and other similar properties are readonly.
Here is my AWS setup in AppDelegate didFinishLaunchingWithOptions method:
let cred = AWSCognitoCredentialsProvider(regionType: .euWest1, identityPoolId: "POOLID")
let config = AWSServiceConfiguration(region: .euWest1, credentialsProvider: cred)
AWSServiceManager.default().defaultServiceConfiguration = config
if let config = config {
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 30
config.maxRetryCount = 3
AWSLambdaInvoker.register(with: config, forKey: "key")
}
let mapperConfiguration = AWSDynamoDBObjectMapperConfiguration()
mapperConfiguration.saveBehavior = .updateSkipNullAttributes
AWSDynamoDBObjectMapper.register(with: config!, objectMapperConfiguration: mapperConfiguration, forKey: "updateObjectMapper")
And here is logout code:
AWSIdentityManager.defaultIdentityManager().logout { (result, error) in
if let cp = AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider as? AWSCognitoCredentialsProvider {
cp.clearKeychain()
}
// Open login screen
}
Please note, that I tried both clearing keychain and not doing it. The result is the same.
I'd really appreciate any help!
Best regards,
Alex
Are you doing this in the implementation of the logins method?
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
FBSDKAccessToken* fbToken = [FBSDKAccessToken currentAccessToken];
if(fbToken){
NSString *token = fbToken.tokenString;
return [AWSTask taskWithResult: #{ AWSIdentityProviderFacebook : token }];
}else{
return [AWSTask taskWithError:[NSError errorWithDomain:#"Facebook Login"
code:-1
userInfo:#{#"error":#"No current Facebook access token"}]];
}
}
More details.

Search users Amazon Cognito with ListUsers API or iOS SDK

I am creating an iOS application in Swift and I cannot find a way to search or get a list of Cognito Users with API. Per Amazon Documentation, it says to use the ListUsers API; however, it does not provide an endpoint to make the request and it is rather difficult to authenticate REST API requests with AWS so is there anyway to do this via iOS SDK? These are the parameters I would like to include in my Request.
[
"AttributesToGet": ["username" ],
"Filter": "username ^= \"micheal\"",
"Limit": 10,
"UserPoolId": "\(AWSCognitoUserPoolId)"
]
You have to search through the Mobile SDK.
For iOS
// Make a AWSCognitoListUsers Request
let getUsersRequest = AWSCognitoIdentityProviderListUsersRequest()
// Add the Parameters
//getUsersRequest?.attributesToGet = ["username"]
getUsersRequest?.userPoolId = AWSCognitoUserPoolId
// Make the Request
AWSCognitoIdentityProvider(forKey: AWSCognitoUserPoolId).listUsers(getUsersRequest!, completionHandler: { (response, error) in
// The response variable contains the Cognito Response
})
There is a a restful API endpoint to do it, because using the aws CLI you can do it. Command Line List User Pool
the aws CLI is a good way to check out if the interface can do it.
How to do it in the IOS SDK is another matter. There is little relationship (none) between the restful command set and the SDK method names. I think it is something you ask the CognitoIdentityProvider to do... I think it is listUsers or thereabouts, and I think you can list the pools as well.
+1 the other answer to this
Here is working code to fetch user lists fro Cognito.
//IAM User Key and Secret Key Required
let IAMUserKey = “****”
let IAMSecretKey = “****”
//Set Credentials
let credentialsProvider : AWSStaticCredentialsProvider = AWSStaticCredentialsProvider(accessKey: IAMUserKey, secretKey: IAMSecretKey)
let serviceConfiguration: AWSServiceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
//Register Identity Provider
AWSCognitoIdentityProvider.register(with: serviceConfiguration, forKey:UserPoolID)
//Request
let getUsersRequest = AWSCognitoIdentityProviderListUsersRequest()
getUsersRequest?.userPoolId = UserPoolID
//mentioned name of attributes in array - name, given_name, username, email and so on
getUsersRequest?.attributesToGet = ["name"]
//Add Value in filter you want to search
getUsersRequest?.filter = "name = \”jen\””
//It will search string in including string
//getUsersRequest?.filter = "name ^= \"jen\""
AWSCognitoIdentityProvider(forKey: UserPoolID).listUsers(getUsersRequest!,completionHandler: {(response, error) in
print("response:\(String(describing: response))")
print("error:\(String(describing: error))")
})

Using DynamoDB With Cognito: Token is not from a supported provider of this identity pool

I am in the process of implementing registration and login for my iOS app, using this project as an example:
https://github.com/awslabs/aws-sdk-ios-samples/tree/75ada5b6283b7c04c1214b2e1e0a6394377e3f2b/CognitoYourUserPools-Sample/Objective-C/CognitoYourUserPoolsSample
Previously, my app was able to access DynamoDB resources by using a credentials provider set up in my AppDelegate's didFinishLaunchingWithOptions method. However, after changing my project to include logging in and function like the example, I see the error:
"__type":"NotAuthorizedException","message":"Token is not from a supported provider of this identity pool."
The code setting the credentialsProviderin AppDelegate currently looks like this:
let serviceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId:APP_CLIENT_ID, clientSecret: APP_CLIENT_SECRET, poolId: USER_POOL_ID)
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: USER_POOL_NAME)
let pool = AWSCognitoIdentityUserPool(forKey:USER_POOL_NAME)
pool.delegate = self
self.storyboard = UIStoryboard(name: "Main", bundle: nil)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: IDENTITY_POOL_ID, identityProviderManager:pool)
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:credentialsProvider)
I also cannot access any DynamoDB data through my app.
Based on the console output, the registration process seems to work correctly, although I'm unsure about the sign-in process. It occurred to me that I had changed the region from EU-West-1, where the DynamoDB resources were stored, to US-East-1. In order to account for this change, I repeated the same steps I had intially taken to allow my app to access DynamoDB:
I created Auth and Unauth roles, both with access to the same actions as the role which had previously worked, but for the EU-West-1 resources instead.
I set these roles to the user pool I created when setting up registration under "unauthenticated role" and "authenticated role".
In case it makes a difference, I should note that I did not use the exact same sign-in process outlined in the example project I linked. Instead, I used the explicit sign in process, like so:
let name = usernameField.text!
let user = pool!.getUser(name)
lock()
user.getSession(name, password: passwordField.text!, validationData: nil, scopes: nil).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: {
(task:AWSTask!) -> AnyObject! in
if task.error != nil {
self.sendErrorPopup("ERROR: Unable to sign in. Error description: " + task.error!.description)
} else {
print("Successful Login")
dispatch_async(dispatch_get_main_queue()){
self.performSegueWithIdentifier("mainViewControllerSegue", sender: self)
}
}
self.unlock()
return nil
})
The methods lock(), unlock(), and sendErrorPopup() are strictly UI-related methods that I made so that the beginning and end of the sign-in process would be more visually clear. The console output always reads "successful login", but I am wondering if this code actually signs the user in correctly, since the error message makes it sound like the user might not be properly authorized.
It occurred to me that the US-West tables might not have been set up correctly, but I experience the same problem even when trying to create new tables, so I don't think that's the issue. Are there steps I might have missed as far as giving the user access to DynamoDB? Has the process changed with AWS Cognito's new beta user pool system?
EDIT 2:
I fixed the previous issue, and for a while, my app was working fine. However, it has suddenly stopped loading DynamoDB data when I sign in, and shows the error message: invalid login token. Can't pass in a Cognito token. Currently, my AppData code looks like this:
let serviceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId:APP_CLIENT_ID, clientSecret: APP_CLIENT_SECRET, poolId: USER_POOL_ID)
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: USER_POOL_NAME)
let pool = AWSCognitoIdentityUserPool(forKey:USER_POOL_NAME)
pool.delegate = self
self.storyboard = UIStoryboard(name: "Main", bundle: nil)
self.credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: IDENTITY_POOL_ID, identityProviderManager:pool)
let manager = IdentityProviderManager(tokens: [NSString:NSString]())
self.credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: IDENTITY_POOL_ID, identityProviderManager: manager)
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:credentialsProvider!)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
...and my sign-in code looks like this:
if locked { return }
trimRegistrationValues()
let name = usernameField.text!
let user = pool!.getUser(name)
lock()
user.getSession(name, password: passwordField.text!, validationData: nil, scopes: nil).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: {
(task:AWSTask!) -> AnyObject! in
if task.error != nil {
self.sendErrorPopup("ERROR: Unable to sign in. Error description: " + task.error!.description)
} else {
print("Successful Login")
let loginKey = "cognito-idp.us-east-1.amazonaws.com/" + USER_POOL_ID
var logins = [NSString : NSString]()
self.credentialsProvider!.identityProvider.logins().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("ERROR: Unable to get logins. Description: " + task.error!.description)
} else {
if task.result != nil{
let prevLogins = task.result as! [NSString:NSString]
print("Previous logins: " + String(prevLogins))
logins = prevLogins
}
logins[loginKey] = name
let manager = IdentityProviderManager(tokens: logins)
self.credentialsProvider!.setIdentityProviderManagerOnce(manager)
self.credentialsProvider!.getIdentityId().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("ERROR: Unable to get ID. Error description: " + task.error!.description)
} else {
print("Signed in user with the following ID:")
print(task.result)
dispatch_async(dispatch_get_main_queue()){
self.performSegueWithIdentifier("mainViewControllerSegue", sender: self)
}
}
return nil
}
}
return nil
}
}
self.unlock()
return nil
})
I haven't changed anything between my app working and not working. I did cause a "too many password resets" error while testing the password reset functionality, but the issue persisted even when I created a new user account on my app, so I don't think that's the cause. Am I handling login correctly? If so, where should I look for other possible causes to this issue?
That exception is usually thrown if you've given Cognito a login but have not enabled your identity pool to consume that login provider. If you haven't, go to the Cognito Federated Identities console and turn on whichever provider you are trying to use (looks like User Pools), and this error should go away.
If you're certain you have set that up, can you give a code snippet of how you're setting the logins?
The key that you set the ID token against in logins should be of the format cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID> not your USER_POOL_NAME. This blog along with the link in your post for our dev guide should explain the steps and code you need.
As for the solution to deprecated logins dictionary, you need to use this constructor to create the credentials provider. The identityProviderManager here should be an implementation of AWSIdentityProviderManager Protocol and the logins method should return the dictionary mapping for your provider name to the token. The credentials provider will call this method every time it needs the identity provider token. Check this answer for more details.

AWS Getting Started With Swift (Major Issues)

I am fairly new to Swift programming, and new to developing a mobile application in general. I tried to follow the following getting started guide here: https://github.com/aws/aws-sdk-ios
What I am currently attempting to do is create the component of my application which creates user accounts, and allows users to log in.
I have followed the ReadMe, but when I get to the following portion of code to make a call to AWS:
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: CognitoRegionType,
identityPoolId: CognitoIdentityPoolId)
let configuration = AWSServiceConfiguration(
region: DefaultServiceRegionType,
credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
let dynamoDB = AWSDynamoDB.defaultDynamoDB()
let listTableInput = AWSDynamoDBListTablesInput()
dynamoDB.listTables(listTableInput).continueWithBlock{ (task: AWSTask!) -> AnyObject! in
let listTablesOutput = task.result as AWSDynamoDBListTablesOutput
for tableName : AnyObject in listTablesOutput.tableNames {
println("\(tableName)")
}
return nil
}
I try to run my program, everything compiles, but stops at the line: let listTablesOutput = task.result as AWSDynamoDBListTablesOutput
When I say stops, I mean that I get a
Thread 6:EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP,subcode=0x0)
Overall, I have spent hours reading documentation but it has not been able to answer any of my questions. Do we need to use DynamoDB to allow users to create an account and log in? If so, what exactly is Cognito for?
Also for the variables, shown in the code above, what exactly is credentialsProvider? Where do we get that information from?
Thank you in advance to anyone who can help me out in this matter.
you should use
let listTablesOutput = task.result as! AWSDynamoDBListTablesOutput
instead of
let listTablesOutput = task.result as AWSDynamoDBListTablesOutput ,
the compiler should warn you that if you compile it with the latest version of Xcode 6.
There is a DynamoDB Sample Project written in Swift which may be helpful.

Resources