Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update AccessTokenPlugin #1611

Merged
merged 11 commits into from Apr 10, 2018
3 changes: 3 additions & 0 deletions Changelog.md
Expand Up @@ -2,6 +2,9 @@

### Changed
- **Breaking Change** Changed `Response`s filter method parameter to use a generic `RangeExpression` that accepts any range type. [#1624](https://github.com/Moya/Moya/pull/1624) by [@LucianoPAlmeida](https://github.com/LucianoPAlmeida).
# [11.0.3] - 2018-04-07
### Added
- Added custom authorization case to AuthorizationType inside AccessTokenPlugin. [#1611](https://github.com/Moya/Moya/pull/1611) by [@SeRG1k17](https://github.com/SeRG1k17).

# [11.0.2] - 2018-04-01
### Fixed
Expand Down
35 changes: 25 additions & 10 deletions Sources/Moya/Plugins/AccessTokenPlugin.swift
Expand Up @@ -13,15 +13,27 @@ public protocol AccessTokenAuthorizable {
// MARK: - AuthorizationType

/// An enum representing the header to use with an `AccessTokenPlugin`
public enum AuthorizationType: String {
public enum AuthorizationType {
/// No header.
case none

/// The `"Basic"` header.
case basic = "Basic"
case basic

/// The `"Bearer"` header.
case bearer = "Bearer"
case bearer

/// Custom header implementation.
case custom(String)

public var value: String? {
switch self {
case .none: return nil
case .basic: return "Basic"
case .bearer: return "Bearer"
case .custom(let customValue): return customValue
}
}
}

// MARK: - AccessTokenPlugin
Expand All @@ -30,8 +42,9 @@ public enum AuthorizationType: String {
A plugin for adding basic or bearer-type authorization headers to requests. Example:

```
Authorization: Bearer <token>
Authorization: Basic <token>
Authorization: Bearer <token>
Authorization: <Сustom> <token>
```

*/
Expand All @@ -46,7 +59,7 @@ public struct AccessTokenPlugin: PluginType {
- parameters:
- tokenClosure: A closure returning the token to be applied in the pattern `Authorization: <AuthorizationType> <token>`
*/
public init(tokenClosure: @escaping @autoclosure () -> String) {
public init(tokenClosure: @escaping () -> String) {
self.tokenClosure = tokenClosure
}

Expand All @@ -59,16 +72,18 @@ public struct AccessTokenPlugin: PluginType {
- returns: The modified `URLRequest`.
*/
public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {

guard let authorizable = target as? AccessTokenAuthorizable else { return request }

let authorizationType = authorizable.authorizationType

var request = request

switch authorizationType {
case .basic, .bearer:
let authValue = authorizationType.rawValue + " " + tokenClosure()
request.addValue(authValue, forHTTPHeaderField: "Authorization")
case .basic, .bearer, .custom:
if let value = authorizationType.value {
let authValue = value + " " + tokenClosure()
request.addValue(authValue, forHTTPHeaderField: "Authorization")
}
case .none:
break
}
Expand Down
49 changes: 33 additions & 16 deletions Tests/AccessTokenPluginSpec.swift
Expand Up @@ -14,37 +14,54 @@ final class AccessTokenPluginSpec: QuickSpec {
let authorizationType: AuthorizationType
}

override func spec() {
let token = "eyeAm.AJsoN.weBTOKen"
let plugin = AccessTokenPlugin(tokenClosure: token)
let token = "eyeAm.AJsoN.weBTOKen"
lazy var plugin = AccessTokenPlugin { self.token }

override func spec() {
it("doesn't add an authorization header to TargetTypes by default") {
let target = GitHub.zen
let request = URLRequest(url: target.baseURL)
let preparedRequest = plugin.prepare(request, target: target)
let preparedRequest = self.plugin.prepare(request, target: target)
expect(preparedRequest.allHTTPHeaderFields).to(beNil())
}

it("doesn't add an authorization header to AccessTokenAuthorizables when AuthorizationType is .none") {
let target = TestTarget(authorizationType: .none)
let request = URLRequest(url: target.baseURL)
let preparedRequest = plugin.prepare(request, target: target)
let authorizationType: AuthorizationType = .none
let preparedRequest = self.createPreparedRequest(for: authorizationType)

expect(authorizationType.value).to(beNil())
expect(preparedRequest.allHTTPHeaderFields).to(beNil())
}

it("adds a basic authorization header to AccessTokenAuthorizables when AuthorizationType is .basic") {
let authorizationType: AuthorizationType = .basic
let preparedRequest = self.createPreparedRequest(for: authorizationType)

let authValue = authorizationType.value!
expect(preparedRequest.allHTTPHeaderFields) == ["Authorization": "\(authValue) \(self.token)"]
}

it("adds a bearer authorization header to AccessTokenAuthorizables when AuthorizationType is .bearer") {
let target = TestTarget(authorizationType: .bearer)
let request = URLRequest(url: target.baseURL)
let preparedRequest = plugin.prepare(request, target: target)
expect(preparedRequest.allHTTPHeaderFields) == ["Authorization": "Bearer eyeAm.AJsoN.weBTOKen"]
let authorizationType: AuthorizationType = .bearer
let preparedRequest = self.createPreparedRequest(for: authorizationType)

let authValue = authorizationType.value!
expect(preparedRequest.allHTTPHeaderFields) == ["Authorization": "\(authValue) \(self.token)"]
}

it("adds a basic authorization header to AccessTokenAuthorizables when AuthorizationType is .basic") {
let target = TestTarget(authorizationType: .basic)
let request = URLRequest(url: target.baseURL)
let preparedRequest = plugin.prepare(request, target: target)
expect(preparedRequest.allHTTPHeaderFields) == ["Authorization": "Basic eyeAm.AJsoN.weBTOKen"]
it("adds a custom authorization header to AccessTokenAuthorizables when AuthorizationType is .custom") {
let authorizationType: AuthorizationType = .custom("CustomAuthorizationHeader")
let preparedRequest = self.createPreparedRequest(for: authorizationType)

let authValue = authorizationType.value!
expect(preparedRequest.allHTTPHeaderFields) == ["Authorization": "\(authValue) \(self.token)"]
}
}

private func createPreparedRequest(for type: AuthorizationType) -> URLRequest {
let target = TestTarget(authorizationType: type)
let request = URLRequest(url: target.baseURL)

return plugin.prepare(request, target: target)
}
}
11 changes: 11 additions & 0 deletions docs/Authentication.md
Expand Up @@ -35,6 +35,7 @@ let provider = MoyaProvider<YourAPI>(plugins: [CredentialsPlugin { target -> URL
Another common method of authentication is by using an access token.
Moya provides an `AccessTokenPlugin` that supports both `Bearer` authentication
using a [JWT](https://jwt.io/introduction/) and `Basic` authentication for API keys.
Also there is support for custom authorization types.

There are two steps required to start using an `AccessTokenPlugin`.

Expand All @@ -53,6 +54,7 @@ for returning the token to be applied to the header of the request.
extension YourAPI: TargetType, AccessTokenAuthorizable {
case targetThatNeedsBearerAuth
case targetThatNeedsBasicAuth
case targetThatNeedsCustomAuth
case targetDoesNotNeedAuth

var authorizationType: AuthorizationType {
Expand All @@ -61,6 +63,8 @@ extension YourAPI: TargetType, AccessTokenAuthorizable {
return .bearer
case .targetThatNeedsBasicAuth:
return .basic
case .targetThatNeedsCustomAuth:
return .custom("CustomAuthorizationType")
case .targetDoesNotNeedAuth:
return .none
}
Expand All @@ -86,6 +90,13 @@ Basic requests are authorized by adding a HTTP header of the following form:
Authorization: Basic <token>
```

**Custom API Key Auth**
Custom requests are authorized by adding a HTTP header of the following form:

```
Authorization: <Custom> <token>
```

## OAuth

OAuth is quite a bit trickier. It involves a multi step process that is often
Expand Down