April 1, 2016

How to Login User Automatically Into The App With SFSafariViewController on iOS 9

The new SFSafariViewController was introduced during the WWDC15 and it’s built into iOS9. It enables you as a developer to deliver data to the app from your web service (site), avoiding any need for authorization so that the user feels a deep level of integration between the web version of your service (site) and the client mobile application. We’ve already built this feature into our project and want to share our mobile app development experience with you!

We’ve made a PDF-version of this article so you can read it later. Download

Imagine the following scenario:

  1. User sign up for some service in web interface
  2. User receive email with an invitation code and AppStore “download button”
  3. User clicks the AppStore “download button”
  4. App is downloading from the AppStore
  5. When user open an app his/her profile and info is already imported into the app.

In this article I’ll try to answer two questions:

  1. How does the invitation work?
  2. How is it implemented?

The trick is simple: when a user clicks on the special link, the website sets cookies in the Safari browser and navigates a user to the AppStore. When the app is opened, it displays Safari View Controller in a hidden UIWindow to open the website special link. When the app opens that page, the website tries to read cookies (that were set when the user clicked the download button in their email), and because Safari View Controller and Safari browser share cookies the website can easily read them and redirect info to the app via the URL scheme. Technically your site just redirects request to your custom scheme like:

“yourApp://some_invitation_info=info”

that iOS interpreters as opening the app by URL scheme and pasting URL inside.

5 small technical steps need to be performed in order to achieve this: 3 steps on the iOS app part and 2 on the backend part.

iOS app part

1) Configure the URL scheme in the app (if the app already uses the URL scheme you can skip this step).

In the project navigator on the left, select your project name, and select your target, then select the Info tab. Expand the URL Types section and click the + button.

Here, you only need to add two things: an Identifier, and the URL scheme.

The Identifier should be a unique string. Apple recommends using a reverse-domain style name to ensure uniqueness. The URL Scheme is the thing that will actually launch the app. The URL scheme you are probably most familiar with is http://—the scheme that precedes all URLs on the web.

So now you can open the application by using the provided the URL Scheme, i.e.:

yourApp://action?param=value

2) Open SFSafariViewController in the hidden window inside the mobile app

import UIKit
 
import SafariServices
@UIApplicationMain
 
class AppDelegate: UIResponder, UIApplicationDelegate {
 
  var window: UIWindow?
 
  var safariViewController: SFSafariViewController!
 
  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    window = UIWindow(frame: UIScreen.mainScreen().bounds)
    window?.makeKeyAndVisible()
    let url = NSURL(string: "https://yoursite.com/invitation")!
 
    safariViewController = SFSafariViewController(URL: url)
 
    window?.insertSubview(safariViewController.view, atIndex: 0)
 
    return true
  }
 
}

3) Handle redirection from your website to the app

func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {

  safariViewController.view.removeFromSuperview()
  safariViewController = nil
  // do your stuff with “yourApp://action?param=value” url

  return true

}

Backend part

1) Set appropriate cookies in the Safari browser SetCookies.php

<?php

setcookie('someKey', 'someValue', time() + 3600 * 24); // setting cookies with expiration date for 24h
header(‘Location:#link_to_appstore'); // redirect to App Store

?>

Or as an alternative – JavaScript code

function createCookie(name,value,days) {

  var expires = "";
  if (days) {
    var date = new Date();
    date.setTime(date.getTime()+(days*24*60*60*1000));
    expires = "; expires=" + date.toGMTString();
  }

  document.cookie = name + "=" + value + expires + "; path=/";

}

createCookie('someKey','someValue',1);
window.location = "#link_to_appstore";

2) Read the cookies and redirect to your app with required data ReadCookiesAndRedirect.php

<?php

  $cookie_name = 'someKey';

  if(isset($_COOKIE[$cookie_name])) { // checks if cookies exists

    $cookie_value = $_COOKIE[$cookie_name];

    if(strcmp('someValue', $cookie_value) == 0) { // validates cookies
      setcookie($cookie_name, '', time() - 3600); // removes old cookies
      header(‘Location:yourApp://invitation?code=1Q2W3E'); //redirects data to your app
    }

  }

?>

Or as an alternative – JavaScript code

function readCookie(name) {

  var nameEQ = name + "=";
  var ca = document.cookie.split(';');

  for(var i=0;i < ca.length;i++) {
    var c = ca[i];
    while (c.charAt(0)==' ') c = c.substring(1,c.length);
    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
  }

  return null;

}

function eraseCookie(name) {
  createCookie(name,"",-1);
}

var x = readCookie('someKey')

if (x === 'someValue') {
  window.location = "yourApp://invitation?code=1Q2W3E";
  eraseCookie('someKey')
}

Summary

To summarise, Apple have introduced yet another handy feature that helps to improve user experience by breaking down a wall between services. So it doesn’t matter where the user last logged in or entered their information, all your service clients will be able to show their latest content. It is clear that this feature has at least one great potential implementation – guest apps can fully skip their sign-in process if the user is already logged in through Safari, giving a real WOW effect to people who are using the app.

That’s it!