CS333

Mobile Development


Ilya Loshkarev
loshkarev.i@gmail.com

Networking

HTTP

App Transport Security

Starting in iOS 9.0 and OS X v10.11, a new security feature called App Transport Security (ATS) is enabled by default for all HTTP connections made with NSURLSession. ATS requires that HTTP connections use HTTPS.

HTTPS

Disabling ATS


              NSAppTransportSecurity : Dictionary
                NSAllowsArbitraryLoads : YES      // http everywhere - unsafe!
            

Specify domains that are allowed to have http connections:


              NSAppTransportSecurity : Dictionary
                edu.mmcs.sfedu.ru : Dictionary
                  NSAllowsArbitraryLoads : YES // allows http in domain
            

iOS 10.0 introduced NSAllowsArbitraryLoadsInMedia and
NSAllowsArbitraryLoadsInWebContent

URL Session

URL Session Diagram

Dispatch queue for remote requests on top of TCP/IP sockets

Shared Session


              let session = URLSession.shared
              let url = URL(string: "http://edu.mmcs.sfedu.ru/")
              let task = session.dataTask(with: url) {
                data, response, error in
                /* handle response */
              }
              task.resume() // start task
            

Default uncofigurable session for handling basic requests

Session Configurtion

  • Default default session configuration
    URLSessionConfiguration.default
  • Ephimeral doesn't write to cache or cookies
    URLSessionConfiguration.ephimeral
  • Background allows background upload and download
    URLSessionConfiguration.background("background.session")

Session Tasks

  • Data Task sends and receives data using NSData objects
    session.dataTask(with: URL)
  • Upload Task sends data and supports background uploads
    session.uploadTask(with: URLRequest, from: Data)
  • Download Task retrieves data and supports background downloads and uploads
    session.downloadTask(with: URL)

Session Delegate

  • URLSessionDelegate
    Authentication on a session-level, Session errors
  • URLSessionTaskDelegate
    Authentication requests, Redirects, Completion
  • URLSessionDataDelegate
    Recieved Data, Should use cache
  • URLSessionDownloadDelegate
    Download has finished, started, progressed

Building a Session


              let config = URLSessionConfiguration.default
              config.allowsCellularAccess = false
              config.timeoutIntervalforRequest = 30.0
              let session = URLSession(configuration: config)
              let url = URL(string: "http://edu.mmcs.sfedu.ru/")
              let task = session.dataTask(with: url) {
                  data, response, error in
                  /* handle response */
              }
              task.resume() // start task
            

If no delegate is assigned to a session,
a completion handler must be provided to recieve data

HTTP Response


              let task = session.dataTask(with: url) {
                  data, response, error in
                  if let httpResponse = response as? HTTPURLResponse {
                      switch httpResponse.statusCode {
                        case 200: print("OK")
                        case 404: print("Not found")
                        default:
                          print("Something else")
                      }
                  }
              }
            

HTTP Headers

HTTP headers can be setup as part of each request or
as default headers in configuration


              let config = URLSessionConfiguration.default
              config.allowsCellularAccess = false
              config.timeoutIntervalforRequest = 30.0
              config.httpAdditionalHeaders["Some Header"] = someValue
            

Download Data


              let request = URLRequest( url: myUrl)
              request.httpMethod = "GET"
              /* set any additional HTTP Headers here */
              let task = session.dataTask(with: request ){
                  data, response, error in
                  /* handle response */
              }
              task.resume()
          

Upload Data


              let request = URLRequest(url: myUrl)
              request.httpMethod = "POST"
              /* set any additional HTTP Headers here */
              let task = session.uploadTask(with: request, from: someData ){
                  data, response, error in
                  /* handle response */
              }
              task.resume()
          

URL with Parameters


              let urlComponents = URLComponents(
                  URL: baseUrl,
                  resolvingAgainstBaseURL: true)!
              urlComponents.path = relativePathString
              urlComponents.query = parametersString.addingPercentEncoding(
                  withAllowedCharacters: .urlHostAllowed)
              let request = URLRequest(url: urlComponents.url!)
            

Multipart HTTP Header

Alamofire

Alamofire https://github.com/Alamofire

HTTP networking library

  • Chainable Request / Response Methods
  • URL / JSON / plist Parameter Encoding
  • Upload File / Data / Stream / MultipartFormData
  • HTTP Response Validation

CocoaPods

Dependency manager for Cocoa projects


              > sudo gem install cocoapods
              // initialize
              > pod init
              // create pod file
              > echo "source 'https://github.com/CocoaPods/Specs.git'
              platform :ios, '10.0'
              use_frameworks!
              target '<Your Target Name>' do
                pod 'Alamofire', '~> 4.0'
              end" > Podfile
              // install all dependences
              > pod install
            
https://cocoapods.org

Simple Request


              Alamofire.request("https://httpbin.org/get").response {
                  response in
                  print(response.request)  // original URL request
                  print(response.response) // HTTP URL response
                  print(response.data)     // server data
                  print(response.result)   // result of response serialization
                }
            

Responses are handled asynchronously

Response Validation


              Alamofire.request("https://httpbin.org/get")
                  .validate(statusCode: 200..<300)
                  .validate(contentType: ["application/json"])
                  .responseData { response in
                      switch response.result {
                      case .success:
                          print("Validation Successful")
                      case .failure(let error):
                          print(error)
                      }
                  }
            

Request with Parameters


              let parameters: Parameters = [
                  "foo": "bar",
                  "baz": ["a", 1],
                  ]
              ]

              Alamofire.request("https://httpbin.org/post",
                  parameters: parameters, encoding: URLEncoding.default)
              Alamofire.request("https://httpbin.org/post",
                  parameters: parameters, encoding: URLEncoding.httpBody)
              Alamofire.request("https://httpbin.org/post", method: .post,
                  parameters: parameters, encoding: JSONEncoding.default)
            

Parameters can be encoded into
URL, HTTP body or JSON

Additional Headers


              let headers: HTTPHeaders = [
                  "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
              ]
              Alamofire.request("https://httpbin.org/post",
                  method: .post, headers: headers)
            

Authentication

AAA Protocols

  • Authentication – confirming your identity
  • Authorization – confirming your right to make a request
  • Accounting – keeping track of user requests

Basic Access Authentication

Implemented by Apache modules

Unauthenticated requests return a response with
401 Unauthorized status and a WWW-Authenticate field


              WWW-Authenticate: Basic realm = "User Visible Realm"
            

Client sends authentication credentials
using HTTP header field Authorization


              Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
            

Session Level Authentication


              func urlSession(_ session: URLSession, task: URLSessionTask,
                  didReceive challenge: URLAuthenticationChallenge,
                  completionHandler: @escaping (URLSession.AuthChallengeDisposition,
                  URLCredential?) -> Void)
              {
                  let credential = URLCredential( // credentials to send
                      user: "test",
                      password: "test",
                      persistence: .forSession)
                  completionHandler(.useCredential, credential)
              }
            

If your authentication is a default Apache authentication
implementation of didReceiveChallenge delegate is prefered

Request Level Authentication


              let authData = Data("\(user):\(password)".utf8)
              let base64String = authData.base64EncodedString()
              let request = URLRequest( url: myUrl)
              request.addValue("Basic \(base64String)",
                  forHTTPHeaderField: "Authorization")
            

Alamofire Request Authentication


              Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)")
                  .authenticate(user: user, password: password)
                  .responseJSON { response in
                      debugPrint(response)
                  }
            

Open Authorisation Protocol

2010 – ver. 2.0

Provides secure deligated access on behalf of resource owner

OAuth Logo

Peudo Authentication

PseudOAuth

3-legged Authorization

  1. Register
  2. Receive Authorization Token
  3. Ask for Authorised Access with Auth.Token
  4. Receive Access Token
  5. Access APIs with given token

Theese two tokens could be given by a servers differnet from an API provider

Ask for Authentication Token


              func startOAuth2Login() {
                  let authPath = "https://github.com/login/oauth/authorize?
                      client_id=\(clientID)&scope=repo&state=TEST_STATE"

                  if let authURL = URL(string: authPath) {
                    UIApplication.shared.openURL(authURL)
                  }
              }
            

openURL is a system-wide call for an App
that is registered for the requested URL Scheme

Browser window will pop up and ask the user
to allow access for our app

Handling URL Callbacks


              URL types
                Item 0
                  URL Schemes
                    Item 0 - yourURLScheme
                  URL identifier - yourAppID
            

iOS app can be register to handle URL Scheme calls

Most OAuth2 systems allow for an URL callback

Receive Authentication Token


              class AppDelegate: UIResponder, UIApplicationDelegate {
                  func application(application: UIApplication, handleOpenURL url: URL) -> Bool {
                      // url == yourURLScheme://...&code=12345&...
                      proceedOAuth2Access(with: getOAuth2Token(url))
                      return true
                  }
              }
            

Your app is getting a callback to handle URL with Auth.Token

Ask for Access Token


              func getOAuth2Token(_ url: URL) -> String? {
                /* parse URL Components for code */
              }
              func proceedOAuth2Access(with authToken: String?) {
                  guard let receivedToken = authToken { return }
                  let getTokenPath = "https://github.com/login/oauth/access_token"
                  let tokenParams = [ "client_id": clientID,
                      "client_secret": clientSecret,
                      "code": receivedToken ]
                  Alamofire.request(.POST, getTokenPath, parameters: tokenParams)
                    .responseString { (request, response, results, error) in
                     /*  handle response to extract access token */
                    }
              }
            

Alamofire Adapter & Retrier


              let oauthHandler = OAuth2Handler(
                  clientID: "12345678",
                  baseURLString: baseURLString,
                  accessToken: "abcd1234",
                  refreshToken: "ef56789a"
              )

              let sessionManager = SessionManager()
              sessionManager.adapter = oauthHandler
              sessionManager.retrier = oauthHandler

              let urlString = "\(baseURLString)/some/endpoint"
              sessionManager.request(urlString)
            

Alamofire proides abstractions to handle
some of the OAuth2 interactions on the session-level

Restful API

Rest

A protocol that utilses HTTP headers and JSON data
to access server endpoints

Non standartized

Expected API Specification

URL/HeaderGETPOSTPUTDELETE
Entity/id select by idinsert with id update with iddelete with id
Entity select all/filteredinsert all/filtered update all/filtereddelete all/filtered

Query Parameters
with URLSession

  • URL Encoded
    urlComponents.query = parametersString
  • HTTP Headers
    request.addValue(key, parameters[key])
  • HTTP Body
    request.body = parametersString

Javascript Object Notation

Is usually used for parameters encoding

JSON Serialization


              // Serialize any KVC compliant object to JSON string
              let data = JSONSerialization.data(withJsonObject: bird, options:[])
              // Deserialize string into NSObject
              if let json = ( try? JSONSerialization.jsonObject(with:
                  data!, options: []) ) as? [String: AnyObject]
              {
                  print(json["name"] as? String)
              }
            

Object Mapper

github.com/ObjectMapper

Mappable

Alamofire Object Mapper

github.com/AlamofireObjectMaper

Remote Synchronization

RestKit

github.com/RestKit

Sync

github.com/SyncDB

Related Resources