Issue With Using Multiple Custom Authenticated Accounts via AWS iOS SDK - ios

We are building an app in iOS 9 that uses AWS as a backend.
The system is using custom authentication via Lambda (loosely based on https://www.youtube.com/watch?v=ZBxWZ9bgd44 and https://github.com/awslabs/api-gateway-secure-pet-store ), and on success cognito credentials are returned to be used for calls to AWS (API Gateway). The app uses a custom credentials provider and when a user successfully logs in this is what is being done to set up the user/session to make future calls to the system's AWS backend for the session:
// The SystemUserCredentials class holds the entered username and password from the UIView
let userCredentials: SystemUserCredentials = SystemUserCredentials(username, password)
// CustomAwsSessionCredentialProvider inherits AWSCredentialsProvider
let apiCredsProvider = CustomAwsSessionCredentialsProvider(credentials: userCredentials)
let defaultConfig = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: apiCredsProvider)
// API_CLIENT_CONFIG_KEY is a constant for the configuration key
SystemAPIClient.registerClientWithConfiguration(defaultConfig, forKey: API_CLIENT_CONFIG_KEY)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultConfig
And that initial user can communicate with our authenticated AWS backend with no problem.
What is being noticed is that when a user logs out with one valid account (the initial one when the app loaded), and then the user tries to log in with a different valid account there are failures in communicating with the [authenticated] web methods, and it looks like it is because the app is still trying to use the cognito credentials from the first/initial session. I did find this comment in AWS's API SDK which I believe is causing the issue: The default service configuration object. This property can be set only once, and any subsequent setters are ignored.
My initial thoughts are to move away from using the defaultServiceManager and its
defaultServiceConfiguration and going with non-default key-based services, clients,
configuration, etc.
My question is - has anyone else run into this, and is there a simpler way to use multiple
custom-authenticated clients in an app using the default service manager and
configuration?
Thanks much in advance.

Related

G Suite Service Account Fails Requests But Delegation Works?

I have followed the documentation for creating a G Suite Service Account. The following works fine to emulate a given user in my domain:
def create_directory_service(user_email):
credentials = ServiceAccountCredentials.from_json_keyfile_name(
SERVICE_ACCOUNT_FILE_PATH,
scopes = SCOPES)
credentials = credentials.create_delegated(user_email)
return build('admin', 'directory_v1', credentials=credentials)
However, I want to be able to just use the service account to access domain information without needing to emulate a particular user. But if I pass in the service account's email address (as given in its key file), I just get HttpError 503: "Service unavailable. Please try again".
I've added the service account's email address as a Service Account Admin, but it still doesn't work. Again, delegation works fine, but not using the service account directly.
Does anyone know what I'm missing?
To use Directory API, you must use delegation to impersonate a user in the domain, that can use Directory API (so usually an admin).

AWS Cognito Authentication + AWS Mobile Client + API Gateway + S3 Bucket

I built a login screen for my app using AWSCognitoAuth, exactly like in one of the provided examples (https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoAuth-Sample). This works fine - it shows the login screen in an instance of SFSafariViewController where the user just logs in using regular username + passwort, or through Facebook or Google. So after this authentication, I get the access and refresh tokens which I can then use for fetching data from my API, which is routed through AWS API Gateway. The corresponding AWS API Gateway routes are configured to use "User Pools" authorization, instead of "IAM".
But now I need to download some files from a S3 bucket, which is not public. So what needs to be done, is to get temporary AWS Credentials and access the bucket using them. The AWSMobileClient library together with the S3 Transfer Utility are able to do that. But my problem is, I don't know how to tell the AWSMobileClient about the fact, that the user has now signed in .AWSMobileClient is initialized in the appDelegate's didFinishLaunching:withOptions like this:
let serviceConfiguration = AWSServiceConfiguration(
region: .EUWest1,
credentialsProvider: AWSMobileClient.sharedInstance().getCredentialsProvider()
)
//create a pool
let configuration = AWSCognitoIdentityUserPoolConfiguration.init(clientId: CognitoIdentityUserPoolAppClientId, clientSecret: CognitoIdentityUserPoolAppClientSecret, poolId: CognitoIdentityUserPoolId)
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: configuration, forKey: AWSCognitoUserPoolsSignInProviderKey)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
AWSFacebookSignInProvider.sharedInstance().setPermissions(["public_profile", "email"])
AWSSignInManager.sharedInstance().register(signInProvider: AWSFacebookSignInProvider.sharedInstance())
return AWSMobileClient.sharedInstance().interceptApplication(
application,
didFinishLaunchingWithOptions: launchOptions)
The thing is, when I completely close the app after doing the AWSCognitoAuth signin and reopen it, the AWSMobileClient somehow finds the session and the credentials are fetched properly and the file from the bucket gets loaded. But I need to somehow trigger this manually after the user signs in, I cannot force the user to quit and reopen the app, not in 2018... I have been debugging this now for a long time and was digging through the AWSMobileClient framework, trying to find how to hook those two together, but with no success. How can I do it?
But now I need to download some files from a S3 bucket, which is not
public. So what needs to be done, is to get temporary AWS Credentials
and access the bucket using them. The AWSMobileClient library together
with the S3 Transfer Utility are able to do that. But my problem is, I
don't know how to tell the AWSMobileClient about the fact, that the
user has now signed in .AWSMobileClient is initialized in the
appDelegate's didFinishLaunching:withOptions
Did you already try to use Pre-signed url? It is meant to cover your requirements. It allows temporary access to aws resources, in your case get access on S3 bucket.
You have samples using iOS sdk here, check the S3TransferUtility-Sample.
S3TransferUtility-Sample (Swift, Objective-C). This is a sample mobile
application that demonstrates how to use the Amazon S3 PreSigned URL
Builder to download / upload files in background. Involved AWS
Services are:
Amazon S3 Amazon
Cognito Identity
Hope it helps!

AWS web and mobile hub application

On AWS, I wish to create an application that allows a user to sign in via mobile, web or both.
I created a system using API Gateway, Lambdas and DynamoDB for the back end. I have sign in working for web using JavaScript but was having issues finding a Swift example for iOS of the same code (objective C only available). So I created a mobile hub application, imported my existing API and have a working iOS sign in.
The issue is that the iOS side uses the Mobile Hub so I now have 2 different User Pools so you can't sign up on web and log in on mobile (or vice versa).
I tried to change the settings in the iOS app to point to my web app Cognito User Pool settings and remove the secret but it errors as it can not be null or empty.
Why does the mobile hub require a client secret? The JavaScript documentation suggested was bad practice since code can be de-compiled and the secret extracted.
There doesn't seem to be any consistent documentation that explains, what I would guess, as the most common use case of a mobile and web app!
The other issue is that I can download my API client SDK for my API Gateway for use with web app and iOS app. But, the generated mobile hub app includes a REST based call? Am I going crazy here or does the official web approach not link to the official mobile approach?
So the key questions are:
Can or should I change the mobile apps to point at the original
Cognito?
if so should I remove the client secret?
can I effectively ignore the Mobile Hub after set up and use it purely for code generation? Assuming it was then working can I just use the generated client SDK for my API Gateway?
Is there a better way of setting up an iOS (later Android too) and web app?
I've spent considerable time and effort, and tried many approaches.
The "Mobile Hub" nicely sets up the user pool, identity pool, IAM roles etc.
The keys etc are mostly provided in the Info.plist file, although ( unwisely ) the developers of the user pool AWSSignInProvider made it have hardcoded keys in a configuration file.
SO:
If you don't intend to use the "Mobile Hub" console application for making changes to your mobile app configuration, then you won't need any more downloads. In that case, you don't have to worry about changes to Info.plist or the configuration file, and you can edit what you want.
It is unclear if you are going to use the mobile hub created identity pool and just want to insert your user pool, or if you want to change both pools. Obviously if you are using the same identity pool then some of the changes below will not be needed (they will be obvious because you will be changing them to be exactly the same).
So all you have to do is change the ID's to get everything hooked up correctly.
In general you need to fix all the downloaded keys and ID's in Info.plist and the configuration file, and then you need to update the server configuration. In more detail, here are the places you need to change it:
in the app:
update all the keys in Info.plist to be the keys you want. (specifically credentials provider and identity manager keys for google) But if you are using other mobile hub services, check the keys there too.
If you are using s3, and some of the other services the directory names/database names are also stored in the code... I leave it as an activity to find them.
in the file MySampleApp->AmazonAWS->AWSConfiguration.swift edit the keys provided by Mobile hub to match your user pool (do this while quietly swearing under your breath because they are not in Info.plist)
in the console:
put your app name in your user pool APP's list if it is not there, and record the user pool id, app id, and app secret.
click on federated identities and the identity pool created by mobile hub and update the authentication provider to use your cognito user pool id and app id.
if you are changing the identity pool too, then you will need to look at the IAM Roles for your auth and unauth users and specifically edit the policy that is named: .....yourapp....signin_MOBILEHUB_xxxxxxx, and change the identity pool id in that policy to be the one you want to use. Do this for both auth and unauth.
( you can change the id if you only use one pool, or add another to a list of id's like this if you will have multiple identity pools (for test...etc))
"Resource": [
"arn:aws:cognito-identity:*:*:identityPool/us-east-1:8s8df8f8-sd9fosd9f0sdf-999sd99fd",
"arn:aws:cognito-identity:*:*:identityPool/us-east-1:dfsf9099-sd9fosd9f0sdf-sd9f0sdf09f9s"
]
similarly, in the trust relationship associated with the roles, you need to fix the id's, (or handle multiple ID's if you want the role to serve multiple identity pools). Here is how to specify multiple id's there.
"Condition": {
"ForAllValues:StringLike": {
"cognito-identity.amazonaws.com:aud": [
"us-east-1:8s8df8f8-sd9fosd9f0sdf-999sd99fsdfdd",
"us-east-1:dfsf9099-sd9fosd9f0sdf-sd9f0sdf09f9s"
]
},
if you are using google too... you need to make sure that you have an identityProvider in the IAM configuration for google (mobile hub did that for you) and if you are using your own identity pool , in your federated identity pool authorization providers configuration you will need to select google open id provider (and put google in the authorized providers too (but I don't think this part is strictly needed))
facebook doesnt use OpenID Connect, it has a proprietary way of configuring into the authentication providers section, so enter those keys if needed in the identity pool authentication providers section.
And that should be enough to make it work.
And no you are not going crazy... the documentation does not match the current IOS SDK. Mobile hub uses the aws-mobilehub-helper-ios (github) which is built on TOP of the sdk, so the documentation does not apply to that either! Mobile Hub Helper has a nice design, so I recommend you use it, rather than the raw SDK.
(and lastly... and I am out of my depth here because I don't use API gateway, but my understanding was that the API Gateway is a way to get credentials to use AWS Services, and with the mobile hub app you will be using Cognito to get those credentials, so I am not sure you will need to bring the API Gateway into it...at all)
UPDATE
You may want to use no client secret for users of your javascript app, and use a IOS Mobile App on the same pool too. This can be done in two ways:
1) The better way is that you create two different client's in the user pool. For one you will generate a client secret, for the other you will UNCHECK the "generate client secret" box.
Then in your Federated Identity Pool you go to Authentication Providers, and click on Cognito, and specify TWO DIFFERENT PROVIDERS USING THE SAME USER POOL ID. (This is not really two different providers, but that is how the console makes you specify it). And you put the two different client ids in those providers.
Now both the IOS app and the Javascript app can access the pool and get authentication and credentials from the identityProvider and credentialsProvider.
2) A not so good way. The reason this way is worse is because I don't know the impact (if any) it has on the security of your mobile app. And at AWS there is nobody to ask the question to without buying a support contract. But the other way exists.
What you do is use the same client id in both apps, and you don't generate a client secret. To do this you put "nil" in the clientSecret. This works fine with some caveats.
First, the AWS Mobile Hub has a bug in it's AWSCognitoUserPoolsSignInProvider. That class requires that the clientSecret is non-null. But in the SDK, the only way to tell the SDK that you want no client secret is to pass nil! However there are workarounds.
(What I did was use the AWSCUPIdPSignInProvider.swift (that I wrote), which will work fine and I have a version that will take a nil for the secret. I did that because it was faster for me to test this out. you can find that signin provider on github if you want to use it)
But a better (more future proof) solution is probably to use the AWSCognitoUserPoolsSignInProvider that the mobile hub delivers, but change the code in AWSMobileClient to configure and register your own pool rather than letting AWSCognitoUserPoolsSignInProvider do it for you.
I haven't bothered to try this, (because we only have to do it because AWS has not gotten around to updating the github aws-mobilehub-helper-ios yet). But basically in AWSMobileClient instead of this code:
func setupUserPool() {
// register your user pool configuration
AWSCognitoUserPoolsSignInProvider.setupUserPoolWithId(AWSCognitoUserPoolId, cognitoIdentityUserPoolAppClientId: AWSCognitoUserPoolAppClientId, cognitoIdentityUserPoolAppClientSecret: AWSCognitoUserPoolClientSecret, region: AWSCognitoUserPoolRegion)
AWSSignInProviderFactory.sharedInstance().registerAWSSignInProvider(AWSCognitoUserPoolsSignInProvider.sharedInstance(), forKey:AWSCognitoUserPoolsSignInProviderKey)
}
you would have something like this code
func setupUserPool() {
// register your user pool configuration
// find the service configuration (we don't know if they set it as default)
let credentialProvider = AWSCognitoCredentialsProvider(regionType: .USEast1 (or your region), identityPoolId: "YourIdentityPoolId")
let configuration = AWSServiceConfiguration(region: .USWest2 (or your region), credentialsProvider: credentialProvider)
// configure and put your own user pool in the service configuration
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: AWSCognitoUserPoolAppClientId, clientSecret: nil, poolId:AWSCognitoUserPoolId)
// now we register that pool with the service configuration using the key they use
AWSCognitoIdentityUserPool.register(with: configuration, userPoolConfiguration: userPoolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
AWSSignInProviderFactory.sharedInstance().registerAWSSignInProvider(AWSCognitoUserPoolsSignInProvider.sharedInstance(), forKey:AWSCognitoUserPoolsSignInProviderKey)
}
But as I said above, solution 1, use two different clients and specify two different providers is preferred.

AWS Cognito Authenticated Credentials IOS Swift 2.3 - 3 (Integrating User Pool & Identity Pool)

I have been trying to generate this "identity id" for my user pool users to access AWS resources. But been unsuccessful.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
//user pool configuration
let serviceConfiguration = AWSServiceConfiguration(
region: AWSRegionType.USWest2,
credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: K.COGNITO_USER_POOL_APP_CLIENT_ID, clientSecret: K.COGNITO_USER_POOL_APP_CLIENT_SECRET, poolId: K.COGNITO_USER_POOL_ID)
//create a pool
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: "UserPool")
self.pool = AWSCognitoIdentityUserPool(forKey: "UserPool")
self.credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USWest2, identityPoolId: K.IDENTITY_POOL_ID, identityProviderManager: self.pool!)
let configuration = AWSServiceConfiguration(region: AWSRegionType.USWest2, credentialsProvider: self.credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
self.pool!.delegate = self
self.user = self.pool!.currentUser()
return true
}
That's All that is given in Integrating User pool with Cognito(Swift) Documentation.
While The Android Code Says about passing the "idToken" & user pool url in the logins map, there is no such thing mentioned in the swift code.
So far, I get the "AccessToken" & "IdToken" in the logs window in Xcode after the user signs in the app.
What do I need to do further to get Authenticated Identities ? Correct me if I'm doing wrong.. Thanks.
You are correct the documentation does not really help much for identity pools and user pools and getting them integrated.
My recommendation would be to, rather than depending on other documentation or examples, use the AWS Mobile Hub. If you use that site, it will DOWNLOAD a full Swift Xcode project that properly does the integration of user pools and another identity provider (Google or Facebook). The user pools integration is a new feature of the mobile hub.
Also, the mobile hub has a nice architecture of the downloaded app, separating Identity Managment from Sign in Management.
But if you want to continue to slog through this using the documentation the following set of notes should help. Plus the following salient points.
1) If you use the SDK you do not have to manage tokens, the SDK gets keeps and exchanges tokens for you.
2) All you have to do is configure your user pool correctly, configure your IAM correctly, and call a short sequence of SDK calls (outlined below). One of the great things that the mobile hub does is get all that right for you once, letting you modify and evolve it as you learn.
3) The short sequence of calls is:
- make a user pool
- make an identity pool
- make the user pool the identityProviderManager for your service configuration
(note: You have done all that above, so all you need is to do the following steps)
- perform the Get Identity API call (this is done when you do the "Get Session" SDK call.
- perform the GetCredentialsForIdentity API call (this is done when you do the "credentials" SDK call)
At that point you will have credentials and the ability to access AWS services based upon your logged in (authenticated) state, and any associated rules in IAM.
There is a (I think) helpful explanation in these notes: notes on using cognito and user pools
Further in that repository there is an example of user pools with identity pools working together including registration, forgot password, etc, And including the ability to do identity merging.
(the new user pool code in Mobile Hub does some of that nicely now (as of 4 days ago when they added user pool code) but does not yet do the identity merging).
Hope this helps
The following answer assumes you're using AWS Mobile Hub, downloaded the awsconfiguration.json file, and that you're using that json file in your app...
This might be far off, but I've been working on an app of my own for almost a year now, so my memory is limited - I don't remember how I set the MobileHub up and if maybe I messed something up along the way.
I was able to log in users, but they were unauthenticated. I could not for the life of me authenticate users. I moved on and just addressed the problem tonight. I realized, somewhere along the way, that the values in my awsconfiguration.json for CognitoUserPoolId, AppClientId, and AppClientSecret were wrong. I remember about a year ago I had to change the CognitoIdentityPoolId, but I'm not sure why. I also know that I downloaded this file from the AWS Mobile Hub sample project for my application.
Anyways, it's worth checking these values to make sure they all match up with the expected values. This is important considering that I was able to use my application unauthenticated yet normally with 3 of these crucial values wrong.

How to use AWS SDK v2.4.7 to authenticate end user via external provider (e.g., Google)?

I'm in the process of integrating AWS Cognito for managing authentication and user data in my iOS 9 app using Swift with the AWS iOS SDK v2.4.7. However, I can't find a clear example of how to do this. I've got the Google Sign-In working (i.e., I can successfully get an ID token from Google), but I'm not sure how I can use this to authenticated with AWS. I've created an identity pool and user role with the correct permissions. My problem seems to be in passing this token to Cognito for authentication on AWS's end.
The code snippet provided in the AWS guide still uses the deprecated methods of the pre-2.4.0 SDK. I haven't found an example of the new best practice. To make matters a bit more confusing, AWS's Authentication Flow overview still seems to be pre-v2.4.0 since it is described in this post dated before the release of v2.4.0, but seems to use a different process than that outlined in that first code snippet. (It also seems that Google's process has changed a bit since the guide was published, moving from a Google+ sign in to a new Google Sign-In system.)
In summary, what is the accepted way of logging in my end user via Google and authenticating via Cognito (for future AWS API calls) using the latest AWS iOS SDK (v2.4.7)?
Here is my current attempt. First, in my app delegate, I initialize the AWS service as described in the guide:
// AWS setup
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId:"us-east-1:<REDACTED>")
let serviceConfig = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = serviceConfig
Then, when the Google Sign-In process is complete, I attempt to use the id token to authenticate with Cognito. The user in the following snippet is the GIDGoogleUser returned by a successful Google Sign-In. I have verified the Google Sign-In is working and I have a valid id token.
let getIdInput = AWSCognitoIdentityGetIdInput()
getIdInput.logins = [ "accounts.google.com" : user.authentication.idToken ]
getIdInput.identityPoolId = "us-east-1:<REDACTED>"
getIdInput.accountId = "<REDACTED>"
AWSCognitoIdentity.defaultCognitoIdentity().getId(getIdInput).continueWithBlock{ (task: AWSTask?) -> AnyObject? in
if task?.error == nil {
let result = task?.result as! AWSCognitoIdentityGetIdResponse
let input = AWSCognitoIdentityGetCredentialsForIdentityInput()
input.identityId = result.identityId
AWSCognitoIdentity.defaultCognitoIdentity().getCredentialsForIdentity(input)
} else {
}
return nil
}
The logs give
Unauthenticated access is not supported for this identity pool.
This is not necessarily incorrect (it is true that I don't allow unauthenticated access for this pool), but is not what I expected, since the Google token which I passed should have authenticated the user. Any thoughts?
Luckily I was early enough in the development process that I could switch backend services. After some quick research, I've tentatively switched to Firebase by Google. I managed to implement in 30 minutes what I had previously struggled with for 6 hours using AWS. I can't say for sure if it's a good alternate for all AWS Cognito use cases, but it looks like it will work for me. Definitely worth checking out if you're just getting started.
To answer the original question related to Cognito. You need to set the logins in AWSCognitoIdentityGetCredentialsForIdentityInput as well.
Additionally for simplification you can use the high level SDK for Cognito.
Examples can be found here:
http://docs.aws.amazon.com/cognito/latest/developerguide/getting-credentials.html

Resources