The bounty expires in 4 days. Answers to this question are eligible for a +250 reputation bounty.
Jugal Thakkar wants to draw more attention to this question.
I have an enterprise Flutter application that needs to launch the login page for the user's Identity Provider (IdP) inside a webview within the app. The IdP supports certificate-based authentication using a certificate present on the user's device (through MDM) to authenticate the user without needing to provide any credentials.
When launching a safari browser to launch this page, it works fine. Safari prompts the user to select a certificate the first time from the ones available and Safari sends it to the server and page successfully proceeds to present the protected resource.
Another requirement is that we need to open the IdP page using a specific user-agent string so that their firewall can be configured to only allow selected apps and not any random Safari page.
Unfortunately, the Safari In-App Browser does not allow overriding user-agent. While using an in-app web view using https://pub.dev/packages/flutter_inappwebview or https://pub.dev/packages/webview_flutter we are unable to get the certificate prompt like the one we see in safari and the communication fails with the following SSL error, with code -1200
An SSL error has occurred and a secure connection to the server cannot be made
When using InAppWebView from https://pub.dev/packages/flutter_inappwebview and accessing the protected site, the onReceivedClientCertRequest handler gets invoked, but I am not sure how to pass the device certificate back in the response. The ClientCertResponse expects a path to the certificate, what should this be?
Is there a way to retrieve the appropriate certificate from the OS's secure storage (keychain?) and send it to the web page? Either Flutter or native iOS code is ok. We are only focusing on iOS for this use case.
I'm currently trying to complete the OAuth dance with the Deezer API.
I have created a Deezer app on their site and full fill required fields.
I'm not using the Deezer SDK as it's deprecated since a long time ago.
I'm loading the request with a WKWebView and the request looks like :
https://connect.deezer.com/oauth/auth.php?app_id=MY_APP_ID&redirect_uri=MY_REDIRECT_URL&response_type=token&perms=basic_access
This open a webview displaying an auth form.
After i login with my deezer account credentials, i'm supposed to be redirected to the previous specified url MY_REDIRECT_URL.
But i intercept the redirection like this :
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Swift.Void) {
if !isLoading {
isLoading = true
}
if let redirectedUrl = navigationAction.request.url, redirectedUrl.host == AppConfig.redirectHost {
// extracting token from url
let deezerToken = DeezerURLBuilder.deezerToken(fromUrl: redirectedUrl)
// saving token
PrefUtils.shared.deezerToken = deezerToken
didLogin = true
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
I don't want to go to the redirect url because i'm in a iOS app and i hope i could prevent the server step.
I have noticed that the redirectedUrl looks like : https://MY_REDIRECT_URL/#access_token=frazeeFRT188maXzvPiCeTDEjCCbrIGGaCwlkGQra4whmp7rtJ&expires=1468
I don't understand why because from the Deezer API, i should see a code query parameter which i should put in the next request for App Authentication like this :
https://connect.deezer.com/oauth/access_token.php?app_id=MY_APP_ID&secret=MY_APP_SECRET&code=THE_MISSING_CODE
That request was sent with URLSession.shared.data.
So i was assuming that i got the final access_token and continue to request the user info with :
https://api.deezer.com/user/me?access_token=frazeeFRT188maXzvPiCeTDEjCCbrIGGaCwlkGQra4whmp7rtJ
Finally i got an error stating : "An active token must be used to query information about the current user"
I try the first request inside Safari in macOS then i was redirected to MY_REDIRECT_URL. After this i tried the request to get user info and it succeeded, i saw the json response in the Safari.
As i understand, Deezer has set cookies during the redirection in Safari. My guess is requests are completed because of the cookies and when i'm in the iOS App WKWebView is dismiss and subsequent requests are made without these.
To sum up,
why am i getting access_token without providing 'my app secret request' ?
(because i ask for it with the response_type param set to token)
How to properly do OAuth dance with Deezer API in SwiftUI/Swift App without Deezer SDK ?
PS: I didn't try OAuthSwift library as i want to keep it simple.
Thx for any help
EDIT: SOLVED
it was a typo in my url parameter... but it is fully working without a server redirect.
I'm linking to the uber api authentication endpoints (/authorize and /token) via iOS and after a successful login, it returns "Safari cannot open the page because the address is invalid". I've narrowed this down to an iOS specific problem because the same react-native configuration works for Android.
I've tried a number of solutions.
Clear safari browsing history, web data, iPhone update, etc. Nothing has worked.
I believe URL Schemes under URL Types in info.plist has a pay here, but I'm struggling to wrap my head around what I need to put there.
Any thoughts?
Upon successful authentication, uber should return an access token for the user.
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
}
I'm currently trying to implement GitHub Authentication via Firebase in an iOS app.
I've read through the docs and figured out what I have to do. I'm having trouble though, in implementing the communication between my app and the WebView where I authorize the application to get user's data. I currently have this code:
let url = urlComponents.url! // https://github.com/login/oauth/authorize + scope
// Not quite sure if I should use open(_:options:completionHandler:) to handle this operation.
guard UIApplication.shared.canOpenURL(url) else {
return
}
UIApplication.shared.open(url, options: [:], completionHandler: nil)
What this code does is bring the user to the browser with the GitHub authorization screen, type the password and then a blank screen shows up. Inspecting the URL in this blank screen, I've found out that it contains the parameter it should have, but I'm not quite sure how to pass this parameter to my code so I can proceed with the authentication.
Firebase doc says I should implement a Custom URL scheme to handle the OAuth callback, but I'm not sure how to do it.
Solved, solution was to edit GitHub's Authorization callback URL to have my custom URL scheme and then proceed through the documentation flow.