How to Share NSURLConnection credentials with Safari? - ios

I'm trying to intercept link and login a user, then send them on to Safari and have the page load with no authentication request.
So, what I'm doing so far...
I register a custom URL scheme for my app. Call it "myhttp". Now someone clicks on a link (say from an email) of myhttp://secured.com/foo and my app runs. The apps pulls the user's credentials from somewhere and makes a call to the real URL with an NSURLConnection. The NSURLConectionDelegate implements connection:didReceiveAuthenticationChallenge and I navigate through the security layer fine. Next I try loading the same url using the UIApplication openURL method to bring up Safari, but I still get an authentication check.
I thought this would work because I read the follow in the Apple documentation.
Credentials stored in persistent storage are kept in the user’s keychain and shared among all apps.
And when I check the NSURLCredentialStorage I can see the credentials I just used stored there with the correct information, protection space, scheme, etc, but clearly I'm doing something wrong or I wouldn't be getting an authentication challenge when I switch to Safari.
So the question is, did I just screw up somewhere along the line, forget some important bit or am I going about this the wrong way?

This wasn't possible before iOS 8, but is now with the Shared Web Credentials feature.
Add a com.apple.developer.associated-domains entitlement to your app.
This entitlement must include all the domains with which you want to
share credentials.
Add an apple-app-site-association file to your website. This file must
include application identifiers for all the apps with which the site
wants to share credentials, and it must be properly signed.
When the app is installed, the system downloads and verifies the site
association file for each of its associated domains. If the
verification is successful, the app is associated with the domain.
An app can share credentials with any associated domains by calling
SecAddSharedWebCredential and SecRequestSharedWebCredential.

Related

Should I save passwords in shared web credentials AND (local) keychain

I am in the process to design a login for a new app that will be associated with a domain, i.e. be the counterpart to an SPA.
Obviously I want to use
iOS 11 Password Autofill, and
Shared Web Credentials
I have read the documentation on autofill as well as watched the WWDC video about it. Also, I checked the article on Shared Web credentials, which I think is older than the new, reworked autofill. Said article recommends:
Do not use the shared web credentials as your primary storage for secure user credentials. Instead, save the user’s credentials in the keychain, and only use the shared web credentials when you can’t find the login credentials in the keychain.
This strikes me a little odd, because it
- Means I have to cover more possible inconsistencies, i.e. synchronize the keychain somehow wit the shared web credentials (what if I have credentials in the keychain as well as the shared web credentials, but they're different?)
- Potentially leaves "garbage" behind in the keychain if my user user uninstalls my app (naturally I hope they won't ever do this, but let's be realistic, some will)
Especially the last point had always bothered me in the past (before shared web credentials and autofill were a thing, or when my app doesn't have an associated domain). Unlike on macOS, the iOS Accounts & Passwords feature (in the Settings app) doesn't list ALL passwords, but only the ones used by Safari (i.e. the shared web credentials), correct? Keychain Access on macOS instead offers a means to view and manage all credentials, even those that aren't synchronized over iCloud.
I understand why the same is not offered on iOS, but it also means that for those passwords that my app saves (locally) to "its" keychain "part" can only be managed if I offer a UI for this in my app. And if the user uninstalls the app before using this, the item will stay in the keychain, at least it was that way when I tried it a couple of years ago.
My main question now is, wouldn't it be easier to disregard the article's advice and only rely on the shared web credentials for password storage? That's the part they can edit in Settings (if ever need be) and also it will reflect any password changes done on the website. I would design my app like this then:
First launch: App starts on the Login screen and offers the username/password via Autofill
User logs in: App saves a simple flag in the shared user defaults indicating the user is logged in.
App gets relaunched, e.g. after a device reboot: The app skips the login screen due to the flag and gets the password and user name from the shared web credentials (assuming the user previously granted it permission, of course)
User explicitly logs out: The app deletes the flag, basically setting everything back to first launch
User deletes the username and password from the shared web credentials (e.g. in the Settings app or with Keychain Access on macOS): The app falls back to the login screen as soon as it detects this (e.g. when attempting a remote request, or after relaunch), regardless of the flag. I think this matches the user intention best (if you delete a password you don't want some apps to hold onto it until you log them out)
This setup would avoid any issues with different items in the keychain and shared web storage and it would immediately propagate updates done in the webpage to the app as well (which is what I'd intent for my app anyways). Is there anything that would keep this app flow from working?
(Note: I asked the same question on the apple developer forums, so if you see that as well don't be confused. I will update any potential answers from there to here and vice versa.)
Edit to address #Aaron's answer:
Thank you so much for the info. Your answer helped me realize I misunderstood something about shared web credentials: I assumed that for an app with associated domain, you can access the credentials without user interaction (after perhaps an initial authorization). Like you can set the checkbox on macOS when an application requests credentials. I now realize this is wrong and on iOS you would always have to verify with the user, thanks.
For completeness sake, I still want to point out some of the other things you said:
You are right, we will eventually use token based authentication, so I will save that in the keychain (probably in addition to the password, see below). I just tried to keep the question simple enough at first.
Our app is like an email client where you update new incoming "mail". The mentioned "login flag" in something like the user defaults would thus just indicate whether the app should behave as if subscribed to an inbox or not. Like in Mail, you wouldn't expect to have to login even after relaunch.
For this reason I will probably eventually save the user's password in the (local) keychain along with a token. If the token expires, I can request a new one without user interaction, that's important in our general site and app design. Only if that request fails I would use the shared web credentials (updating my local copy of the creds in the process).
For what it's worth, the last point you mentioned is probably debatable. On macOS, for example (where you can edit the entire keychain, not just Safari passwords) de facto logs you out of an app. Mail, again, as an example. If the keychain item for an inbox is gone, Mail re-asks that the next time it is launched and tries to access the content (effectively a "kind of" login in a way).
Again, thank you a lot for answering, now I can close an open todo. :) Also thanks to #HamZa for giving out a bounty!
Considering this advice:
Do not use the shared web credentials as your primary storage for secure user credentials. Instead, save the user’s credentials in the keychain, and only use the shared web credentials when you can’t find the login credentials in the keychain.
The main issue here is that the shared web credentials process is a little clunky — it requires user interaction and takes time to resolve the credentials. So if the user has already authenticated with your app you want to avoid showing them the login page at all. You can do this by storing credentials in your app's keychain where you can access them immediately without a network connection or user permission.
This doesn't mean you need to store the user's password in the keychain. Typically you would store something like an OAuth access token in the keychain. The presence of this token means the user is authenticated - and if an API endpoint rejects your token then you can take them back to the login page.
This suggestion:
User logs in: App saves a simple flag in the shared user defaults indicating the user is logged in.
is possibly insecure depending on what you're hiding behind the login page, but typically any content belonging to the user should require a valid token to access, not just a bool in the user defaults.
I think this matches the user intention best (if you delete a password you don't want some apps to hold onto it until you log them out)
I disagree with this; I would not expect an iOS app to log out because I deleted a password from my Safari keychain.

Is there a way to know if there is a cookie available before showing the SFAuthenticationSession prompt

On iOS 11, Apple introduced a new way to share auth data between the web and a Mobile App with SFAuthenticationSession.
It would be a bad user experience to show the SFAuthenticationSession prompt to every new users - that might have never used my website - to have them agree and then get nothing out of it and have ask them to login.
The documentation is pretty empty on Apple side. This is the only example I found.
Is there a way to know if there is a cookie available before showing the SFAuthenticationSession prompt? Or maybe, with the Associated Domains enabled, when authenticating with my domain, the system should not show the prompt?
No. Even if there aren't cookies, the user can login entering his username/password and then clicking on "Login"/"Enter" on the website (E.g: Facebook, Instagram).
Accessing the cookies won't be enabled:
When the webpage is presented, it runs in a separate process, so the user and web service are guaranteed that the app has no way to gain access to the user’s credentials. Instead, the app gets a unique authentication token.
Official docs
Working example for Instagram-OAuth: https://github.com/dvdhpkns/SFAuthenticationSession-Instagram-Oauth
And the GitHub repo you added was posted by the author to send a bug report about errors in cookie sharing for local servers (rdar://33418129. Original tweet)
Are cookies shared between Safari and SFAuthenticationSession?
#DVDHPKNS
They’re supposed to be shared, but we have some timing bugs right now. Please do file bugs about what you’re seeing.
#rmondello (Apple employee)
P.S: They added more information to the docs since your original post date.

Log in with other iOS App (Similar to log in with Facebook)

I have one app where users can create accounts and log in. Other apps will use this account to send in information on app usage.
As of now, users go from the normal apps to the login app via deep-linking, and they send their URL scheme to the login app, so the login app can return them afterwards using this URL scheme. A token is also sent back which is used to identify the user.
To get this to work the normal apps need an URL scheme in their Info.plist however. Logging in with Facebook or Twitter, this is not necessary.
Is there any way to do this without needing the URL Schemes in the Info.plist?
You could probably build a system to accomplish this using Branch deep linking with appended query param links and our match_guaranteed link parameter. That would let you pass data around without needing to hard-code the URL scheme into your client apps, because all of that configuration is handled server-side. It also covers you in the situation that one of the apps isn't installed.
I imagine it would look something like this:
Set up the 'master' app with a Branch key
Set up each client app with its own Branch key
Build an appended params link from the client app into the master app, including some sort of identifying token(s) for the client app. This token could even be the exact return link needed, which you can generate in advance
Do whatever you need in the master app with the sign in or registration
If successful, send the user back to the client app either by building an appended params link, or using the pre-generated link if you passed that over initially

GIDSignIn require password

I'm developing an internal app that will leverage our corporate Google Drive accounts and will be used on shared devices (iPads shared among teachers and students at school sites).
Is there a way to force GIDSignIn to require a password with each sign-in attempt? Right now, even after calling GIDSIgnIn.sharedInstance().signOut() (or GIDSignIn.sharedInstacne().disconnect()) the user doesn't need to enter their password the next time they access the app. That means, when the device is taken by the next user, they could very easily access the other user's account.
Am I missing something? Do I need to somehow clear the cookies store in the UIWebView that the GIDSignIn process uses?
Where available, the GIDSignIn login process uses a SFSafariViewController, not a UIWebView. It leverages the cookies (as well as passwords) stored in Safari, for a quicker login.
I don't think you would be able to clear such cookies. You should be able to force a Google log out, though, by opening https://www.google.com/accounts/Logout in an SFSafariViewController, though the interaction with the rest of your app may be a bit weird. See Logout link with return URL (OAuth) for a way to provide a return URL which you may try to use to control the process (you'll need to use an URL scheme to return, though).
Note that iOS may prompt to save login information, and then provide said login information to subsequent users. You'll need to disable that in Settings -> Safari -> AutoFill
There may be other ways of achieving it via configuration of the device, but iOS is not really designed for multiple users at the moment.

How to to share context between Safari and Native App?

I have a need to set some context via Safari (a context token), and then read that context from a native iOS app. What are the best practices for doing this?
A couple thoughts so far:
Set the context in an HTML 5 database, but I'm not sure this will work because the database might be only accessible from Safari. Would using a WebUIView in the native app allow me to access the same HTML5 database / local storage as Safari?
Set the context in device storage, but I'm not sure this will work because I don't know if Safari can actually write to device storage.
I would suggest one of these two options:
Let the web server keep track on the user both in the app and on the website, for example by creating a user account.
or
Pass the context token to the app immediately via an URL-scheme by registering your app as a protocol handler, see more info here
Suggested way:
Send e-mail with link and context token, when user clicks link, save context token in cookie in safari, then redirect to appstore for app download.
When the user downloaded the app and opens it, present a button for the user, when the user clicks it, open a web page in safari.
Safari loads the cookie with the context token, and then triggers another link using a URL-scheme like yourAppName://contextToken=12345678. The link opens your app which reads the context token from the URL.
There is no best practice for directly sharing data between safari and a native app directly and that it is simply not intended that you should do that. All cookies and storages are sandboxed for each app and safari has its own sandbox.
Letting your server doing the job via user accounts is the best and clean way i.m.o. That is why you have user accounts. If you didn't try out the protocol handler for reading specific URLs, that could also be made handy I think.
Could you have the app hit a URL on first launch hosted by server which is redirecting the user in safari, and compare IP addresses, time, iOS version, etc to get at least an approximate match? If an approximate match is insufficient, you could, when you see an approximate match, have your app open safari to confirm their identify via cookie.
It’s easy to send messages between a UIWebView and your native up using WebViewJavascriptBridge.
In your case, though, the accepted answer’s suggestion of using a custom URL scheme (directly from email to app, post-install) makes the most sense.

Resources