Refresh Token AWS Cognito User Pool, Code Block Not Even Running - ios

I am attempting to refresh a users tokens, and I am doing this in a "child" app that only has access to idToken, accessToken, refreshToken, and the user's information aside for their password. It is sandboxed and cannot communicate to main app.
I have attempted the following code to run initiateAuth
let authParams = [
"REFRESH_TOKEN": user.refreshToken,
"SECRET_HASH": config.getClientSecret()
]
let provider = AWSCognitoIdentityProvider(forKey: config.getClientSecret())
let req = AWSCognitoIdentityProviderInitiateAuthRequest()
req?.authFlow = AWSCognitoIdentityProviderAuthFlowType.refreshToken
req?.clientId = config.getClientId()
req?.authParameters = authParams
provider.initiateAuth(req!).continueWith() {
resp in
print("I'm not running")
if (resp != nil) {
print(resp.error.debugDescription)
} else {
print(resp.result.debugDescription)
}
return nil
}
Shockingly enough, the continueWith block of code doesn't even run at all, and the print statement "I'm not running" is not called. I'm at a loss of what to do, as this seems to be doing what they do in their SDK: https://github.com/aws/aws-sdk-ios/blob/master/AWSCognitoIdentityProvider/AWSCognitoIdentityUser.m#L173

The comment from #behrooziAWS solved this same issue for me.
Specifically, even though the initializer for AWSCognitoIdentityProvider(forKey:) is documented in XCode's quick help as:
+ (nonnull instancetype)CognitoIdentityProviderForKey:(nonnull NSString *)key;
it can (and it will) return nil if the CognitoIdentityProvider for the provided key is not found. One case this happens is when AWSCognitoIdentityProvider.register(with:forKey:) was never called for the provided key.
It's even more confusing when if(provider != nil) produces a compiler warning that it will always be true. I had to use the following code snippet to get things working without warnings:
let provider: AWSCognitoIdentityProvider? = AWSCognitoIdentityProvider(
forKey: config.getClientSecret())
guard provider != nil else {
fatalError("AWSCognitoIdentityProvider not registered!")
}
P.S. I hope someone with more experience in Swift+ObjC can comment on why the initializer shows up or is translated as nonnull instancetype even though the Objective C source code only has instancetype.
Edit: The reason why this initializer (and pretty much everything else in the AWSCognitoIdentityProvider/AWSCognitoIdentityUser.h file) is annotated with nonnull automatically is because of the NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END macros. In the case of CognitoIdentityProviderForKey, this non-null assumption should not be valid.

Related

use of restorePreviousSignIn() in googleSign in iOS

Google migration doc here says that
// Old:
guard let signIn = GIDSignIn.sharedInstance() else { return }
if (signIn.hasAuthInKeychain()) {
signIn.signInSilently()
}
// New:
guard let signIn = GIDSignIn.sharedInstance() else { return }
if (signIn.hasPreviousSignIn()) {
signIn.restorePreviousSignIn()
// If you ever changed the client ID you use for Google Sign-in, or
// requested a different set of scopes, then also confirm that they
// have the values you expect before proceeding.
if (signIn.currentUser.authentication.clientID != YOUR_CLIENT_ID
// TODO: Implement hasYourRequiredScopes
|| !hasYourRequiredScopes(signIn.currentUser.grantedScopes)) {
signIn.signOut()
}
}
As I had tried with device user still get redirect to account.google.com and have to choose the account right? so what is use of restorePreviousSignIn(). How it benefit to user? Thanks in advance.
Do you set the GIDSignInDelegate (GIDSignIn.sharedInstance().delegate) before calling restorePreviousSignIn()?
As the documentation of restorePreviousSignIn() says:
The delegate will be called at the end of this process indicating success or failure. The current values of GIDSignIn's configuration properties will not impact the restored user.
Btw. I'm using the restorePreviousSignIn() without any trouble.
// Sorry for asking in answer, I don't have enough reputation to comment on you question.

"provideCredentialWithoutUserInteractionForIdentity:" is not working

I have an app that autofills the password on iOS 12. Now I want to implement this function:
- (void)provideCredentialWithoutUserInteractionForIdentity:(ASPasswordCredentialIdentity *)credentialIdentity;
But I cant get it to work like it should.
AutoFill is enabled in settings and also enabled for my app.
I've read the documentation but this doens't help me. https://developer.apple.com/documentation/authenticationservices/ascredentialproviderviewcontroller/2977554-providecredentialwithoutuserinte?language=objc
I've tried calling this function from viewDidLoad, prepareCredentialListForServiceIdentifiers,.. but this is stupid and definitely won't work.
- (void)provideCredentialWithoutUserInteractionForIdentity:(ASPasswordCredentialIdentity *)credentialIdentity {
ASPasswordCredential *credential = [[ASPasswordCredential alloc] initWithUser:#"theUsername" password:#"thePassword"];
[self.extensionContext completeRequestWithSelectedCredential:credential completionHandler:nil];
}
The function should show the credentials above the keyboard, but this doesn't happen. It just shows the default "Passwords" button.
Make sure you have some ASPasswordCredentialIdentitys for your domain/url in ASCredentialIdentityStore. (These are records with some unique recordIdentifier, that doesn't hold password but hold data that can help you decide what password you should take from some secure storage of your choice.)
When you open a website, iOS looks up the ASCredentialIdentityStore, and shows a big button for quick login if there's a matching record. If you hit the button - this is only when provideCredentialWithoutUserInteraction callback is executed. Your task is to work with ASPasswordCredentialIdentity passed as an argument (it has recordIdentifier field) and find matching password for it (in your database/keychain/etc.) When you have password - you create ASPasswordCredential and pass it to self.extensionContext.completeRequest. Also make sure to call extensionContext.cancelRequest in case of any errors.
here's my example
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
let databaseIsUnlocked = false
if (databaseIsUnlocked) {
// this function queries my custom keychain records by recordIdentifier (Generic Passwords) to find matching password
getItemForRecordId(identifier: credentialIdentity.recordIdentifier) { password, err in
guard password != nil else {
print("password was nil")
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
return;
}
let passwordCredential = ASPasswordCredential(user: credentialIdentity.user, password: password as! String);
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil);
};
} else {
self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue))
}
}

In Swift-iOS How I catch User Authentication erros in AWS Cognito?

I'm trying aws cognito user pool and got stacked in the user sign up process. I already configured my user pool and are executing the sign-up method, but I can find a way to get the error code returned by aws services. Here my user pool instantiation, that is working fine:
let poolConfig = AWSCognitoIdentityUserPoolConfiguration(
clientId: userPool_clientId,
clientSecret: userPool_secret,
poolId: userPool_id)
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPool(with: poolConfig,
forKey: userPoll_App)
userPool = AWSCognitoIdentityUserPool(forKey: userPoll_App)
Then, in my view controller I have a Button whit a #IBAction with this:
if userPool != nil {
let attName = AWSCognitoIdentityUserAttributeType()!
attName.name = "name"
attName.value = userNome
let attLast = AWSCognitoIdentityUserAttributeType()!
attLast.name = "family name"
attLast.value = userSobrenome
let attEmail = AWSCognitoIdentityUserAttributeType()!
attEmail.name = "email"
attEmail.value = userEmail
var result:Bool = false
userPool!.signUp(userNome,
password: userPwd,
userAttributes: [attName, attLast, attEmail],
validationData: nil).continue({(task:AWSTask!) in
if (task.error != nil) {
print (task.error!)
result = false
} else {
result = true
}
return nil
})
After that code, I test the result to see if it is true or false and take the appropriate action. But...
I'm having different errors in this process and I need to evaluate this errors in development time. For example, the first error that I got was because I misconfigured the AWS region. Ok! Game on!! But the second error was because the password informed by the user did not passed the validation of the pool. In this case, I want to know the error was because the validation process and inform the user to take the appropriate action. I do not want to have this logic in the iOS app. The task.error object just give a localized description property and it is not very helpful.
By the way: I'm using Swift 3.2, iOS 10.2, aws-ios-sdk2 and Xcode 8.
I would like to expand on behrooziAWS's answer.
In Swift 4 you can match the error code with enums like AWSCognitoIdentityProviderErrorType.TheErrorType.rawValue.
Here's a tip for searching your error type, just type "AWSErrorType" and Xcode's autocomplete would show all the enums and then you can look through them.
Here's a code I use.
AWSobject.AWSfunction().continueWith { task -> Any? in
if let err = task.error as NSError? {
switch err.code {
case: AWSCognitoIdentityProviderErrorType.userNotFound.rawValue:
// Handle case
default:
// Handle all other cases here
return nil
}
// Do stuff on success!
}
task.error.code will contain a code you can compare to values in this enum. Look here for the particular error codes that can be returned by SignUp.

Firebase and Swift 3 Asynchronous Unit Testing

So I've done some research online and the best answers I've found are either outdated or intended for Android. Any help is appreciated!
For another project I did within the last week, I had to write about 2 dozen test cases for a custom Heroku/PostGreSQL backend with Swift 2.3. All I did was create an asyncExpectation variable at the beginning of the test, fulfill the expectation after the completion handler executed, and then wait for it to be fulfilled at the bottom of the test.
Now I'm using Firebase and Swift 3 for a new project. For some reason, nothing gets added to the database when I uncomment the asyncExpectation.fulfill() below. With it commented out, everything works fine. How should I go about testing data storage/retrieval with Firebase and Swift 3?
Edit: I should include that amidst my researching online, I uncovered that I may need to use dispatching, but this seems like a solution for nested completionHandlers, whereas my database append method isn't quite a completionHandler, so I'm at a loss for how to proceed.
class RooMate_v2Tests: XCTestCase {
func testCreateUser() {
let asyncExpectation = expectation(description: "createUserTestOne")
var testSuccess = false
let testUser = (email: "TESTUSER|" + String().randomString(length: 6) + "#test.com", password: String().randomString(length: 6), firstName: "jimmy", lastName: "jenkins")
FIRAuth.auth()?.createUser(withEmail: testUser.email, password: testUser.password) { (user, error) in
if error != nil {
print("Error: \(error.debugDescription)")
} else {
let userProfileInfo = ["firstName" : testUser.firstName,
"lastName" : testUser.lastName,
"email" : testUser.email,
"profilePictureURL" : "N/A"]
let backendRef = FIRDatabase.database().reference()
backendRef.child("users").child("TESTUSER|" + user!.uid).setValue(userProfileInfo)
// testSuccess = true
}
// asyncExpectation.fulfill()
}
waitForExpectations(timeout: 10) { (error) in
XCTAssertTrue(testSuccess)
}
}
}
in my opinion, your problem is that you are checking the success of the setValue in the handler of the wait. As described in the documentation, the handler is called only in case of time out:
A block to be invoked when a call to
-waitForExpectationsWithTimeout:handler: times out or has had all associated expectations fulfilled.
I suggest you to retrieve the value just set and check it, something like this:
let retrievedUserProfile = backendRef.child("users").child("TESTUSER|" + user!.uid)
XCTAssertTrue(retrievedUserProfile == userProfileInfo)
I don't know if this code is correct, it is just to explain my suggestion.
I hope it's helpful!

Cognito getIdentityId() does not work in swift for unauthenticated user

I have the following code for getting the Cognito ID via unauthenticated user in swift for an IOS app:-
import Foundation
import AWSCore
import AWSCognito
class CognitoInit {
func getCognitoIdentityId() -> String {
let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.USEast1,
identityPoolId:"My_idenitity_pool_id_in_aws")
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
credentialsProvider.getIdentityId().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
return task.error?.localizedDescription
}
else {
// the task result will contain the identity id
return task.result
}
return nil
}
return "DEFAULT_COGNITO_ID"
}
}
when getCognitoIdentityId() in a stub ViewController is called it always returns "DEFAULT_COGNITO_ID"(I am running it in an emulator). Stepping into the code via a debugger reveals that the async task does not run inside (the entire code block from if (task.error != nil) { to return nil is bypassed.
Am I missing something. Things that I can think of
Are there permissions needed for network/async calls that must be separately enabled?
Are there configurations needed in Cognito Identity Pool that I must do.
Are async tasks blocked in emulators?
Please help. I am new to swift development.
It sounds like it might just be the asynchronous nature of that call in play. Can you try the example exactly as described in the documentation, wait a few seconds, then try to get the identity id via
credentialsProvider.identityId
and see what happens? Since it is asynchronous, having it setup in this way won't work the way you're hoping, I think. You'll need to initiate the async call beforehand.

Resources