Get user attributes AWS Amplify iOS SDK - ios

I'm building an iOS app with the SDK Amplify so I have my users registered on AWS.
I already have my sign in/sign up flow working but the problem is that with the newest version of the SDK I have absolutely no idea of how can I get attributes of a registered user like his family name, email address etc...
With this new SDK everything seems to work around the AWSMobileClientclass but I see nothing from this class that can help me to get what I want.
The official documentation is anemic and doesn't cover or even point to my use case.
If somebody can give me some hint or even some good ressources I'll be very thankful!

Hi YoanGJ and future guests,
Based on your comment you were looking for some sample code.
AWSMobileClient.sharedInstance().getUserAttributes { (attributes, error) in
if let attributes = attributes {
XCTAssertTrue(attributes.count == 3, "Expected 3 attributes for user.")
XCTAssertTrue(attributes["email_verified"] == "false", "Email should not be verified.")
}else if let error = error {
XCTFail("Received un-expected error: \(error.localizedDescription)")
}
getAttrExpectation.fulfill()
}
This excerpt shows how you could call getUserAttributes and it comes from the integration tests found here.

The method was missing from the initial release and has since been added. You can use the getUserAttributes with the following API in the latest SDK version 2.8.x:
public func getUserAttributes(completionHandler: #escaping (([String: String]?, Error?) -> Void))
You can find the source code here:
https://github.com/aws-amplify/aws-sdk-ios/blob/master/AWSAuthSDK/Sources/AWSMobileClient/AWSMobileClientExtensions.swift#L532
Thanks,
Rohan

For a note:
Make sure you did configure attribute read and write permission accordingly in your Cognito user pool App client to access your user attributes using getUserAttributes.
To configure attributes read and write permissions in user pool,
User pool -> General settings -> App clients -> Choose your app client
-> Show details -> Set attribute read and write permissions
Thanks!

Related

Firebase Dynamiclinks is unable to parse universal link

I have an app on TestFlight that I'm trying to setup passwordless signin using emailid from FireBase.
I've done the following:
Enabled Passwordless sign in on Firebase console
Created a Dynamic link on Firebase.
httpsdotslashslash<custom domain>/login/?link=https://<custom domain>/login&isi=<app id from itunes connect. Looks like this: 167*******>&ibi=<bundleid>
Confirmed that https://<custom domain>/apple-app-site-association returns correctly. Note that I'm using Firebase hosting to host this site. And have confirmed that https://<firebase-projectid>.firebase.app/apple-app-site-association also returns the above. i.e. custom domain is correctly resolving to this firebase hosting url.
In Info, Added a url type with identifier 'appLink' and 'url scheme'
Ensured that my GoogleService-Info is current
Enabled Associated Domains in 'Signing and Capabilities' in my project Target under 'All' (not just Debug or Release)
Added applinks:<customdomain> and activitycontinuation:<customdomain>
In my 'SceneDelegate', I'm handling the dynamic link with
private func handleIncomingDynamicLink(_ incomingURL: URL) {
DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { dynamicLink, error in
guard error == nil else {
return print("ⓧ Error in \(#function): \(error!.localizedDescription)")
}
guard let link = dynamicLink?.url?.absoluteString else { return }
if Auth.auth().isSignIn(withEmailLink: link) {
// Save the link as it will be used in the next step to complete login
UserDefaults.standard.set(link, forKey: "Link")
}
}
}
In my viewcontroller, have copied the code from Firebase's quickstart PasswordlessViewController.swift as is.
Observed behavior:
The email link is being sent. Here's what it looks like though. Not sure if this is right.
httpsdotslashslash<custom domain>/?link=https://<firebase-projectid>.firebaseapp.com/__/auth/action?apiKey=<web api key>&mode=signIn&oobCode=25CRdL7n3qXNatDT2nEypR9UWy17E_G0ChlmqrkpznsAAAGGLYK3jQ&continueUrl=https://<custom domain>/login&lang=en&ibi=<bundleid>&ifl=https://<firebase-projectid>.firebaseapp.com/__/auth/action?apiKey=<web api key>&mode=signIn&oobCode=25CRdL7n3qXNatDT2nEypR9UWy17E_G0ChlmqrkpznsAAAGGLYK3jQ&continueUrl=https://<custom domain>/login&lang=en
Clicking on this url in my email correctly opens my app. handleIncomingDynamicLink() is also called correctly. But it throws the error ⓧ Error in handleIncomingDynamicLink(_:): The operation couldn’t be completed. Universal link URL could not be parsed by Dynamic Links.
Have tried a bunch of things.
But, unable to proceed beyond this stage. Not sure what is wrong with my link. What is it supposed to look like when behaving correctly? Should i be doing something with the oobCode? Why are there two redirections?
Happy to assist with any other information. Thank you!
I was able to solve it by adding all my url prefixes to info.plist.
<key>FirebaseDynamicLinksCustomDomains</key>
<array>
<string>https://<custom domain>/login</string>
<string>https://<custom domain>/</string>
</array>
More details at https://firebase.google.com/docs/dynamic-links/custom-domains#console

AWS Cognito check and get users

I'm building an iOS App that is using Amazon MobileHub. Currently it's associated with Cognito and the sign up and sign in flow works great.
What I'm trying to achieve is for one user to be able to add another user as a friend so to speak. To make that possible, I need to check if a user with a specific username exists and if it does, get some attributes such as the name of that target user.
I've tried to use the get user > get details function but it gives me an error Authentication delegate not set.
Here's the code I used:
var pool: AWSCognitoIdentityUserPool?
let user = pool?.getUser(usernameField.text!)
self.pool = AWSCognitoIdentityUserPool.init(forKey: AWSCognitoUserPoolsSignInProviderKey)
user?.getDetails().continueWith { (task: AWSTask<AWSCognitoIdentityUserGetDetailsResponse>) -> Any? in
if let error = task.error as NSError? {
print("ERROR: " + error.localizedDescription)
return ""
}
print(task.result)
return ""
}
An approach I thought of was to store the username and the attributes I want to access to DynamoDB and then access it there but that will just create double unnecessary entries.
The issue you'll run into is that user attributes aren't publicly visible. Only the user who has signed in can call GetUser. If it's a back end process, you could do this via the AdminGetUser operation, but this looks client side so I wouldn't recommend that. The only real way around this would be to do what you suggested at the bottom of your post, ultimately.

iOS: How to detect if a user is subscribed to an auto-renewable subscription

Hopefully the title is self-explanatory. I'm trying to do something like this:
checkIfUserIsSubscribedToProduct(productID, transactionID: "some-unique-transaction-string", completion: { error, status in
if error == nil {
if status == .Subscribed {
// do something fun
}
}
}
does anything like the hypothetical code I've provided exist? I feel like I'm taking crazy pills
Edit
In similar questions I keep seeing a generic answer of "oh you gotta validate the receipt" but no explanation on how, or even what a receipt is. Could someone provide me with how to "validate the receipt"? I tried this tutorial but didn't seem to work.
Edit - For Bounty
Please address the following situation: A user subscribes to my auto-renewable subscription and gets more digital content because of it - cool, implemented. But how do I check whether that subscription is still valid (i.e. they did not cancel their subscription) each time they open the app? What is the simplest solution to check this? Is there something like the hypothetical code I provided in my question? Please walk me through this and provide any further details on the subject that may be helpful.
I know everyone was very concerned about me and how I was doing on this - fear not, solved my problem. Main problem was that I tried Apple's example code from the documentation, but it wasn't working so I gave up on it. Then I came back to it and implemented it with Alamofire and it works great. Here's the code solution:
Swift 3:
let receiptURL = Bundle.main.appStoreReceiptURL
let receipt = NSData(contentsOf: receiptURL!)
let requestContents: [String: Any] = [
"receipt-data": receipt!.base64EncodedString(options: []),
"password": "your iTunes Connect shared secret"
]
let appleServer = receiptURL?.lastPathComponent == "sandboxReceipt" ? "sandbox" : "buy"
let stringURL = "https://\(appleServer).itunes.apple.com/verifyReceipt"
print("Loading user receipt: \(stringURL)...")
Alamofire.request(stringURL, method: .post, parameters: requestContents, encoding: JSONEncoding.default)
.responseJSON { response in
if let value = response.result.value as? NSDictionary {
print(value)
} else {
print("Receiving receipt from App Store failed: \(response.result)")
}
}
As some comments pointed out there's a couple flaws with these answers.
Calling /verifyReceipt from the client isn't secure.
Comparing expiration dates against the device clock can be spoofed by changing the time (always a fun hack to try after cancelling a free trial :) )
There are some other tutorials of how to set up a server to handle the receipt verification, but this is only part of the problem. Making a network request to unpack and validate a receipt on every app launch can lead to issues, so there should be some caching too to keep things running smoothly.
The RevenueCat SDK provides a good out-of-the box solution for this.
A couple reasons why I like this approach:
Validates receipt server side (without requiring me to set up a server)
Checks for an "active" subscription with a server timestamp so can't be spoofed by changing the device clock
Caches the result so it's super fast and works offline
There's some more details in this question: https://stackoverflow.com/a/55404121/3166209
What it works down to is a simple function that you can call as often as needed and will return synchronously in most cases (since it's cached).
subscriptionStatus { (subscribed) in
if subscribed {
// Show that great pro content
}
}
What are you trying to achieve in particular? Do you want to check for a specific Apple ID?
I highly doubt that this is possible through the SDK. Referring to Is it possible to get the user's apple ID through the SDK? you can see that you can't even ask for the ID directly but rather services attached to it.
What would work is caching all transactions on your own server and search its database locally but that would require the app to ask for the user's Apple ID so the app could update the subscription state whenever it launches as it can check for IAP of the ID associated with the device.
However, the user could just type whatever he wanted - and it's unlikely to get this through Apple's app review process.
I am using MKSoreKit https://github.com/MugunthKumar/MKStoreKit for auto-renew subscriptions.but it is in objective c you can check the library code for solution.I am using it in my code and it is working fine.
using below method you can easily check subscription status..
if([MKStoreManager isProductPurchased:productIdentifier]) {
//unlock it
}
It gets the apple id from device and I think that is user specific

Apple Store build rejected while using CloudKit/iCloud

I just submited my app to the Apple Store and it failed submission because of the following issue and I am quite confuse about how to work around it.
From Apple - 17.2 Details - We noticed that your app requires users to
register with personal information to access non account-based
features. Apps cannot require user registration prior to allowing
access to app content and features that are not associated
specifically to the user.
Next Steps - User registration that requires the sharing of personal
information must be optional or tied to account-specific
functionality. Additionally, the requested information must be
relevant to the features.
My app uses CloudKit to save, retrieve and share records. But the app itself do not ask for any personal details neither share any personal details like emails, names, date of birth..., it just asks the user to have an iCloud account active on the device. Then CloudKit uses the iCloud credentials in order to work.
It becomes confusing because:
1 - I can't change the way CloudKit works and stop asking for the user to login on iCloud. Every app that uses CloudKit needs an user logged on iCloud.
2 - As other apps (facebookas an example) if you do not login the app cannot fundamentally work. So the login is not tied to specific functionality, but to the whole functionality of the app.
The code example bellow is called on an initial screen (before getting inside the app functional areas) every time the app starts to make sure the user has the iCloud going. If the user has iCloud I take him inside the app. If not I stop him and ask him to get iCloud sorted. But I guess that is what they are complaining about here - "User registration that requires the sharing of personal information must be optional or tied to account-specific functionality. Additionally, the requested information must be relevant to the features.".
Which puts myself in a quite confusing situation. Not sure how to resolve the issue. Has anyone has similar issues with CloudKit/iCloud/AppStore Submission? Any insights?
iCloud check code bellow:
func cloudKitCheckIfUserIsAuthenticated (result: (error: NSError?, tryAgain: Bool, takeUserToiCloud: Bool) -> Void){
let container = CKContainer.defaultContainer()
container.fetchUserRecordIDWithCompletionHandler{
(recordId: CKRecordID?, error: NSError?) in
dispatch_async(dispatch_get_main_queue()) {
if error != nil
{
if error!.code == CKErrorCode.NotAuthenticated.rawValue
{
// user not on icloud, taki him there
print("-> cloudKitCheckIfUserIsAuthenticated - error fetching ID - not on icloud")
// ERROR, USER MUST LOGIN TO ICLOUD - LOCK HIM OUTSIDE THE APP
result(error: error, tryAgain: false, takeUserToiCloud: true)
}
print("-> cloudKitCheckIfUserIsAuthenticated - error fetching ID - other error \(error?.description)")
// OTHER ERROR, TRY AGAIN
result(error: error, tryAgain: true, takeUserToiCloud: false)
}
else
{
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
publicDatabase.fetchRecordWithID(recordId!,
completionHandler: {(record: CKRecord?, error: NSError?) in
if error != nil
{
// error getting user ID, try again
print("-> cloudKitCheckIfUserIsAuthenticated - error fetching user record - Error \(error)")
// ERROR, TRY AGAIN
result(error: error, tryAgain: true, takeUserToiCloud: false)
}
else
{
if record!.recordType == CKRecordTypeUserRecord
{
// valid record
print("-> cloudKitCheckIfUserIsAuthenticated - fetching user record - valid record found)")
// TAKE USER INSIDE APP
result(error: error, tryAgain: false, takeUserToiCloud: false)
}
else
{
// not valid record
print("-> cloudKitCheckIfUserIsAuthenticated - fetching user record - The record that came back is not a user record")
// ERROR, TRY AGAIN
result(error: error, tryAgain: true, takeUserToiCloud: false)
}
}
})
}
}
}
}
Initially my application would ask the user to login to iCloud on launch screen. If the users did not have an iCloud account functional they would not be able to get inside the app.
Solution
Let the user get inside the app and click on the main sections. In fact the app was completely useless but the user could see it's odd empty screens without the ability to save or load anything. By the time they tried to load or save things I would prompt them the needed to login on iCloud to make the app usable.
Practical outcome
I don't think apple's change request added anything of value to the UX. In fact it just added complexity for the user to understand what he can do and what he cannot.
As an example Facebook locks the user outside if the user do not provide his personal details because without this data the application has absolutely no use and that is my case... You could arguably say the user should be able to get inside, but what he would see or do? Then you would have to cater for all the exceptions this UX builds and throw warnings for the user to fix the account issue everywhere on an annoying pattern of warnings.
So I am not sure "how" Facebook could get it approved and I could not.
Although I got the app approved I disagree Apple feedback improved the application in any way.

iOS OneDrive (skydrive) app displays permissions dialog every time it runs

I'm developing an iOS app that gives users access to their OneDrive/SkyDrive and I've run into a very annoying issue:
The very first time a user links the app to their OneDrive, everything goes as expected:
They have to enter a user id and password
Then they have to agree to let the app access their info
Then they get to browse their OneDrive
That's all good.
But, if the app closes, and you try to access the OneDrive again, rather than skipping straight to #3, and being able to access the OneDrive, they are stopped at step #2 (step 1 is skipped, as expected) and they have to agree again to let the app access their info.
The code is taken directly from the iOS examples in the online documentation (with some slight modification based on samples found here on Stack Overflow), but, here it is for inspection:
- (void) onedriveInitWithDelegate:(id)theDelegate {
self.onedriveClient = [[LiveConnectClient alloc] initWithClientId:MY_CLIENT_ID
delegate:theDelegate
userState:#"initialize"];
}
And then, theDelegate implements this:
- (void)authCompleted:(LiveConnectSessionStatus) status
session:(LiveConnectSession *) session
userState:(id) userState {
NSLog(#"Status: %u", status);
if ([userState isEqual:#"initialize"]) {
NSLog( #"authCompleted - Initialized.");
if (session == nil) {
[self.onedriveClient login:self
scopes:[NSArray arrayWithObjects:#"wl.basic", #"wl.signin", #"wl.skydrive_update", nil]
delegate:self
userState:#"signin"];
}
}
if ([userState isEqual:#"signin"]) {
if (session != nil) {
NSLog( #"authCompleted - Signed in.");
}
}
}
I thought that perhaps the status value might give a clue and that maybe I could avoid the login call, but it's always zero/undefined when I get to authCompleted after calling initWithClientId. (And session is always nil.)
Is there a scope I'm missing? Is there a different call to make rather than a straight-up login call? Or is it more complicated than that? I've seen reference to "refresh tokens" related to OAuth2 login, but I've not been able to find any concrete examples of how they might be used in this situation.
Any help and/or insights greatly appreciated.
Diz
Well, it turns out that the answer is pretty simple here. I just needed to add the "wl.offline_access" scope to my list of scopes during the initial login operation. The docs didn't really imply this type of behavior for this scope, but, that did the trick for me.
With this new scope added, subsequent invocations of the app no longer bring up the "agree to give the app these permissions" dialog, and I can go straight to browsing the OneDrive.
(Credit where it's due: Stephane Cavin over at the microsoft forums gave me the tip I needed to work this out. Gory details are here:
http://social.msdn.microsoft.com/Forums/en-US/8c5c7a99-7e49-401d-8616-d568eea3cef1/ios-onedrive-skydrive-app-displays-permissions-dialog-every-time-it-runs?forum=onedriveapi )
Diz

Resources