Ilya Loshkarev loshkarev.i@gmail.com
SFEDU 2016
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.
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
Dispatch queue for remote requests on top of TCP/IP sockets
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
URLSessionConfiguration.default
URLSessionConfiguration.ephimeral
URLSessionConfiguration.background("background.session")
session.dataTask(with: URL)
session.uploadTask(with: URLRequest, from: Data)
session.downloadTask(with: URL)
URLSessionDelegate
URLSessionTaskDelegate
URLSessionDataDelegate
URLSessionDownloadDelegate
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
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 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
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()
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()
let urlComponents = URLComponents(
URL: baseUrl,
resolvingAgainstBaseURL: true)!
urlComponents.path = relativePathString
urlComponents.query = parametersString.addingPercentEncoding(
withAllowedCharacters: .urlHostAllowed)
let request = URLRequest(url: urlComponents.url!)
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
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
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)
}
}
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
let headers: HTTPHeaders = [
"Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
]
Alamofire.request("https://httpbin.org/post",
method: .post, headers: headers)
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
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
let authData = Data("\(user):\(password)".utf8)
let base64String = authData.base64EncodedString()
let request = URLRequest( url: myUrl)
request.addValue("Basic \(base64String)",
forHTTPHeaderField: "Authorization")
Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(user: user, password: password)
.responseJSON { response in
debugPrint(response)
}
2010 – ver. 2.0
Provides secure deligated access on behalf of resource owner
Theese two tokens could be given by a servers differnet from an API provider
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
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
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
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 */
}
}
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
A protocol that utilses HTTP headers and JSON data
to access server endpoints
Non standartized
URL/Header | GET | POST | PUT | DELETE |
---|---|---|---|---|
Entity/id | select by id | insert with id | update with id | delete with id |
Entity | select all/filtered | insert all/filtered | update all/filtered | delete all/filtered |
urlComponents.query = parametersString
request.addValue(key, parameters[key])
request.body = parametersString
Is usually used for parameters encoding
// 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)
}