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

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.

Related

Stuck with Api response Ktor

I am trying to build a KMM application using Ktor for our ApiServices. I have created a BaseApiClass where I have all of the api related code.
Code for BaseApiClass :-
class BaseAPIClass {
//Create Http Client
private val httpClient by lazy {
HttpClient {
defaultRequest {
host = ApiEndPoints.Base.url
contentType(ContentType.Application.Json)
header(CONNECTION, CLOSE)
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
install(HttpTimeout) {
requestTimeoutMillis = NETWORK_REQUEST_TIMEOUT
}
expectSuccess = false
// JSON Deserializer
install(JsonFeature) {
val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
}
serializer = KotlinxSerializer(json)
}
}
}
// Api Calling Functions I have few more similar to this but issue is random and comes in any of the api
#Throws(Exception::class)
suspend fun sampleApi(requestBody: RequestBody?) : Either<CustomException, BaseResponse<EmptyResponseModel>> {
return try {
val response = httpClient.post<BaseResponse<EmptyResponseModel>> {
url(ApiEndPoints.sample.url)
if (requestBody != null) {
body = requestBody
}
}
Success(response)
}
catch (e: Exception) {
Failure(e as CustomException)
}
}
Here's how I call the api from iOS app :-
val apiClass = BaseApiClass()
func callApi() {
apiClass.sampleApi(requestBody: .init(string: "value here")) { (result, error) in
result?.fold(failed: { (error) -> Any? in
// Error here
}, succeeded: { (result) -> Any? in
// Success here
})
}
}
Now here if I try to call similar few more api's with the same object i.e apiClass then after few calls it get stuck inside my function callApi it don't send even api request (Because I can't see Request Logs printed in my console) and because of that I cannot do any other operations as I don't get anything from api.
As soon as I change my screen or close the app and try to call the same api then it works good.
But instead of creating a object only at one time like this apiClass = BaseApiClass() if I try to do with BaseApiClass().sampleApi(request params here) {// completion handler here} it works fine I don't get any issues with this.
I am not sure what causes this to happen everything works good in Android this is faced only with iOS.
Try to set LogLevel.NONE in the install(Logging) block.
At the moment I resolved in this way because it seems a bug of Ktor.
See: https://youtrack.jetbrains.com/issue/KTOR-2711
It should be fixed in the version 1.6.0.
Are you using the multithreaded variant of the Coroutines library? The official docs state that you should use this variant when working with Ktor. See here
After all the efforts and trying a lot of debugging skills I got to understand that my completion handler in the shared module is never called even if I receive the response the response from api.
The only solution I have achieved is creating the different HTTP Client using expect and actual mechanism. By making separate clients I have not encountered the issue yet.
If you have any other answers or solutions I would be happy to have a look at it.

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)
}

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

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.

How to authenticated Google Cloud Endpoint call from an iOS client

My google backend APIs are using Oauth 2.0, when i call one of my api with that code :
func userService() -> GTLServiceUserController{
var service : GTLServiceUserController? = nil
if service == nil {
service = GTLServiceUserController()
service?.retryEnabled = true
}
return service!
}
And
func callApi() {
let service = userService()
let query = GTLQueryUserController.queryForGetAllUsers()
service.executeQuery(query) { (ticket: GTLServiceTicket!, object: AnyObject!, error: NSError!) -> Void in
if(error != nil){
print("Error :\(error.localizedDescription)")
return
}
let resp = object as? GTLUserControllerOperationResponse
if(resp != nil){
print(resp)
}
}
}
I'm getting The operation couldn’t be completed. (com.google.appengine.api.oauth.OAuthRequestException: unauthorized)
I already read this article, but i don't want my users to log in, instead i want my app to present its credentials to the service for authentication without user interaction.
I want to use this approach, but they are not telling how to do it with iOS apps.
So basically i want to be able to call my APIs successfully without any user interaction or sign-in dialog.
Please note that i have those classes on my project :
GTMOAuth2Authentication
GTMOAuth2SignIn
GTMOAuth2ViewControllerTouch
Thank's in advance
If you don't need to authenticate individual users (via OAuth2 login), then you do not need to do anything else and your App can directly call the API (as described in the "Calling the Endpoint API (unauthenticated)" section).
Service accounts are for server-to-server interactions and cannot be used to authenticate individual users; if you embed a service account & key within your iOS App, anyone can extract it from the App and start calling your API, hence it does not add any security.

how to correctly make cloud code request with parse?

I am attempting to make all my user sessions with Parse exclusive, meaning if a user is already logged in on a certain device in a certain location, if another device logs in with the same credentials, I want the previous session(s) to be terminated, with a message of an alert view of course. Sort of like the old AOL Instant Messaging format. I figured the code for this action should be written in the login logic, so I wrote this within my login "succession" code :
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if user != nil || error == nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSuccess", sender: self)
PFCloud.callFunctionInBackground("currentUser", withParameters: ["PFUser":"currentUser"])
//..... Get other currentUser session tokens and destroy them
}
} else {
Thats probably not the correct cloud code call, but you get the point. When the user logs in once again on another device, I want to grab the other sessions and terminate them. Does anyone know the correct way to go about making this request in swift?
I speak swift with a stutter, but I think I can answer adequately in almost-swift. The key idea is to start the success segue only after the cloud says it's okay. Here's what I think you want:
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if (user != nil) {
// don't do the segue until we know it's unique login
// pass no params to the cloud in swift (not sure if [] is the way to say that)
PFCloud.callFunctionInBackground("isLoginRedundant", withParameters: []) {
(response: AnyObject?, error: NSError?) -> Void in
let dictionary = response as! [String:Bool]
var isRedundant : Bool
isRedundant = dictionary["isRedundant"]!
if (isRedundant) {
// I think you can adequately undo everything about the login by logging out
PFUser.logOutInBackgroundWithBlock() { (error: NSError?) -> Void in
// update the UI to say, login rejected because you're logged in elsewhere
// maybe do a segue here?
}
} else {
// good login and non-redundant, do the segue
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
}
} else {
// login failed for typical reasons, update the UI
}
}
Please don't take me too seriously on swift syntax. The idea is to nest the segue in the completion handlers to know that you need to do it before starting it. Also, please note that the explicit placement on the main_queue within the completion handler is unnecessary. The SDK runs those blocks on the main.
A simple check to determine if a user's session is redundant (not unique) looks like this...
Parse.Cloud.define("isLoginRedundant", function(request, response) {
var sessionQuery = new Parse.Query(Parse.Session);
sessionQuery.equalTo("user", request.user);
sessionQuery.find().then(function(sessions) {
response.success( { isRedundant: sessions.length>1 } );
}, function(error) {
response.error(error);
});
});

Resources