2 // OAuthSwiftCredential.swift
5 // Created by Dongri Jin on 6/22/14.
6 // Copyright (c) 2014 Dongri Jin. All rights reserved.
10 /// Allow to customize computed headers
11 public protocol OAuthSwiftCredentialHeadersFactory {
12 func make(_ url: URL, method: OAuthSwiftHTTPRequest.Method, parameters: OAuthSwift.Parameters, body: Data?) -> [String: String]
16 // swiftlint:disable:next class_delegate_protocol
17 public protocol OAuthSwiftSignatureDelegate {
18 static func sign(hashMethod: OAuthSwiftHashMethod, key: Data, message: Data) -> Data?
21 // The hash method used.
22 public enum OAuthSwiftHashMethod: String {
26 func hash(data: Data) -> Data? {
29 let mac = SHA1(data).calculate()
30 return Data(bytes: UnsafePointer<UInt8>(mac), count: mac.count)
37 /// The credential for authentification
38 open class OAuthSwiftCredential: NSObject, NSSecureCoding, Codable {
40 public static let supportsSecureCoding = true
42 public enum Version: Codable {
45 public var shortVersion: String {
63 init(_ value: Int32) {
74 public func encode(to encoder: Encoder) throws {
75 var container = encoder.singleValueContainer()
76 try container.encode(self.toInt32)
79 public init(from decoder: Decoder) throws {
80 self.init(try decoder.singleValueContainer().decode(Int32.self))
84 public enum SignatureMethod: String {
85 case HMAC_SHA1 = "HMAC-SHA1"
86 case RSA_SHA1 = "RSA-SHA1"
87 case PLAINTEXT = "PLAINTEXT"
89 public static var delegates: [SignatureMethod: OAuthSwiftSignatureDelegate.Type] =
90 [HMAC_SHA1: HMAC.self]
92 var hashMethod: OAuthSwiftHashMethod {
94 case .HMAC_SHA1, .RSA_SHA1:
101 func sign(key: Data, message: Data) -> Data? {
102 if let delegate = SignatureMethod.delegates[self] {
103 return delegate.sign(hashMethod: self.hashMethod, key: key, message: message)
105 assert(self == .PLAINTEXT, "No signature method installed for \(self)")
112 open internal(set) var consumerKey = ""
113 open internal(set) var consumerSecret = ""
114 open var oauthToken = ""
115 open var oauthRefreshToken = ""
116 open var oauthTokenSecret = ""
117 open var oauthTokenExpiresAt: Date?
118 open internal(set) var oauthVerifier = ""
119 open var version: Version = .oauth1
120 open var signatureMethod: SignatureMethod = .HMAC_SHA1
122 /// hook to replace headers creation
123 open var headersFactory: OAuthSwiftCredentialHeadersFactory?
129 public init(consumerKey: String, consumerSecret: String) {
130 self.consumerKey = consumerKey
131 self.consumerSecret = consumerSecret
134 // MARK: NSCoding protocol
135 fileprivate struct NSCodingKeys {
136 static let bundleId = Bundle.main.bundleIdentifier
137 ?? Bundle(for: OAuthSwiftCredential.self).bundleIdentifier
139 static let base = bundleId + "."
140 static let consumerKey = base + "comsumer_key"
141 static let consumerSecret = base + "consumer_secret"
142 static let oauthToken = base + "oauth_token"
143 static let oauthRefreshToken = base + "oauth_refresh_token"
144 static let oauthTokenExpiresAt = base + "oauth_token_expires_at"
145 static let oauthTokenSecret = base + "oauth_token_secret"
146 static let oauthVerifier = base + "oauth_verifier"
147 static let version = base + "version"
148 static let signatureMethod = base + "signatureMethod"
151 /// Cannot declare a required initializer within an extension.
152 /// extension OAuthSwiftCredential: NSCoding {
153 public required convenience init?(coder decoder: NSCoder) {
155 guard let consumerKey = decoder
156 .decodeObject(of: NSString.self,
157 forKey: NSCodingKeys.consumerKey) as String? else {
158 if #available(iOS 9, OSX 10.11, *) {
159 let error = CocoaError.error(.coderValueNotFound)
160 decoder.failWithError(error)
165 guard let consumerSecret = decoder
166 .decodeObject(of: NSString.self,
167 forKey: NSCodingKeys.consumerSecret) as String? else {
168 if #available(iOS 9, OSX 10.11, *) {
169 let error = CocoaError.error(.coderValueNotFound)
170 decoder.failWithError(error)
174 self.init(consumerKey: consumerKey, consumerSecret: consumerSecret)
176 guard let oauthToken = decoder
177 .decodeObject(of: NSString.self,
178 forKey: NSCodingKeys.oauthToken) as String? else {
179 if #available(iOS 9, OSX 10.11, *) {
180 let error = CocoaError.error(.coderValueNotFound)
181 decoder.failWithError(error)
185 self.oauthToken = oauthToken
187 guard let oauthRefreshToken = decoder
188 .decodeObject(of: NSString.self,
189 forKey: NSCodingKeys.oauthRefreshToken) as String? else {
190 if #available(iOS 9, OSX 10.11, *) {
191 let error = CocoaError.error(.coderValueNotFound)
192 decoder.failWithError(error)
196 self.oauthRefreshToken = oauthRefreshToken
198 guard let oauthTokenSecret = decoder
199 .decodeObject(of: NSString.self,
200 forKey: NSCodingKeys.oauthTokenSecret) as String? else {
201 if #available(iOS 9, OSX 10.11, *) {
202 let error = CocoaError.error(.coderValueNotFound)
203 decoder.failWithError(error)
207 self.oauthTokenSecret = oauthTokenSecret
209 guard let oauthVerifier = decoder
210 .decodeObject(of: NSString.self,
211 forKey: NSCodingKeys.oauthVerifier) as String? else {
212 if #available(iOS 9, OSX 10.11, *) {
213 let error = CocoaError.error(.coderValueNotFound)
214 decoder.failWithError(error)
218 self.oauthVerifier = oauthVerifier
220 self.oauthTokenExpiresAt = decoder
221 .decodeObject(of: NSDate.self, forKey: NSCodingKeys.oauthTokenExpiresAt) as Date?
222 self.version = Version(decoder.decodeInt32(forKey: NSCodingKeys.version))
223 if case .oauth1 = version {
224 self.signatureMethod = SignatureMethod(rawValue: (decoder.decodeObject(of: NSString.self, forKey: NSCodingKeys.signatureMethod) as String?) ?? "HMAC_SHA1") ?? .HMAC_SHA1
228 open func encode(with coder: NSCoder) {
229 coder.encode(self.consumerKey, forKey: NSCodingKeys.consumerKey)
230 coder.encode(self.consumerSecret, forKey: NSCodingKeys.consumerSecret)
231 coder.encode(self.oauthToken, forKey: NSCodingKeys.oauthToken)
232 coder.encode(self.oauthRefreshToken, forKey: NSCodingKeys.oauthRefreshToken)
233 coder.encode(self.oauthTokenSecret, forKey: NSCodingKeys.oauthTokenSecret)
234 coder.encode(self.oauthVerifier, forKey: NSCodingKeys.oauthVerifier)
235 coder.encode(self.oauthTokenExpiresAt, forKey: NSCodingKeys.oauthTokenExpiresAt)
236 coder.encode(self.version.toInt32, forKey: NSCodingKeys.version)
237 if case .oauth1 = version {
238 coder.encode(self.signatureMethod.rawValue, forKey: NSCodingKeys.signatureMethod)
241 // } // End NSCoding extension
243 // MARK: Codable protocol
244 enum CodingKeys: String, CodingKey {
248 case oauthRefreshToken
249 case oauthTokenSecret
251 case oauthTokenExpiresAt
253 case signatureMethodRawValue
256 public func encode(to encoder: Encoder) throws {
257 var container = encoder.container(keyedBy: CodingKeys.self)
258 try container.encode(self.consumerKey, forKey: .consumerKey)
259 try container.encode(self.consumerSecret, forKey: .consumerSecret)
260 try container.encode(self.oauthToken, forKey: .oauthToken)
261 try container.encode(self.oauthRefreshToken, forKey: .oauthRefreshToken)
262 try container.encode(self.oauthTokenSecret, forKey: .oauthTokenSecret)
263 try container.encode(self.oauthVerifier, forKey: .oauthVerifier)
264 try container.encodeIfPresent(self.oauthTokenExpiresAt, forKey: .oauthTokenExpiresAt)
265 try container.encode(self.version, forKey: .version)
266 if case .oauth1 = version {
267 try container.encode(self.signatureMethod.rawValue, forKey: .signatureMethodRawValue)
271 public required convenience init(from decoder: Decoder) throws {
272 let container = try decoder.container(keyedBy: CodingKeys.self)
276 self.consumerKey = try container.decode(String.self, forKey: .consumerKey)
277 self.consumerSecret = try container.decode(String.self, forKey: .consumerSecret)
279 self.oauthToken = try container.decode(type(of: self.oauthToken), forKey: .oauthToken)
280 self.oauthRefreshToken = try container.decode(type(of: self.oauthRefreshToken), forKey: .oauthRefreshToken)
281 self.oauthTokenSecret = try container.decode(type(of: self.oauthTokenSecret), forKey: .oauthTokenSecret)
282 self.oauthVerifier = try container.decode(type(of: self.oauthVerifier), forKey: .oauthVerifier)
283 self.oauthTokenExpiresAt = try container.decodeIfPresent(Date.self, forKey: .oauthTokenExpiresAt)
284 self.version = try container.decode(type(of: self.version), forKey: .version)
286 if case .oauth1 = version {
287 self.signatureMethod = SignatureMethod(rawValue: try container.decode(type(of: self.signatureMethod.rawValue), forKey: .signatureMethodRawValue))!
292 /// for OAuth1 parameters must contains sorted query parameters and url must not contains query parameters
293 open func makeHeaders(_ url: URL, method: OAuthSwiftHTTPRequest.Method, parameters: OAuthSwift.Parameters, body: Data? = nil) -> [String: String] {
294 if let factory = headersFactory {
295 return factory.make(url, method: method, parameters: parameters, body: body)
297 switch self.version {
299 return ["Authorization": self.authorizationHeader(method: method, url: url, parameters: parameters, body: body)]
301 return self.oauthToken.isEmpty ? [:] : ["Authorization": "Bearer \(self.oauthToken)"]
305 open func authorizationHeader(method: OAuthSwiftHTTPRequest.Method, url: URL, parameters: OAuthSwift.Parameters, body: Data? = nil) -> String {
306 let timestamp = String(Int64(Date().timeIntervalSince1970))
307 let nonce = OAuthSwiftCredential.generateNonce()
308 return self.authorizationHeader(method: method, url: url, parameters: parameters, body: body, timestamp: timestamp, nonce: nonce)
311 open class func generateNonce() -> String {
312 let uuidString: String = UUID().uuidString
313 return uuidString[0..<8]
316 open func authorizationHeader(method: OAuthSwiftHTTPRequest.Method, url: URL, parameters: OAuthSwift.Parameters, body: Data? = nil, timestamp: String, nonce: String) -> String {
317 assert(self.version == .oauth1)
318 let authorizationParameters = self.authorizationParametersWithSignature(method: method, url: url, parameters: parameters, body: body, timestamp: timestamp, nonce: nonce)
320 var parameterComponents = authorizationParameters.urlEncodedQuery.components(separatedBy: "&") as [String]
321 parameterComponents.sort { $0 < $1 }
323 var headerComponents = [String]()
324 for component in parameterComponents {
325 let subcomponent = component.components(separatedBy: "=") as [String]
326 if subcomponent.count == 2 {
327 headerComponents.append("\(subcomponent[0])=\"\(subcomponent[1])\"")
331 return "OAuth " + headerComponents.joined(separator: ", ")
334 open func authorizationParametersWithSignature(method: OAuthSwiftHTTPRequest.Method, url: URL, parameters: OAuthSwift.Parameters, body: Data? = nil) -> OAuthSwift.Parameters {
335 let timestamp = String(Int64(Date().timeIntervalSince1970))
336 let nonce = OAuthSwiftCredential.generateNonce()
337 return self.authorizationParametersWithSignature(method: method, url: url, parameters: parameters, body: body, timestamp: timestamp, nonce: nonce)
340 open func authorizationParametersWithSignature(method: OAuthSwiftHTTPRequest.Method, url: URL, parameters: OAuthSwift.Parameters, body: Data? = nil, timestamp: String, nonce: String) -> OAuthSwift.Parameters {
341 var authorizationParameters = self.authorizationParameters(body, timestamp: timestamp, nonce: nonce)
343 for (key, value) in parameters {
344 if key.hasPrefix("oauth_") {
345 authorizationParameters.updateValue(value, forKey: key)
349 let combinedParameters = authorizationParameters.join(parameters)
351 authorizationParameters["oauth_signature"] = self.signature(method: method, url: url, parameters: combinedParameters)
353 return authorizationParameters
356 open func authorizationParameters(_ body: Data?, timestamp: String, nonce: String) -> OAuthSwift.Parameters {
357 var authorizationParameters = OAuthSwift.Parameters()
358 authorizationParameters["oauth_version"] = self.version.shortVersion
359 authorizationParameters["oauth_signature_method"] = self.signatureMethod.rawValue
360 authorizationParameters["oauth_consumer_key"] = self.consumerKey
361 authorizationParameters["oauth_timestamp"] = timestamp
362 authorizationParameters["oauth_nonce"] = nonce
363 if let b = body, let hash = self.signatureMethod.hashMethod.hash(data: b) {
364 authorizationParameters["oauth_body_hash"] = hash.base64EncodedString(options: [])
367 if !self.oauthToken.isEmpty {
368 authorizationParameters["oauth_token"] = self.oauthToken
370 return authorizationParameters
373 open func signature(method: OAuthSwiftHTTPRequest.Method, url: URL, parameters: OAuthSwift.Parameters) -> String {
374 let encodedTokenSecret = self.oauthTokenSecret.urlEncoded
375 let encodedConsumerSecret = self.consumerSecret.urlEncoded
377 let signingKey = "\(encodedConsumerSecret)&\(encodedTokenSecret)"
379 var parameterComponents = parameters.urlEncodedQuery.components(separatedBy: "&")
380 parameterComponents.sort {
381 let p0 = $0.components(separatedBy: "=")
382 let p1 = $1.components(separatedBy: "=")
383 if p0.first == p1.first { return p0.last ?? "" < p1.last ?? "" }
384 return p0.first ?? "" < p1.first ?? ""
387 let parameterString = parameterComponents.joined(separator: "&")
388 let encodedParameterString = parameterString.urlEncoded
390 let encodedURL = url.absoluteString.urlEncoded
392 let signatureBaseString = "\(method)&\(encodedURL)&\(encodedParameterString)"
394 let key = signingKey.data(using: .utf8)!
395 let msg = signatureBaseString.data(using: .utf8)!
397 let sha1 = self.signatureMethod.sign(key: key, message: msg)!
398 return sha1.base64EncodedString(options: [])
401 open func isTokenExpired() -> Bool {
402 if let expiresDate = oauthTokenExpiresAt {
403 return expiresDate <= Date()
406 // If no expires date is available we assume the token is still valid since it doesn't have an expiration date to check with.
412 override open func isEqual(_ object: Any?) -> Bool {
413 guard let rhs = object as? OAuthSwiftCredential else {
417 return lhs.consumerKey == rhs.consumerKey
418 && lhs.consumerSecret == rhs.consumerSecret
419 && lhs.oauthToken == rhs.oauthToken
420 && lhs.oauthRefreshToken == rhs.oauthRefreshToken
421 && lhs.oauthTokenSecret == rhs.oauthTokenSecret
422 && lhs.oauthTokenExpiresAt == rhs.oauthTokenExpiresAt
423 && lhs.oauthVerifier == rhs.oauthVerifier
424 && lhs.version == rhs.version
425 && lhs.signatureMethod == rhs.signatureMethod