Firebase Phone Authentication Using Multiple Authentication Providers - ios

Introduction
We've been having trouble using Firebase Auth configured with multiple authentication providers.
The documentation specifies that an application can support multiple firebase projects.
Email Sign In
We have been able to successfully configure our application so that email authentication works with multiple authentication providers.
For example:
// Configure secondary app
FirebaseApp.configure(name: "SecondaryAuth", options: options)
// Retrieve newly created app
guard let secondaryApp = FirebaseApp.app(name: "SecondaryAuth") else { return }
// Retrieve an Auth client configured against newly created app
let secondaryAuth = Auth.auth(app: secondaryApp)
So calling secondaryAuth.signIn(withEmail: …, password: …) is successful and works just fine.
Phone Sign In
The problem we are facing is related to phone authentication.
We have noticed that PhoneAuthProvider’s initializer can take in an Auth parameter, which leads us to believe that phone authentication should work using different authentication providers.
The documentation specifies that for phone authentication to work we must:
Enable phone number sign in on our Firebase projects
Enable app verification using:
Silent Push Notifications
reCAPTCHA
We have enabled these requirements and our app’s Firebase projects all support:
Phone number sign in
Have properly configured APNS
Xcode project has correctly configured URL Schemes
The problem we are facing is that after we call verifyPhoneNumber by passing the PhoneAuthProvider a secondary auth like so:
PhoneAuthProvider.provider(auth: secondaryAuth).verifyPhoneNumber(..., uiDelegate: nil)
We are receiving this error message:
'Please register custom URL scheme 'app-1-...' in the app's Info.plist file.'
Keep in mind that we have specified the proper URL Scheme pertaining to the secondaryApp previously mentioned inside the app’s info.plist.

Related

Firebase sendSignInLinkToEmail with Custom SMTP does not work

When using passwordless signin across any SDK (I'm using JS-web version 8 and iOS/Android with react-native-firebase) the link that is generated does not use the link url in the action settings if you've configured custom SMTP settings for email templates with a custom domain.
My custom domain for example is prod.fun.stuff and is already setup with Firebase Hosting and I've added it for the email templates as well.
When you use the sendSignInLinkToEmail method, you pass it an action code settings object. Here's an example of what I'm using:
const actionCodeSettings = {
url: 'https://prod.fun.stuff/links/confirmEmail',
handleCodeInApp: true,
iOS: {
bundleId: 'com.stuff.fun'
},
android: {
packageName: 'com.stuff.fun'
},
dynamicLinkDomain: 'prod.fun.stuff'
}
In my iOS app I've configured Universal Links properly to handle links in the app that use this domain:
# fun.entitlements
applinks:prod.fun.stuff
I use Mailgun as my email service and have configured its SMTP server settings for my domain mg.fun.stuff to send all my emails. While enabled, the login link is actually sent with:
http://email.mg.fun.stuff/e/someCrazyEncodedString...
What's unexpected is the login link is not the same domain as the actionCodeSettings I passed sendSignInLinkToEmail, and also that the protocol is http instead of https
but if I disable the custom SMTP server the login link that is sent to users is actually more intelligible:
https://prod.fun.stuff/?link=https://fun-stuff.firebaseapp.com/__/auth/action?apiKey%3DgeneratedAPIKey%26mode%3DsignIn%26oobCode%3DgeneratedoobCode%26continueUrl%3Dhttps://prod.fun.stuff/links/confirmEmail%26lang%3Den&apn=com.stuff.fun&amv=1&ibi=com.stuff.fun&ifl=https://fun-stuff.firebaseapp.com/__/auth/action?apiKey%3DgeneratedAPIKey%26mode%3DsignIn%26oobCode%3DgeneratedoobCodeQ%26continueUrl%3Dhttps://prod.stuff.fun/links/confirmEmail%26lang%3Den
It seems that with a custom SMTP server Firebase auth the login link has to be handled and decrypted by the custom server before it returns the proper redirect urls which can be handled.
This is an issue because when the user clicks on the link it doesn't get handled by the app, it instead opens the phone's browser.
Here are my Firebase SMTP settings:
Is there something I've possibly configured wrong with my email server? Or is this expected behavior when using a custom SMTP server? Should I handle the email server CNAME as another Universal link in the app?

MSAL library sign in without response

I'm trying an application that is able to display all and create new events for the Outlook calendar. I am using the MSAL library to get the authentication code. The problem is that although the login screen appears when I touch the buttons enter or cancel I do not notice any reaction. This is my code:
class OutlookManagerController: BaseViewController {
let oClientKey = "a07745a5-3b90-4385-a2b2-8223dbf68688"
let authScopes = ["openid+https://outlook.office.com/contacts.read+offline_access"]
func getAcessToken(){
if let application = try? MSALPublicClientApplication.init(clientId: oClientKey) {
application.acquireToken(forScopes: authScopes) { (result, error) in
if result != nil {
let userToken = result!.accessToken!
print(userToken)
} else {
print(error!.localizedDescription)
}
}
}
else {
print("Unable to create application.")
}
}
}
It would appear that you are not listening for the return of the auth code from the SFSafariViewController which MSAL launches when you call AcquireToken(). MSAL uses the SFSafariViewControllerfor login in order to enable better security and provide Single Sign-On across applications. You just need to set up a redirect URI that has the ability to call back your application and use the correct Issuer for your tenant.
That is the problem you are running in to. Sign in is successful, but Azure can't find it's way back to your app!
First, some groundwork.
System Webviews
Most modern OAuth2 libraries now use the System Webview for signing a user in. A System Webview is a browser component that an application can launch that appears to be part of the application but is actually an isolated process which runs the operating system's web browser. For iOS this is the SFSafariViewController and on Android it is Chrome Custom Tabs.
The benefit of the System Webview for singing a user in are numerous, including:
Better security. An application can not access the credentials typed in to a System Webview as it's an isolated browser process.
Today many applications use a username and password form or an embedded webview to get credentials. This allows an application to listen in and grab these credentials. Many companies have begun disallowing applications that have this kind of sign in. System Webviews ensures your app doesn't have this problem.
Single Sign-On. Once a user has signed in with the System Webview, a cookie is placed in the browser and that account is available to any application, preventing the need for a user to sign in to every app separately.
As more consumers and businesses leverage phone SMS and other factors as additional steps, having to redo this step as well as use your password is getting very annoying for customers. System Webviews remove this problem.
Better control. A user can choose which account to provide the application, or add a brand new account in the System Webview if supported.
You've seen Google and Microsoft make the transition to this System Webview with our latest SDKs as well as update our identity services, and it's to give the customer these protections and features. App Auth from the OpenID project, which has code contributed by both Google and Microsoft engineers among others, also provides this support.
System Webview Needs To Return Back To Your App
One of the things that might have occurred to you reading the above is this:
If an application now calls a System Webview for sign-in, and has no control over it, how do I get the tokens I need back from the System Webview?
The answer is that you must leverage the mechanisms each operating system has to call back the application, and then use the redirectURI from the OAuth2 protocol to return the response back to that application using that mechanism. It's almost exactly like how this works in a web browser. You get redirected away from the website to an identity provider for sign in, and then the identity provider uses the redirectURI to return the user back to the website with tokens in the response somewhere.
For iOS this done through Custom URL Schemes. A URL scheme is in the format of CFBundleURLScheme:\\CFBundleURLSchemes and is defined as:
CFBundleURLName
A string containing the abstract name of the URL scheme. To ensure uniqueness, it is recommended that you specify a reverse-DNS style of identifier, for example, com.acme.myscheme.
The string you specify is also used as a key in your app’s InfoPlist.strings file. The value of the key is the human-readable scheme name.
CFBundleURLSchemes
An array of strings containing the URL scheme names—for example, http, mailto, tel, and sms.
To register a URL type for your app, include the CFBundleURLTypes key in your app’s Info.plist file. The CFBundleURLTypes key contains an array of dictionaries, each of which defines a URL scheme the app supports.
How To Get A System Webview To Return To Your App
So, combining these two things, it's fairly simple what you need to do:
Figure out what your URL scheme will be for your app (e.g. appauth://abc123.
Tell Azure AD what your new URL scheme is, adding it as another redirectURI for your application.
Configure your application to listen for this URL scheme and launch the app
Configure the application itself to grab the token once this URL scheme launches your app (App Auth, as well our own MSAL libraries, do this for you!)
1. Figure out what your URL scheme will be for your app
You can read up on how to make URL schemes from Apple's Inter-App Communication documentation, but we recommend you use the scheme of appauth://<client id> (e.g. appauth://ab032846-efee-481f-b6bc-493aae92c432)
2. Tell Azure AD what your new URL scheme is
You need to add this URL scheme as a RedirectURI to your app. This is easy to do. Just visit https://apps.dev.microsoft.com/ and select your application. Scroll down to Custom App URIs under Native Applications and add your new redirectURI!
3. Configure your application to listen for this URL scheme
Add the following to your info.plist file as per Apple's instructions in Inter-App Communication. It should look something like this, although different apps will have different and additional URL schemes registered:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>ab032846-efee-481f-b6bc-493aae92c432</string>
<key>CFBundleURLSchemes</key>
<array>
<string>app-auth</string>
</array>
</dict>
</array>
4. Configure the application itself to grab the token
Although listening for a response back from a web browser to an application is a well known pattern for all iOS apps, the actual implementation varies by the identity SDK you use. For MSAL put the following code in your AppDelegate.m file:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
print("Received callback!")
MSALPublicClientApplication.handleMSALResponse(url)
return true
}
The code is rather easy to understand once you know the background above. This is all about receiving a response from the identity service by grabbing the URL that is returned and then giving everything in that URL back to the app so it can grab the tokens it needs to continue it's work.
That's it!
Your app should now work with Azure Active Directory. This pattern of the System Webview is common both across many Identity Providers (Microsoft, Google, Facebook) and mobile platforms.
This is documented in our sample application here.
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
MSALPublicClientApplication.handleMSALResponse(url)
return true
}

Non-interactive login (cached credentials) against Azure Graph API on iOS

I am creating an app on iOS that will run in a "kiosk" mode. Part of the application requires users to be able to search an organisation's directory. I would like to support Azure AD via the Azure Graph API to provide this function.
I don't want to require an interactive login when the app starts and I don't want to have to use an additional web service; I would like for the iOS app to simply access the Azure Graph API via REST.
I am aware of the risks associated with cached credentials, however the use of "service accounts" for non-interactive logins is fairly well established, the access is read-only and the credentials can be secured in the iOS keychain.
I have looked through numerous Azure samples and read the documentation and it seems that the method that provides what I need acquireToken(resource, credential) isn't available in the iOS ADAL library (and either is the ClientCredential class).
To clarify, this is how I would like my app to work:
User installs the app from the app store and runs it the first time
As part of the setup they authenticate to Azure AD by providing their tenant, application client ID and an application key. If they can't authenticate with an application key, a user id/password is acceptable as long as:
They never get prompted to authenticate again
Is there a solution here or do I just give up on Azure AD?
This can be done, but not with the ADALiOS framework as it doesn't expose the client_credentials grant that is required to make it work.
I was able to build a working demonstration using p2/OAuth. The sample app is here
The steps to build a working solution are:
Login to the legacy Azure Management portal and select your Azure AD Instance
Create a new application in that AD instance
Select "Add an application my organisation is developing"
Give it a name and select "Web application and/or Web API" not "Native Client Application"
Enter values for sign on url and app id url. These need to be well-formed URLs but do not need to be reachable
Once the application has been created select "Configure". Note the Client ID - you will need this
In the "Keys" selection, select 1 or 2 years from the drop down, then click "Save"
Once the key is displayed, copy this and save it somewhere; it can't be displayed again.
Set the required "Permissions to other applications" to allow your app the access it needs
Finally, at the bottom of the screen click "view endpoints" - You need to copy the OAuth 2.0 Token Endpoint and the OAuth 2.0 Authorization Endpoint
Download the demo code from GitHub
Run pod install
Plug the values into the Settings.plist file
Run the app
The meat of the authentication process is to set up an instance of OAuth2ClientCredentials -
let settings = [
"client_id": appData.clientId!,
"client_secret": appData.secret!,
"authorize_uri": appData.authString!,
"token_uri": appData.tokenString!,
"keychain": true,
"secret_in_body": true
] as OAuth2JSON
self.oauth2 = OAuth2ClientCredentials(settings: settings)
Then you can call doAuthorize() to get a token
self.oauth2.doAuthorize()

Issue With Using Multiple Custom Authenticated Accounts via AWS iOS SDK

We are building an app in iOS 9 that uses AWS as a backend.
The system is using custom authentication via Lambda (loosely based on https://www.youtube.com/watch?v=ZBxWZ9bgd44 and https://github.com/awslabs/api-gateway-secure-pet-store ), and on success cognito credentials are returned to be used for calls to AWS (API Gateway). The app uses a custom credentials provider and when a user successfully logs in this is what is being done to set up the user/session to make future calls to the system's AWS backend for the session:
// The SystemUserCredentials class holds the entered username and password from the UIView
let userCredentials: SystemUserCredentials = SystemUserCredentials(username, password)
// CustomAwsSessionCredentialProvider inherits AWSCredentialsProvider
let apiCredsProvider = CustomAwsSessionCredentialsProvider(credentials: userCredentials)
let defaultConfig = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: apiCredsProvider)
// API_CLIENT_CONFIG_KEY is a constant for the configuration key
SystemAPIClient.registerClientWithConfiguration(defaultConfig, forKey: API_CLIENT_CONFIG_KEY)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultConfig
And that initial user can communicate with our authenticated AWS backend with no problem.
What is being noticed is that when a user logs out with one valid account (the initial one when the app loaded), and then the user tries to log in with a different valid account there are failures in communicating with the [authenticated] web methods, and it looks like it is because the app is still trying to use the cognito credentials from the first/initial session. I did find this comment in AWS's API SDK which I believe is causing the issue: The default service configuration object. This property can be set only once, and any subsequent setters are ignored.
My initial thoughts are to move away from using the defaultServiceManager and its
defaultServiceConfiguration and going with non-default key-based services, clients,
configuration, etc.
My question is - has anyone else run into this, and is there a simpler way to use multiple
custom-authenticated clients in an app using the default service manager and
configuration?
Thanks much in advance.

iOS Google Signin Doesn't Work Properly With New Scopes

In a deprecation announcement from Google, they say that developers need to migrate away from these scopes:
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
and instead use these scopes:
email
profile
After doing so on my App-Engine backend, however, I'm having problems with my iOS app. On first run, it asks me to sign in and the screen shows both scopes correctly after which the app runs just fine. On the second run, where it is supposed to retrieve the authorization credentials from protected storage, it does not work as the App-Engine server is receiving null for the User parameter in the endpoint.
The App-Engine endpoint requires both the email and profile scope. The iOS code for retrieving previous credentials is:
GTMOAuth2Authentication* auth =
[GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:AUTH_KEYCHAIN_NAME
clientID:Constants.IOS_CLIENT_ID
clientSecret:Constants.IOS_CLIENT_SECRET];
and the iOS code for authenticating should there be no previous credentials is:
viewController = [[GTMOAuth2ViewControllerTouch alloc]
initWithScope:Constants.EMAIL_SCOPE
clientID:Constants.IOS_CLIENT_ID
clientSecret:Constants.IOS_CLIENT_SECRET
keychainItemName:AUTH_KEYCHAIN_NAME
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)];
This used to work just fine! Now the App-Engine server changed the email scope from .../userinfo.email to email and the profile scope from .../userinfo.profile to profile. The iPad app exhibits the same behavior against the new backend with Constants.EMAIL_SCOPE set to either value.
The google-api-objectivec-client library is v0510, the latest.
The Android version of my app continues to work just fine without any code changes after the backend change.
Update 2015-02-26: Now using r424 (2014-12-30) of google-api-objectivec-client. No change. If the AppEngine backend is using the new "profile" and "email" scopes, the iOS app cannot authenticate on the second (or later) run where it is loading the credentials from store rather than going through the sign-in flow.
The AppEngine log of the first (sign-in credentials) shows:
com.google.api.server.spi.auth.GoogleIdTokenUtils getCurrentUser: getCurrentUser: IdToken; email=testuser#gmail.com
The AppEngine log of the second (loaded credentials) shows:
com.google.api.server.spi.auth.AppEngineAuthUtils getCurrentUser: getCurrentUser: AccessToken; Tried and failed to get client id for scope 'com.google.api.server.spi.config.scope.DisjunctAuthScopeExpression#a015b54e'
com.google.appengine.api.oauth.InvalidOAuthParametersException:
at com.google.appengine.api.oauth.OAuthServiceImpl.makeSyncCall(OAuthServiceImpl.java:139)
at com.google.appengine.api.oauth.OAuthServiceImpl.getGetOAuthUserResponse(OAuthServiceImpl.java:118)
at com.google.appengine.api.oauth.OAuthServiceImpl.getAuthorizedScopes(OAuthServiceImpl.java:90)
at com.google.api.server.spi.auth.AppEngineAuthUtils.getOAuth2AuthorizedScopes(AppEngineAuthUtils.java:140)
at com.google.api.server.spi.auth.AppEngineAuthUtils.getCurrentUser(AppEngineAuthUtils.java:89)
...
This exception does not propagate up; null is returned for the user.
Is there something else that needs to be done for the GTMOAuth2ViewControllerTouch to work correctly with the new scopes? Or is there perhaps something wrong on the AppEngine side?

Resources