Retrieving Cognito Identity ID using credentialsProvider vs AWSMobileClient - ios

Why am I successfully getting getting Cognito ID when I use getIdentityID from credentials provider but get an error when using AWSMobileClient?
I have setup a Federated Identity with a custom developer provider. I am initializing the Amazon Cognito credentials provider using the code snippet generated by the Amazon Cognito console.
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: "IDENTITY_POOL_ID")
let configuration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
Following this, I am retrieving the cognito Identity ID using the following:
credentialsProvider.getIdentityId().continueWith(block: { (task) -> AnyObject? in
if (task.error != nil) {
print("Error: " + task.error!.localizedDescription)
}
else {
// the task result will contain the identity id
let cognitoId = task.result!
print("Cognito id: \(cognitoId)")
}
return task;
})
When I run this, I successfully retrieve the identity ID, and I can also view it as an unauthenticated user on my console for the federated identity. However, when I try retrieving the identity ID using AWSMobileClient as follows:
AWSMobileClient.sharedInstance().initialize { (userState, error) in
if let userState = userState {
print("UserState (AccountView): \(userState.rawValue)")
} else if let error = error {
print("error: \(error.localizedDescription)")
}
}
// Retrieve your Amazon Cognito ID
AWSMobileClient.sharedInstance().getIdentityId().continueWith { task in
if let error = task.error {
print("error: \(error.localizedDescription) \((error as NSError).userInfo)")
print(error)
}
if let result = task.result {
print("identity id: \(result)")
}
return nil
}
I get AWSMobileClient error 30 : error: cognitoIdentityPoolNotConfigured(message: "Cannot get identityId since cognito credentials configuration is not available.")
Why is this? What am I missing?
I am using AWSMobileClient, AWSCore
Environment(please complete the following information):
- SDK Version: 'AWSMobileClient', '~> 2.10.0', 'AWSCore', '~> 2.10.0'
- Dependency Manager: Cocoapods
- Swift Version : 5.0
- Xcode Version: 10.3
Device Information (please complete the following information):
- Device: MacOS 10.14.5
- iOS 12.4

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

Passing LWA token to Cognito

I am working a an app which uses the Alexa Voice Service and maintains different users, so the users needs to login with Amazon (LWA). I have implemented it like it is written in the docs and it works flawlessly.
LWA docs: https://developer.amazon.com/de/docs/login-with-amazon/use-sdk-ios.html
AMZNAuthorizationManager.shared().authorize(request, withHandler: {(result : AMZNAuthorizeResult?, userDidCancel : Bool, error : Error?) -> () in
if error != nil {
// Handle errors from the SDK or authorization server.
}
else if userDidCancel {
// Handle errors caused when user cancels login.
}
else {
// Authentication was successful.
// Obtain the access token and user profile data.
self.accessToken = result!.token
self.user = result!.user!
}
})
Furthermore I need to retrieve information from DynamoDB, which uses Cognito for authentification. As stated in the docs, there should be a way pass the access token form LWA to Cognito, but I can't find the proper place to do it. They say LWA provides an AMZNAccessTokenDelegate, which it does not. The delegate method provides an API result which Cognito needs. The link in the Cognito docs below refers to the same exact link from the LWA docs I posted above.
Cognito docs: https://docs.aws.amazon.com/cognito/latest/developerguide/amazon.html
func requestDidSucceed(apiResult: APIResult!) {
if apiResult.api == API.AuthorizeUser {
AIMobileLib.getAccessTokenForScopes(["profile"], withOverrideParams: nil, delegate: self)
} else if apiResult.api == API.GetAccessToken {
credentialsProvider.logins = [AWSCognitoLoginProviderKey.LoginWithAmazon.rawValue: apiResult.result]
}
}
What am I missing?
[EDIT]
I crawled through the LWA sources today until I finally found the correct delegate method.
Use AIAuthenticationDelegate instead of AMZNAccessTokenDelegate
But that lets me sit in front of the next two problems:
I.
Value of type 'AWSCognitoCredentialsProvider' has no member 'logins'
Maybe I have to use the following?
.setValue([AWSCognitoLoginProviderKey.LoginWithAmazon.rawValue: apiResult.result], forKey: "logins")
II.
Use of unresolved identifier 'AWSCognitoLoginProviderKey'
What do I put here? Maybe the API key I got from LWA?
[EDIT2]
I wanted to try it out, but requestDidSucceed never gets called, even through I successfully logged in.
class CustomIdentityProvider: NSObject, AWSIdentityProviderManager {
func logins() -> AWSTask<NSDictionary> {
return AWSTask(result: loginTokens)
}
var loginTokens : NSDictionary
init(tokens: [String : String]) {
self.loginTokens = tokens as NSDictionary
}
}
in the Authorization code at this below in successsful
AMZNAuthorizationManager.shared().authorize(request) { (result, userDidCancel, error) in
if ((error) != nil) {
// Handle errors from the SDK or authorization server.
} else if (userDidCancel) {
// Handle errors caused when user cancels login.
} else {
let logins = [IdentityProvider.amazon.rawValue: result!.token]
let customProviderManager = CustomIdentityProvider(tokens: logins)
guard let apiGatewayEndpoint = AWSEndpoint(url: URL(string: "APIGATEWAYURL")) else {
fatalError("Error creating API Gateway endpoint url")
}
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USWest2, identityPoolId: "IDENTITY_ID", identityProviderManager:customProviderManager)
let configuration = AWSServiceConfiguration(region: .USWest2, endpoint: apiGatewayEndpoint, credentialsProvider: credentialsProvider)
}

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 iOS SDK AWSServiceManager multiple service configurations

I've integrated the AWS iOS SDK (v.2.3.6) into my application. It works fine and good, except that I've noticed that defaultServiceManager has a disclaimer:
"You should use this singleton method instead of creating an instance of the service manager".
I ordinarily wouldn't have an issue with this, except it's defaultServiceConfiguration is immutable:
"This property can be set only once, and any subsequent setters are ignored."
I have a requirement that a service configuration (ie. identityPoolId + region) be able to change at runtime.
What are the possible ways around this? I'd love to be able to just reset the service configuration at any point, but that's unlikely given what the documentation says.
You should not mutate the default service configuration. Instead, each service client provides the following class methods:
+ register[ServiceClientName]WithConfiguration:forKey:
+ [ServiceClientName]ForKey:
For example, for AWSS3TransferUtility, they are:
+ registerS3TransferUtilityWithConfiguration:forKey:
+ S3TransferUtilityForKey:
In this way, you can pass a different service configuration for each service client in the runtime. By following this pattern, you can avoid the unintentionally "polluted" default service configuration bugs that can be very difficult to debug.
To use the custom configurations with each upload you can create temporary access key and secret with a session. Then you can use those keys to upload your file. Below is the code snippet
/// This function is used to authorize user with AWS.
private func connectWithAWS() -> AWSServiceConfiguration? {
/// Simple session credentials with keys and session token.
let credentialsProvider = AWSBasicSessionCredentialsProvider.init(accessKey: "TEMPORARY ACCESS KEY", secretKey: "TEMPORARY SECRET KEY", sessionToken: "TEMPORARY SESSION")
/// A service configuration object.
guard let configuration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider) else {
return nil
}
return configuration
}
func uploadFileToS3() {
/// Get configurations for bucket
guard let configuration = connectWithAWS() else {
///AWSServiceConfiguration Not Initialised.
return
}
///Check if a AWSS3TransferUtility already exist for current access key or not.
let trans = AWSS3TransferUtility.s3TransferUtility(forKey: "TEMPORARY ACCESS KEY")
if trans == nil {
/// If AWSS3TransferUtility is nil than create new for a access id
///
AWSS3TransferUtility.register(with: configuration, transferUtilityConfiguration: nil, forKey: "TEMPORARY ACCESS KEY") { (err) in
print("Error in AWSS3TransferUtility.register: ->>> \(err?.localizedDescription ?? "")")
}
}
///
/// Check if a AWSS3TransferUtility already exist for current access key or not.
guard let transferUtility = AWSS3TransferUtility.s3TransferUtility(forKey: "TEMPORARY ACCESS KEY") else {
return
}
///
/// Start Uploading process
///
let expression = AWSS3TransferUtilityUploadExpression()
expression.setValue("public-read", forRequestHeader: "x-amz-acl")
let s3BucketName = "BUCKET NAME"
let url = URL.init(fileURLWithPath: "fileURL")
transferUtility.uploadFile(url, bucket: s3BucketName, key: "fileName", contentType: "contentType", expression: expression) { (task, error) in
if error != nil {
print("Upload failed ❌ (\(error!))")
return
}
if task.status == AWSS3TransferUtilityTransferStatusType.completed {
let s3URL = "https://\(s3BucketName).s3.amazonaws.com/\(task.key)"
print("Uploaded to:\n\(s3URL)")
return
} else {
print("Not uploaded")
}
}.continueWith { (task) -> Any? in
if let error = task.error {
print("Upload failed ❌ (\(error))")
}
if task.result != nil, task.isCompleted == true {
let s3URL = "https://\(s3BucketName).s3.amazonaws.com/\(task.result!.key)"
print("Uploading Start of : \(s3URL)")
} else {
print("Unexpected empty result.")
}
return nil
}
}

Amazon Cognito with iOS: Access to Identity Forbidden

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.

Resources