I've integrated cognito into my xcode project. The sign up/password update features are working correctly. However I can't seem to get the sign in process to work. I turned on the logs and I get the following error
{"__type":"NotAuthorizedException","message":"Access Token has expired"}
Domain=com.amazonaws.AWSCognitoIdentityProviderErrorDomain Code=-1000 "Authentication delegate not set" UserInfo={NSLocalizedDescription=Authentication delegate not set}]
I have also implemented the AWSCognitoIdentityInteractiveAuthenticationDelegate delegate in the AppDelegate script.
Here's the AppDelegate code
class AppDelegate: UIResponder, UIApplicationDelegate {
class func defaultUserPool() -> AWSCognitoIdentityUserPool {
return AWSCognitoIdentityUserPool(forKey: userPoolID)
}
var window: UIWindow?
var loginViewController: LoginVC?
var navigationController: UINavigationController?
var storyboard: UIStoryboard?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// Warn user if configuration not updated
if (CognitoIdentityUserPoolId == "us-east-1_TavWWBZtI") {
let alertController = UIAlertController(title: "Invalid Configuration",
message: "Please configure user pool constants in Constants.swift file.",
preferredStyle: .alert)
let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
alertController.addAction(okAction)
self.window?.rootViewController!.present(alertController, animated: true, completion: nil)
//print("Please configure user pool constants in Constants.swift file.")
}
// setup logging
AWSDDLog.sharedInstance.logLevel = .verbose
AWSDDLog.add(AWSDDTTYLogger.sharedInstance)
// setup service configuration
let serviceConfiguration = AWSServiceConfiguration(region: CognitoIdentityUserPoolRegion, credentialsProvider: nil)
// create pool configuration
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: CognitoIdentityUserPoolAppClientId,
clientSecret: CognitoIdentityUserPoolAppClientSecret,
poolId: CognitoIdentityUserPoolId)
// initialize user pool client
AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
// fetch the user pool client we initialized in above step
let pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
self.storyboard = UIStoryboard(name: "Main", bundle: nil)
pool.delegate = self
return AWSMobileClient.sharedInstance().interceptApplication(
application, didFinishLaunchingWithOptions:
launchOptions)
//return true
}
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let navigationController = self.window?.rootViewController as? UINavigationController {
if navigationController.visibleViewController is SummaryReportVC ||
navigationController.visibleViewController is GoalStatusReportVC || navigationController.visibleViewController is YearTotalsReportVC || navigationController.visibleViewController is DailyActivityReportVC {
return UIInterfaceOrientationMask.all
} else {
return UIInterfaceOrientationMask.portrait
}
}
return UIInterfaceOrientationMask.portrait
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate {
func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
print("Calling signin VC from app delegate")
if (self.navigationController == nil) {
self.navigationController = self.storyboard?.instantiateViewController(withIdentifier: "NCFirst") as? UINavigationController
}
if (self.loginViewController == nil) {
self.loginViewController = self.navigationController?.viewControllers[0] as? LoginVC
}
DispatchQueue.main.async {
self.navigationController!.popToRootViewController(animated: true)
if (!self.navigationController!.isViewLoaded
|| self.navigationController!.view.window == nil) {
self.window?.rootViewController?.present(self.navigationController!,
animated: true,
completion: nil)
}
}
return self.loginViewController!
}
}
Here's my LoginVC code
class LoginVC: UIViewController {
@IBOutlet weak var loginButton: UIButton!
@IBOutlet weak var forgotPasswordLabel: UILabel!
@IBOutlet weak var signUpLabel: UILabel!
@IBOutlet weak var emailTF: UITextField!
@IBOutlet weak var passwordTF: UITextField!
var passwordAuthenticationCompletion: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>?
let pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)
var usernameText: String?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.tintColor = UIColor.white
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
self.navigationController!.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationController!.navigationBar.shadowImage = UIImage()
self.navigationController!.navigationBar.isTranslucent = true
loginButton.addTarget(self, action: #selector(loginUser), for: .touchUpInside)
loginButton.layer.cornerRadius = 18
emailTF.addPadding(.left(35))
passwordTF.addPadding(.left(35))
let tap = UITapGestureRecognizer(target: self, action: #selector(goToForgotPasswordVC))
let tap2 = UITapGestureRecognizer(target: self, action: #selector(goToSignUp1VC))
forgotPasswordLabel.isUserInteractionEnabled = true
forgotPasswordLabel.addGestureRecognizer(tap)
signUpLabel.isUserInteractionEnabled = true
signUpLabel.addGestureRecognizer(tap2)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.passwordTF.text = nil
self.emailTF.text = usernameText
}
@objc func loginUser() {
print("Got inside Login func")
if (self.emailTF.text != nil && self.passwordTF.text != nil) {
print("Calling login method now")
let authDetails = AWSCognitoIdentityPasswordAuthenticationDetails(username: self.emailTF.text!, password: self.passwordTF.text! )
self.passwordAuthenticationCompletion?.set(result: authDetails)
} else {
print("Empty fields")
let alertController = UIAlertController(title: "Missing information",
message: "Please enter a valid user name and password",
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
}
}
@objc func goToActivitySessionsVC() {
let storyboard = UIStoryboard(name: "TabBar", bundle: nil)
let destVC = storyboard.instantiateViewController(withIdentifier: "TabBarVC")
self.navigationController?.pushViewController(destVC, animated: true)
self.navigationController?.isNavigationBarHidden = true
}
@objc func goToForgotPasswordVC() {
let storyboard = UIStoryboard(name: "ForgotPassword", bundle: nil)
let destVC = storyboard.instantiateViewController(withIdentifier: "ForgotPasswordVC")
self.navigationController?.pushViewController(destVC, animated: true)
}
@objc func goToSignUp1VC() {
let storyboard = UIStoryboard(name: "SignUp", bundle: nil)
let destVC = storyboard.instantiateViewController(withIdentifier: "SignUp1VC")
self.navigationController?.pushViewController(destVC, animated: true)
}
/* func checkLoginStatus() {
if !AWSSignInManager.sharedInstance().isLoggedIn {
showSignIn()
}
else {
print("Logged In")
AWSSignInManager.sharedInstance().logout(completionHandler: {(result: Any?, error: Error?) in
self.showSignIn()
print("Sign-out Successful");
})
}
}
}
*/
extension LoginVC: AWSCognitoIdentityPasswordAuthentication {
public func getDetails(_ authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityPasswordAuthenticationDetails>) {
print("Get details called")
self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource
DispatchQueue.main.async {
if (self.usernameText == nil) {
self.usernameText = authenticationInput.lastKnownUsername
}
}
}
public func didCompleteStepWithError(_ error: Error?) {
print("Did commplete step with error called")
DispatchQueue.main.async {
if let error = error as NSError? {
let alertController = UIAlertController(title: error.userInfo["__type"] as? String,
message: error.userInfo["message"] as? String,
preferredStyle: .alert)
let retryAction = UIAlertAction(title: "Retry", style: .default, handler: nil)
alertController.addAction(retryAction)
self.present(alertController, animated: true, completion: nil)
print(error.description)
} else {
self.emailTF.text = nil
self.dismiss(animated: true, completion: nil)
print("Got in else")
}
}
}
}
One other thing to note is that getDetails never gets called and so does the didCompleteStepWithError method. When I click the sign in button, nothing happens.
You need to call getDetails. In the sample app, they call getDetails in UserDetailTableViewController. Try this line of code below pool.delegate = self in AppDelegate.
self.pool?.currentUser()?.getDetails()
I referred AWS Cognito User Pools in iOS (Swift) app
The AWS documentation is quite confusing. After much trial and error, I was able to successfully set up Cognito, sign up, authenticate on log in, and un-authenticate on sign out. To be quite honest, I don't fully understand why I call certain things. To the best of my ability, I will explain here.
Here's how Cognito works.. First it assumes that the user is already logged in and authenticated. It checks to see if this is true. This is the reason why the entry point for your storyboard is the view controller that users see after they are logged in. This is all done with the code that runs in your AppDelegate on launch. More on what you need to fix in that below.
If the user is not logged in, startPasswordAuthentication() will be called. In your code, (as it should be) this defined in the extension of AppDelegate for the protocol AWSCognitoIdentityInteractiveAuthenticationDelegate. Furthermore, startPasswordAuthentication() is called every time the user needs to log in. If the user is already logged in once the app starts, this is not called.
Another note on your question - getDetails is only called on launch if the user is not signed in. If on launch the user is not signed in, then this is called. It is also called if you are signed in and then you sign out.
So make sure the entry point for your storyboard is the logged-in screen.
On the statement that follows I am not entirely sure, so feel free to correct me if so: AWS automatically accesses the entry point upon successful log-in. Everything you are going in your @objc func loginUser() looks correct to me. That's what I have. But make sure your entry point is not the log-in screen but rather what shows after successful log in. Here is a picture of my storyboard:
Try adding the following. I am not quite sure why exactly this works, but it results in proper authentication:
In your AppDelegate, right after your variable for the storyboard, add a boolean isInitialized as such:
Then add this code after you set up your Cognito configuration. (right before your return statement in didFinishLaunchingWithOptions) :
Now replace the return statement you currently have for didFinishLaunching with the following:
Make sure you have this delegate set in your viewDidLoad() method for your login screen (Note you have to import AWSAuthCore):
and implement the protocol in your log-in VC as such:
Add these variables as class variables to your view controller that users see after they are logged in. They are referenced below.
Finally, make sure that you are checking the user attributes in the initial screen in viewWillAppear(). For example call the function fetchUserAttributes in this method:
Finally, here is my code for signing out:
I hope this helps. I understand this is really confusing, and to be honest, I was quite confused just writing this.. Good luck and feel free to message me with any questions.