Skip to content

Commit 163c70e

Browse files
authored
[Swift 5] create sample for URLSsession and Alamofire bearer authentication (#8302)
* [swift] create a sample of bearer token authentication with URLSession * [swift] create a sample of bearer token authentication with URLSession * [swift] create a sample of bearer token authentication with URLSession * [swift] create a sample of bearer token authentication with URLSession * [swift] create a sample of bearer token authentication with Alamofire * [swift] create a sample of bearer token authentication with Alamofire
1 parent 72d6cff commit 163c70e

File tree

7 files changed

+294
-52
lines changed

7 files changed

+294
-52
lines changed

samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/Podfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ SPEC CHECKSUMS:
2020

2121
PODFILE CHECKSUM: 509bec696cc1d8641751b52e4fe4bef04ac4542c
2222

23-
COCOAPODS: 1.9.0
23+
COCOAPODS: 1.10.0

samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj

+56-51
Large diffs are not rendered by default.

samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests/SwaggerClient/AppDelegate.swift

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import UIKit
10+
import PetstoreClient
1011

1112
@UIApplicationMain
1213
class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -15,6 +16,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
1516

1617
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
1718
// Override point for customization after application launch.
19+
20+
// Customize requestBuilderFactory
21+
PetstoreClientAPI.requestBuilderFactory = BearerRequestBuilderFactory()
22+
1823
return true
1924
}
2025

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//
2+
// BearerDecodableRequestBuilder.swift
3+
// SwaggerClient
4+
//
5+
// Created by Bruno Coelho on 31/12/2020.
6+
// Copyright © 2020 Swagger. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Alamofire
11+
import PetstoreClient
12+
13+
class BearerRequestBuilderFactory: RequestBuilderFactory {
14+
func getNonDecodableBuilder<T>() -> RequestBuilder<T>.Type {
15+
BearerRequestBuilder<T>.self
16+
}
17+
18+
func getBuilder<T: Decodable>() -> RequestBuilder<T>.Type {
19+
BearerDecodableRequestBuilder<T>.self
20+
}
21+
}
22+
23+
class BearerRequestBuilder<T>: AlamofireRequestBuilder<T> {
24+
override func createSessionManager() -> SessionManager {
25+
let sessionManager = super.createSessionManager()
26+
27+
let bearerTokenHandler = BearerTokenHandler()
28+
sessionManager.adapter = bearerTokenHandler
29+
sessionManager.retrier = bearerTokenHandler
30+
31+
return sessionManager
32+
}
33+
}
34+
35+
class BearerDecodableRequestBuilder<T: Decodable>: AlamofireDecodableRequestBuilder<T> {
36+
override func createSessionManager() -> SessionManager {
37+
let sessionManager = super.createSessionManager()
38+
39+
let bearerTokenHandler = BearerTokenHandler()
40+
sessionManager.adapter = bearerTokenHandler
41+
sessionManager.retrier = bearerTokenHandler
42+
43+
return sessionManager
44+
}
45+
}
46+
47+
class BearerTokenHandler: RequestAdapter, RequestRetrier {
48+
private static var bearerToken: String? = nil
49+
50+
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
51+
if let bearerToken = Self.bearerToken {
52+
var urlRequest = urlRequest
53+
urlRequest.setValue("Bearer \(bearerToken)", forHTTPHeaderField: "Authorization")
54+
return urlRequest
55+
}
56+
57+
return urlRequest
58+
}
59+
60+
func should(_: SessionManager, retry request: Request, with _: Error, completion: @escaping RequestRetryCompletion) {
61+
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
62+
Self.startRefreshingToken { isTokenRefreshed in
63+
completion(isTokenRefreshed, 0.0)
64+
}
65+
} else {
66+
completion(false, 0.0)
67+
}
68+
}
69+
70+
private static func startRefreshingToken(completionHandler: @escaping (Bool) -> Void) {
71+
// Get a bearer token
72+
let dummyBearerToken = "..."
73+
74+
bearerToken = dummyBearerToken
75+
PetstoreClientAPI.customHeaders["Authorization"] = "Bearer \(dummyBearerToken)"
76+
77+
completionHandler(true)
78+
}
79+
}

samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClient.xcodeproj/project.pbxproj

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */; };
1818
6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */; };
1919
6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */; };
20+
A5465867259E09C600C3929B /* BearerDecodableRequestBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */; };
2021
A5EA12642419439700E30FC3 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12622419439700E30FC3 /* FileUtils.swift */; };
2122
A5EA12652419439700E30FC3 /* UIImage+Extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA12632419439700E30FC3 /* UIImage+Extras.swift */; };
2223
FB5CCC7EFA680BB2746B695B /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */; };
@@ -49,6 +50,7 @@
4950
6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAPITests.swift; sourceTree = "<group>"; };
5051
7F98CC8B18E5FA9213F6A68D /* Pods_SwaggerClient.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClient.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5152
83FDC034BBA2A07AE9975250 /* Pods_SwaggerClientTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwaggerClientTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
53+
A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BearerDecodableRequestBuilder.swift; sourceTree = "<group>"; };
5254
A5EA12622419439700E30FC3 /* FileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = "<group>"; };
5355
A5EA12632419439700E30FC3 /* UIImage+Extras.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+Extras.swift"; sourceTree = "<group>"; };
5456
ACB80AC61FA8D8916D4559AA /* Pods-SwaggerClient.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClient.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClient/Pods-SwaggerClient.release.xcconfig"; sourceTree = "<group>"; };
@@ -123,6 +125,7 @@
123125
children = (
124126
6D4EFB941C692C6300B96B06 /* AppDelegate.swift */,
125127
6D4EFB961C692C6300B96B06 /* ViewController.swift */,
128+
A5465866259E09C600C3929B /* BearerDecodableRequestBuilder.swift */,
126129
6D4EFB981C692C6300B96B06 /* Main.storyboard */,
127130
6D4EFB9B1C692C6300B96B06 /* Assets.xcassets */,
128131
6D4EFB9D1C692C6300B96B06 /* LaunchScreen.storyboard */,
@@ -317,6 +320,7 @@
317320
buildActionMask = 2147483647;
318321
files = (
319322
6D4EFB971C692C6300B96B06 /* ViewController.swift in Sources */,
323+
A5465867259E09C600C3929B /* BearerDecodableRequestBuilder.swift in Sources */,
320324
6D4EFB951C692C6300B96B06 /* AppDelegate.swift in Sources */,
321325
);
322326
runOnlyForDeploymentPostprocessing = 0;

samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests/SwaggerClient/AppDelegate.swift

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import UIKit
10+
import PetstoreClient
1011

1112
@UIApplicationMain
1213
class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -15,6 +16,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
1516

1617
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
1718
// Override point for customization after application launch.
19+
20+
// Customize requestBuilderFactory
21+
PetstoreClientAPI.requestBuilderFactory = BearerRequestBuilderFactory()
22+
1823
return true
1924
}
2025

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//
2+
// BearerDecodableRequestBuilder.swift
3+
// SwaggerClient
4+
//
5+
// Created by Bruno Coelho on 31/12/2020.
6+
// Copyright © 2020 Swagger. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import PetstoreClient
11+
12+
class BearerRequestBuilderFactory: RequestBuilderFactory {
13+
func getNonDecodableBuilder<T>() -> RequestBuilder<T>.Type {
14+
BearerRequestBuilder<T>.self
15+
}
16+
17+
func getBuilder<T: Decodable>() -> RequestBuilder<T>.Type {
18+
BearerDecodableRequestBuilder<T>.self
19+
}
20+
}
21+
22+
class BearerRequestBuilder<T>: URLSessionRequestBuilder<T> {
23+
override func execute(_ apiResponseQueue: DispatchQueue = PetstoreClientAPI.apiResponseQueue, _ completion: @escaping (Result<Response<T>, Error>) -> Void) {
24+
25+
// Before making the request, we can validate if we have a bearer token to be able to make a request
26+
BearerTokenHandler.refreshTokenIfDoesntExist {
27+
28+
// Here we make the request
29+
super.execute(apiResponseQueue) { result in
30+
31+
switch result {
32+
case .success:
33+
// If we got a successful response, we send the response to the completion block
34+
completion(result)
35+
36+
case let .failure(error):
37+
38+
// If we got a failure response, we will analyse the error to see what we should do with it
39+
if case let ErrorResponse.error(_, data, response, error) = error {
40+
41+
// If the error is an ErrorResponse.error() we will analyse it to see if it's a 401, and if it's a 401, we will refresh the token and retry the request
42+
BearerTokenHandler.refreshTokenIfUnauthorizedRequestResponse(
43+
data: data,
44+
response: response,
45+
error: error
46+
) { wasTokenRefreshed in
47+
48+
if wasTokenRefreshed {
49+
// If the token was refreshed, it's because it was a 401 error, so we refreshed the token, and we are going to retry the request by calling self.execute()
50+
self.execute(apiResponseQueue, completion)
51+
} else {
52+
// If the token was not refreshed, it's because it was not a 401 error, so we send the response to the completion block
53+
completion(result)
54+
}
55+
}
56+
} else {
57+
// If it's an unknown error, we send the response to the completion block
58+
completion(result)
59+
}
60+
61+
}
62+
}
63+
}
64+
}
65+
}
66+
67+
class BearerDecodableRequestBuilder<T: Decodable>: URLSessionDecodableRequestBuilder<T> {
68+
override func execute(_ apiResponseQueue: DispatchQueue = PetstoreClientAPI.apiResponseQueue, _ completion: @escaping (Result<Response<T>, Error>) -> Void) {
69+
70+
// Before making the request, we can validate if we have a bearer token to be able to make a request
71+
BearerTokenHandler.refreshTokenIfDoesntExist {
72+
73+
// Here we make the request
74+
super.execute(apiResponseQueue) { result in
75+
76+
switch result {
77+
case .success:
78+
// If we got a successful response, we send the response to the completion block
79+
completion(result)
80+
81+
case let .failure(error):
82+
83+
// If we got a failure response, we will analyse the error to see what we should do with it
84+
if case let ErrorResponse.error(_, data, response, error) = error {
85+
86+
// If the error is an ErrorResponse.error() we will analyse it to see if it's a 401, and if it's a 401, we will refresh the token and retry the request
87+
BearerTokenHandler.refreshTokenIfUnauthorizedRequestResponse(
88+
data: data,
89+
response: response,
90+
error: error
91+
) { wasTokenRefreshed in
92+
93+
if wasTokenRefreshed {
94+
// If the token was refreshed, it's because it was a 401 error, so we refreshed the token, and we are going to retry the request by calling self.execute()
95+
self.execute(apiResponseQueue, completion)
96+
} else {
97+
// If the token was not refreshed, it's because it was not a 401 error, so we send the response to the completion block
98+
completion(result)
99+
}
100+
}
101+
} else {
102+
// If it's an unknown error, we send the response to the completion block
103+
completion(result)
104+
}
105+
106+
}
107+
}
108+
}
109+
}
110+
}
111+
112+
class BearerTokenHandler {
113+
private static var bearerToken: String? = nil
114+
115+
static func refreshTokenIfDoesntExist(completionHandler: @escaping () -> Void) {
116+
if bearerToken != nil {
117+
completionHandler()
118+
} else {
119+
startRefreshingToken {
120+
completionHandler()
121+
}
122+
}
123+
}
124+
125+
static func refreshTokenIfUnauthorizedRequestResponse(data: Data?, response: URLResponse?, error: Error?, completionHandler: @escaping (Bool) -> Void) {
126+
if let response = response as? HTTPURLResponse, response.statusCode == 401 {
127+
startRefreshingToken {
128+
completionHandler(true)
129+
}
130+
} else {
131+
completionHandler(false)
132+
}
133+
}
134+
135+
private static func startRefreshingToken(completionHandler: @escaping () -> Void) {
136+
// Get a bearer token
137+
let dummyBearerToken = "..."
138+
139+
bearerToken = dummyBearerToken
140+
PetstoreClientAPI.customHeaders["Authorization"] = "Bearer \(dummyBearerToken)"
141+
142+
completionHandler()
143+
}
144+
}

0 commit comments

Comments
 (0)