Testing passwordless auth in Firebase test lab for iOS - ios

I am trying to figure out how to perform e2e test via firebase test lab for iOS that allow to check passwordless authentication flow, which essentially should do following
Enters email within my app
Firebase sends auth link to such email
Somehow I need to be logged into such email somewhere in firebases test device, I assume either in mail app, or gmail?
I need to know when new email arrives and open it
Once I opened an email I need to click on auth link
This should bring me back into the app and authenticate
My biggest issue at the moment is figuring out steps that happen outside my app i.e. how can I prepare for this test and log in under my email address (is it better to log into gmail in safari for example or somehow add this acc to apples mail app?).

Testing email
In my experience, testing your own code to see if an email was sent is not straightforward beyond checking if the method call you expect to send the email has happened.
Add on top of that using Firebase, which does not expose its underlying email send code, and that looks like a challenge to me.
In terms of testing, I suggest you assert that your method calls to send email happened or that the relevant code path was reached. In Firebase web, this looks like:
firebase.auth().sendSignInLinkToEmail(email, actionCodeSettings)
.then(function() {
// The link was successfully sent. Inform the user.
// Save the email locally so you don't need to ask the user for it again
// if they open the link on the same device.
window.localStorage.setItem('emailForSignIn', email);
// TODO save email to something accessible in your iOS tests
// TODO In your tests, confirm that email was saved after it was sent
})
.catch(function(error) {
// Some error occurred, you can inspect the code: error.code
});
See: https://firebase.google.com/docs/auth/web/email-link-auth#send_an_authentication_link_to_the_users_email_address
Another option:
You could setup a test user with an email address on a mail server that you manage, and check for incoming mail for that test user with your own custom mail reading code.
I would use Firebase Admin tools for this: https://firebase.google.com/docs/auth/admin/manage-users#create_a_user

I think you should first take a look at firebase docs for iOS on how to create dynamic links that you can use for email auth.
https://firebase.google.com/docs/auth/ios/email-link-auth
https://firebase.google.com/docs/auth/ios/passing-state-in-email-actions#configuring_firebase_dynamic_links
After you're done with those two check out the following code:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// [END old_delegate]
if handlePasswordlessSignIn(withURL: url) {
return true
}
}
func handlePasswordlessSignIn(withURL url: URL) -> Bool {
let link = url.absoluteString
// [START is_signin_link]
if Auth.auth().isSignIn(withEmailLink: link) {
// [END is_signin_link]
UserDefaults.standard.set(link, forKey: "Link")
(window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: false)
window?.rootViewController?.children[0].performSegue(withIdentifier: "passwordless", sender: nil)
return true
}
return false
}
This is just an example on how you can handle the deep link in your app after the user taps the link. The delegate method
func application(_ application: UIApplication, open url: URL,
sourceApplication: String?, annotation: Any) -> Bool
in AppDelegate is used for all deep links into an app. You could set up for example your own scheme that your app conforms to. And you can send url type links with your custom scheme into your app from the browser for example.
To do this just Open Xcode, go to Project Settings -> Info, and add inside ‘The URL Types” section a new URL scheme. Add something of the sort of com.myApp in order for it to be as unizue as possible. Then you can just type into a browser com.myApp://main and handle that in the appDelegate.
Edit: It says so in their docs that you can present a prompt inside the app for the user to input the email. Where the user opens his email from isn't really your concern as long as your dynamic link is set up properly.

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

How to fetch the appID without using Branch SDK iOS?

I am working in iOS swift project. I have integrated branch for deep link activity. In the branch link we have appended the appID and retrieved the id when the user clicked the app link using below code in didFinishLaunch in AppDelegate.
let branch: Branch = Branch.getInstance()
branch.initSession(launchOptions: launchOptions, automaticallyDisplayDeepLinkController: true, deepLinkHandler: { params, error in
if error == nil {
// parse appID
}
})
But sometimes, we can’t fetch the ID due to branch error. So we planned to remove branch integration in our code. But plist contains URL Scheme, URL identifier etc.. for navigate to this app.
So, When the user clicks the app link, how to fetch the value with out using above code?
Additional note:
The below function is not triggered in my app while clicking the app link. Is any other way to trigger this function, so that I can check the url value in the below function to processing further.
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
}

How Can I Unit Test Calling of iOS Application Delegate Methods?

I have an iOS application that integrates with the GitHub API. I am unit testing my OAuth requests, which requires testing the receipt of a code from the GitHub API that I will use to exchange for a token.
In my AppDelegate.swift, I have the following method, which is used to handle the callback from GitHub when the user authorizes my application to use their GitHub account:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return true
}
The steps are as follows:
Open the application.
Using the URL for authorizing GitHub account access (https://github.com/login/oauth/authorize), an instance of SFSafariViewController is presented, allowing the user to press the 'Authorize' button.
GitHub uses the callback URL to my application that I provided when registering my application with GitHub, which sends a notification to open my app.
The method above is executed, where I retrieve the code parameter from url.
However, I am stuck trying to find a way to test this without actually making a request to the GitHub API. I can create a URL instance that mimics what GitHub supplies my application with, but I would like to test this without making an actual request.
Is there a way to unit test this, or is this something that I shouldn't worry about since it's handled by the OS, and instead, only test my code for parsing the code parameter of a test URL?
UPDATE
After taking Jon's advice, I created a test class to allow me to simulate the GitHub callback in action:
class GitHubAuthorizationCallbackTests: XCTestCase {
let delegate = AppDelegateMock()
func test_AuthorizationCallbackFromGitHub_ApplicationOpensURL() {
guard let url = URL(string: "xxxxxxxxxxxxxx://?code=********************") else { return XCTFail("Could not construct URL") }
let isURLOpened = delegate.application(UIApplication.shared, open: url)
XCTAssertTrue(isURLOpened, "URL is not opened from GitHub authorization callback. Expected URL to be opened from GitHub authorization callback.")
}
}
Then, I created AppDelegateMock.swift to be used instead of AppDelegate.swift, adding in the intended method to be called when the GitHub callback is executed to open my app:
import UIKit
class AppDelegateMock: NSObject, UIApplicationDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
return true
}
}
The test passes, allowing me to test the logic that I need to test for handling the code parameter that is returned from GitHub int he url parameter of the method.
Because you want to test a callback… just have tests invoke the callback directly, as if the GitHub framework invoked it.
Write simple tests around the happy path. Then, because you're dealing with external data you can't control, write tests that (if Swift allows) invoke the callback with strange options.

Branch clicked_branch_link is 0 without metadata Swift

Trying to set up branch for deep linking. In settings for the Branch control panel the link domain is set to: get.myapp.co. For iOS the URI Scheme is: myapp://. Under the Associated Domains section of entitlements I have:
applinks:get.myapp.co
applinks:get-alternate.myapp.co
I have my AppDelegate.swift setup as described by the guide.
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let branch = Branch.getInstance()
branch?.initSession(launchOptions: launchOptions) { (params, error) in
//We can handle links when an app is launched/open here
if let error = error {
print("Branch error: \(error.localizedDescription)", logType: .Error)
} else {
print("Branch params: \(params.description)")
}
}
}
Now I have a marketing ink setup in the control panel with the key: redirect and value subscription. When I tap on the link I output I get is:
Branch params: [AnyHashable("+clicked_branch_link"): 0, AnyHashable("+is_first_session"): 0, AnyHashable("+non_branch_link"): http://get.myapp.co/xmas-special-offer]
So +clicked_branch_link is 0. My metadata with the key redirect is also missing. What am I doing wrong here?
EDIT:
Note I was pasting the link from the control panel into an email and then opening it from the mail app, this doesn't work. BUT if I send the link from the control panel via sms the metadata appears and clicked_branch_link is 1. What gives?
Spent a lot of time trying to solve the same issue.
My problem was caused by opening test link while using live environment.
For using test environment I had to get branch singletone like:
Branch *branch = [Branch getTestInstance];
Alex from Branch here: the +clicked_branch_link: 0 param means that the SDK is not detecting any Branch link click for that device prior to the app launching. This is why you're not getting your metadata back. The presence of +non_branch_link in the return indicates some configuration issues, which are probably causing the failure to detect a Branch link click. Based on some of the details in your question, I think there may be some confusion around how to configure your custom domain and your URI scheme.
Without seeing the specific configuration in your dashboard, this is tough to diagnose. Could you either edit your question with more details (example links, screenshots of the dashboard, etc), or submit a ticket with the Branch integrations team for further debugging?

how to send verification code by sms in swift 2

i build a register form for my app and i need to send the user a verifiation code by sms in order to complete the registration proccess.
i tried to use MFMessageComposeViewController but its open the dialog sms on the device so the user can see the code.
i also checked the web for 3party of sending sms but there is a problem with the country code. i know its posible becuse whatsapp do it to confirm the user.
what it the right way to do it?
this is the topic the i tried:
Sending SMS in iOS with Swift
The best way to achieve this is by creating some views for allowing the user to enter the phone number with the country code which can be used by a server to send a request for initiating the OTP verification. To achieve this you need to:
Create View Controllers.
Upload Phone Number and Country code to the server.
Validate the requests by verifying the OTP.
As mentioned by Dan, you can use Digits in Fabric for that purpose, and create custom views for a great UX.
On the other hand, you can also use a service called as SendOTP from MSG91 - you can use it for internal testing and development ideas as they provide you with 5,000 free OTP SMS. The service has a complete set of APIs which you can implement on the backend as well on the app front. Also, they provide a framework so that you don't need to create the views, but only presentViewController and call delegate methods for knowing what happened during the verification process - such as Cancelled or Verified or Failed.
Swift implementation of the same looks like this:
class OTPFrame: UIViewController, sendOTPAuthenticationViewControllerDelegate {
func loadOTPFramework() {
SendOTP.sharedManager().startWithApiId("yourAppID")
let frameworkPath: NSString = NSBundle.mainBundle().privateFrameworksPath!
let frameworkBundlePath: NSString = frameworkPath.stringByAppendingPathComponent("SendOTPFramework.framework")
let frameworkBundle : NSBundle
= NSBundle(path: frameworkBundlePath as String)!
let authenticationViewController: AuthenticationViewController = AuthenticationViewController(nibName: "AuthenticationViewController", bundle: frameworkBundle)
authenticationViewController.delegate = self self.presentViewController(authenticationViewController, animated: true, completion: nil)
}
func authenticationisSuccessfulForMobileNumber(mobNo: String!, withCountryCode countryCode: String!) {
print("Success")
}
func canceledAuthentication() {
print("Cancelled")
}
func authenticationisFailedForMobileNumber(mobNo: String!, withCountryCode countryCode: String!) {
print("Failed")
}
}
Disclaimer: I, in no way, endorse the services mentioned above - you are free to choose whatever you like.
Thank You!
I would give digits a try! It's part of the Twitter Fabric package and it's very simple to use. The user enters their phone number and Fabric takes care of validating the number.

Resources