I am trying to use AWS Cognito User Pools to add login/signup functionality for my swift iOS app. I have set up my xcworkspace with Cocoapods. In my App Delegate I have set up my Credentials Provider and Service Configuration seen below.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityPoolId: cognitoIdentityPoolId)
let defaultServiceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
return true
}
but when i try to set up the user pool configuration with:
let configurationUserPool = AWSCognitoIdentityUserPoolConfiguration(clientId: "###", clientSecret: "#########", poolId: "###")
i get a compilation error of "use of unresolved identifier AWSCognitoIdentityUserPoolConfiguration", which i dont understand because i have imported AWSCore and AWSCognito
any help or insight would be much appreciated thanks
import AWSCognitoIdentityProvider
Must also be added to the project and imported in your AppDelegate class in order for that method to be accessible.
Related
I am using AWS S3 as a backend for sound file storage in an iOS app. I can upload sound files to the bucket as I wish, but I am having trouble to make the download work.
I was first hoping to make things work using PFFile, but since I did not succeed as expected, I did some research and had the impression that using Cognito was the way to go. Since it is my firstime to use it, I may well be missing some important part.
Here is the application:didFinishLaunchingWithOptions method.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
..........
// For the AWSS3 configuration:
let poolID = "us-east-1:123.........",
credentialProvider = AWSCognitoCredentialsProvider(regionType: .USEast1,
identityPoolId: poolID)
let awsConfig = AWSServiceConfiguration(region: .APNortheast1,
credentialsProvider: credentialProvider)
AWSServiceManager.default().defaultServiceConfiguration = awsConfig
return true
}
At some point I have this code executed:
transferManager = AWSS3TransferManager.default()
For downloading the sound file, this code is run:
let soundFileName = ....,
downloadFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(soundFileName),
downloadRequest = AWSS3TransferManagerDownloadRequest()
downloadRequest?.bucket = "theappbucket"
downloadRequest?.key = soundFileName
downloadRequest?.downloadingFileURL = downloadFileURL
transferManager.download(downloadRequest!).continueWith(
executor: AWSExecutor.mainThread(),
block: {
(task:AWSTask<AnyObject>) -> Any? in
if let error = task.error {
print("Error in \(#function)\n" + error.localizedDescription)
print("Error in \(#function)\n\(error)")
return nil
}
.....
});
Here I get this error:
The operation couldn’t be completed. (com.amazonaws.AWSServiceErrorDomain error 11.)
UserInfo={HostId=ND....../FY=, Message=Access Denied, Code=AccessDenied, RequestId=EAC.....}
Having some doubts, I have also tried using .USEast1 instead of .APNortheast1, in which case the error becomes:
The operation couldn’t be completed. (com.amazonaws.AWSS3ErrorDomain error 0.)
But since my bucket is set in Asia Pacific (Tokyo) I presume the correct setting is .APNortheast1.
Can someone point out some problem in what I am doing?
I'm using Amazon Cognito for authentication and AWS iOS SDK v. 2.6.11 in my project. My app has the following flow on the main view: Get session, then make an API call using subclass of AWSAPIGateway class.
The issue here is that after successfully authenticating with Amazon Cognito, the API call response code is 403.
After stopping the app and then running it again (now the user is already authenticated) the response status code from the API is 200.
This is the message in responseData I get from the API call with 403 response:
"Message":"User: arn:aws:sts::############:assumed-role/####_unauth_MOBILEHUB_##########/CognitoIdentityCredentials is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:############:********####:##########/Development/POST/my-api-endpoint
(identifiers replaced with # characters)
It seems that the API calls are unauthorized. Is there a way to make those API calls authorized after an successful authentication?
This is the authentication code in my initial UIViewController:
let user = pool.currentUser() ?? pool.getUser()
user.getSession("myUsername", password: "myPassword", validationData: nil).continueOnSuccessWith { sessiontask -> Any? in
// i've left error handling out of this example code
let request = AWSAPIGatewayRequest(httpMethod: "POST",
urlString: "/my-api-endpoint",
queryParameters: nil,
headerParameters: nil,
httpBody: nil)
let serviceClient = AWSAPI_MY_AUTOGENERATED_Client.default()
return serviceClient.invoke(request).continueOnSuccessWith(block: { (task) -> Any? in
if let result = task.result, result.statusCode == 200 {
// A: all good - Continue
} else {
// B: Handle error (403 etc.)
}
return nil
})
This is how my AppDelegate looks like:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let pool = AWSCognitoIdentityUserPool.default()
let credentialsProvider = AWSMobileClient.sharedInstance().getCredentialsProvider()
let configuration = AWSServiceConfiguration(
region: .EUCentral1,
credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
// keeping reference to the pool and the credentials provider
self.pool = pool
self.credentialsProvider = credentialsProvider
window = UIWindow(frame: UIScreen.main.bounds)
let rootViewController = MyInitialViewController()
window!.rootViewController = rootViewController
window!.makeKeyAndVisible()
return AWSMobileClient.sharedInstance().interceptApplication(application, didFinishLaunchingWithOptions: launchOptions)
}
I am trying to implement the Google Sign In API so that users can authenticate both Gmail and Google Drive in my iOS app. I want to have two Google Sign in buttons (one in the first tutorial and one in the apps settings in case you want to change accounts later), but can not get either one to work. I tried following these three tutorials but can't get the sign in buttons to work. Right now clicking the buttons will bring up the sign-in screen, let you login, bring you back to the app, and then nothing else happens (I can't use any of the account info). Here is the code I have:
App Delegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
UINavigationBar.appearance().barTintColor = .red
UINavigationBar.appearance().barStyle = .blackOpaque
UINavigationBar.appearance().tintColor = .white
// Initialize Google sign-in
var configureError: NSError?
GGLContext.sharedInstance().configureWithError(&configureError)
assert(configureError == nil, "Error configuring Google services: \(configureError as Optional)")
return true
}
func application(_ app: UIApplication,
open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance().handle(url as URL!,
sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String!,
annotation: options[UIApplicationOpenURLOptionsKey.annotation])
}
Table View Controller (the settings screen)
override func viewDidLoad() {
super.viewDidLoad()
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().scopes.append("https://www.googleapis.com/auth/gmail.send")
// Uncomment to automatically sign in the user.
GIDSignIn.sharedInstance().signInSilently()
// Implement these methods only if the GIDSignInUIDelegate is not a subclass of UIViewController.
// Stop the UIActivityIndicatorView animation that was started when the user
// pressed the Sign In button
func signInWillDispatch(signIn: GIDSignIn!, error: NSError!) {
//myActivityIndicator.stopAnimating()
}
// Present a view that prompts the user to sign in with Google
func signIn(signIn: GIDSignIn!,
presentViewController viewController: UIViewController!) {
self.present(viewController, animated: true, completion: nil)
}
// Dismiss the "Sign in with Google" view
func signIn(signIn: GIDSignIn!,
dismissViewController viewController: UIViewController!) {
self.dismiss(animated: true, completion: nil)
}
}
//MARK: - Google Stuff
public func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if (error == nil) {
// Perform any operations on signed in user here.
//let userId = user.userID // For client-side use only!
//let idToken = user.authentication.idToken // Safe to send to the server
let email = user.profile.email
let fullName = user.profile.name
UserDefaults.standard.set(email, forKey: "googleEmail")
print("signed in \(fullName as Optional) to Google")
} else {
print("\(error.localizedDescription)")
}
}
#IBAction func didTapSignOut(_ sender: AnyObject) {
GIDSignIn.sharedInstance().signOut()
UserDefaults.standard.removeObject(forKey: "googleEmail")
}
Step 1:
You must need Client ID for Google Sign in.
Make sure that you have a google account to get a Client ID.
Let me explain the procedure to get a Client ID from google account.
You need to sign into Google Developers Console using your Google Account.
Step 2:
Create a Simple Swift App using Xcode,if you haven't created yet.
If you have already created an application, choose your application name.Add your App bundle identifier.
If it asks for your country/region.
You should put your country/region and click on Continue button.
Step 3:
Select Google service you want to use.
Click on Google Sign-in to select and click on the button to enable Google Sign-in service.
Now the Google Sign in service is enabled for your application.
Step 4:
Download GoogleService-info.plist
Now you can download GoogleService-info.plist file.You will use it later while configuring your Xcode project
Step 5
Configure Your Xcode Project
Now, create a new Xcode project:
However,to implement Google Sign-in service,you will need Google Sign-in SDK
Download Google Sign-in SDK.
Add GoogleService-Info.plist to the root of your Xcode project.
I found the best tutorial in detail by Huntmyideas
I'm using Swift 3 and Xcode 8.1 and I'm using Amazon Cognito iOS SDK together with Facebook SDK to provide user authentication.
I'm facing number of issues while using the latest Amazon Cognito SDK (2.4.11).
Main issues are:
warning "logins is deprecated: Use AWSIdentityProviderManager". There's a workaround here, but I'd like to have a normal, official Amazon way to do it.
Nevertheless, the Amazon official docs seems to be 6 month old and describes how to use an old SDK (see "iOS - Swift" section)
Whenever I try to retrieve a current user cognitoId - I get a new one. I use syntax credentialsProvider.getIdentityId().continue. But I expect to get an old one. UPD: I'm not authenticated with FB in this case.
I'd really appreciate an official Amazon SDK developers comment here.
I find it very frustrating that I have to hunt after a solution in the internet and not being able to just follow official Amazon documentation, because it's outdated.
I would recommend against following the push paradigm that is suggested above and switch to the pull paradigm. The purpose of AWSIdentityProviderManager is to prompt you for a token only when the SDK needs it, not for you to set it externally periodically whether the SDK needs it or not. This way you don't have to manage token expiry yourself, just make sure your token is valid when logins is called and if it isn't you can use an AWSCompletionSource to get a fresh one.
Assuming you have integrated Facebook login, your IdentityProviderManager should look something like this:
import Foundation
import AWSCore
import FacebookLogin
import FacebookCore
class FacebookProvider: NSObject, AWSIdentityProviderManager {
func logins() -> AWSTask<NSDictionary> {
if let token = AccessToken.current?.authenticationToken {
return AWSTask(result: [AWSIdentityProviderFacebook:token])
}
return AWSTask(error:NSError(domain: "Facebook Login", code: -1 , userInfo: ["Facebook" : "No current Facebook access token"]))
}
}
To use it:
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.YOUR_REGION, identityPoolId: "YOUR_IDENTITY_POOL_ID", identityProviderManager: FacebookProvider())
let configuration = AWSServiceConfiguration(region: AWSRegionType.usEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
And then to test getting credentials:
AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider.credentials().continue(with: AWSExecutor.default(), with: { (task) -> Any? in
print(task.result ?? "nil")
return task
})
BTW, I needed to add this to my app delegate to get Facebook Login to work with Swift which is not mentioned in the instructions here https://developers.facebook.com/docs/swift/login :
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(app, open: url, sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String, annotation: options[UIApplicationOpenURLOptionsKey.annotation])
}
Yes in latest SDK logins property get deprecated, so we need to assign IdentityProvider to logins by using AWSIdentityProviderManager delegate. So do as follows,
Create one custom class which adopt AWSIdentityProviderManager delegate.
import UIKit
import AWSCognitoIdentityProvider
import AWSCore
import Foundation
class DVCustomIdentityProvider: NSObject, AWSIdentityProviderManager {
var tokens: NSDictionary = [String : String]() as NSDictionary
init(tokens: [String : String]) {
self.tokens = tokens as NSDictionary
}
func logins() -> AWSTask<NSDictionary> { // AWSIdentityProviderManager delegate method
return AWSTask(result: tokens)
}
}
Add following code in view controller you want.
#IBAction func loginButtonPressed(_ sender: UIButton) {
if (phoneNumberTextField.text != nil) && (passwordTextField.text != nil) {
// Change userName.getSession.... with your Facebook method to get authenticate from Facebook, in success block add what I added in my success block.
userName.getSession(phoneNumberTextField.text!, password: passwordTextField.text!, validationData: nil).continue(with: AWSExecutor.mainThread(), withSuccessBlock: { (task: AWSTask<AWSCognitoIdentityUserSession>) -> Any? in // Your Facebook call will go here
if task.error != nil {
// Error
} else {
// SUCCESS BLOCK
self.updateCredentials()
}
return nil
})
} else {
// Credential empty
}
}
func updateCredentials() {
let customcedentialProvider = DVCustomIdentityProvider(tokens: ["graph.facebook.com" : token]))
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: "Your region", identityPoolId: "Your pool id", unauthRoleArn: "Your unearth role name", authRoleArn: "Your auth role name", identityProviderManager: customcedentialProvider)
let configuration = AWSServiceConfiguration(region: "Your region", credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
credentialsProvider.getIdentityId().continue(with: AWSExecutor.mainThread(), withSuccessBlock: { (taskTask: AWSTask<NSString>) -> Any? in
if taskTask.error == nil && taskTask.exception == nil {
kUserIdentityID = taskTask.result as String? // Im saving user identity id in constant variable called "kUserIdentityID"
} else {
// Do Nothing
}
return nil
})
}
import following in your view controller
import AWSCognitoIdentityProvider
import AWSCore
import AWSCognito
Note: This code is written in swift 3
I'm building an iOS (Swift) app using AWS as the backend with Developer Authenticated Identities. Everything works fine until I close the app, leave it for a while and then relaunch. In this scenario I often, but not always, receive ExpiredTokenException errors when trying to retrieve data from AWS.
Here is my code:
class DeveloperAuthenticatedIdentityProvider: AWSAbstractCognitoIdentityProvider {
var _token: String!
var _logins: [ NSObject : AnyObject ]!
override var token: String {
get {
return _token
}
}
override var logins: [ NSObject : AnyObject ]! {
get {
return _logins
}
set {
_logins = newValue
}
}
override func getIdentityId() -> AWSTask! {
if self.identityId != nil {
return AWSTask(result: self.identityId)
} else {
return AWSTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
if self.identityId == nil {
return self.refresh()
}
return AWSTask(result: self.identityId)
})
}
}
override func refresh() -> AWSTask! {
let apiUrl = "https://url-goes-here" // call my server to retrieve an OpenIdToken
request.GET(apiUrl, parameters: nil, progress: nil,
success: {
(task: NSURLSessionDataTask, response: AnyObject?) -> Void in
let tmp = NSMutableDictionary()
tmp.setObject("temp", forKey: "ExampleApp")
self.logins = tmp as [ NSObject : AnyObject ]
let jsonDictionary = response as! NSDictionary
self.identityId = jsonDictionary["identityId"] as! String
self._token = jsonDictionary["token"] as! String
awstask.setResult(response)
},
failure: {
(task: NSURLSessionDataTask?, error: NSError) -> Void in
awstask.setError(error)
}
)
return awstask.task
}
}
And in the AppDelegate:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let identityProvider = DeveloperAuthenticatedIdentityProvider()
// set default service configuration
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: cognitoRegion, identityProvider: identityProvider, unauthRoleArn: unauthRole, authRoleArn: authRole)
let configuration = AWSServiceConfiguration(region: defaultServiceRegion, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
// set service configuration for S3 (my bucket is located in a different region to my Cognito and Lambda service)
let credentialsProviderForS3 = AWSCognitoCredentialsProvider(regionType: cognitoRegion, identityProvider: identityProvider, unauthRoleArn: unauthRole, authRoleArn: unauthRole)
let awsConfigurationForS3 = AWSServiceConfiguration(region: s3ServiceRegion, credentialsProvider: credentialsProviderForS3)
AWSS3TransferUtility.registerS3TransferUtilityWithConfiguration(awsConfigurationForS3, forKey: "S3")
return true
}
This post suggests that the Cognito token has expired and it is up to the developer to manually refresh. This seems overly complex as it would require setting a timer to refresh regularly, handling app closures and relaunches and handling AWS requests that occur while the refresh is taking place. Is there a simpler way? For example, is it possible to have the AWS SDK automatically call refresh whenever it attempts to query the server using an expired token?
Any help would be appreciated. I'm using version 2.3.5 of the AWS SDK for iOS.
The AWS Mobile SDK for iOS 2.4.x has a new protocol called AWSIdentityProviderManager. It has the following method:
/**
* Each entry in logins represents a single login with an identity provider.
* The key is the domain of the login provider (e.g. 'graph.facebook.com') and the value is the
* OAuth/OpenId Connect token that results from an authentication with that login provider.
*/
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins;
The responsibility of an object conforming to this protocol is to return a valid logins dictionary whenever it is requested. Because this method is asynchronous, you can make networking calls in it if the cached token is expired. The implementation is up to you, but in many cases, AWSIdentityProviderManager manages multiple AWSIdentityProviders, aggregates them and return the logins dictionary.
Unfortunately developers refreshing the token is the only way.
I agree that it would be simpler for app developers if AWS SDK handled this but the way CrdentialsProvider is designed is supposed to be generic for all providers. For example, if someone wants to use Facebook as provider then AWS SDK will not be able to handle the refresh on its own and developer will have t handle that in his app. Keeping the refresh flow out of the SDK gives us the capability to keep the CredentialsProvider generic.