Last active
September 17, 2022 15:16
-
-
Save djk12587/4d7c099be8f135973d7b287a22ab4fd0 to your computer and use it in GitHub Desktop.
PopNetworking - Reauthentication (Deprecated)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import PopNetworking | |
public enum ReauthorizationMethod { | |
/// Your access token is invalid and needs to be refreshed. Once reauthorization is complete your request will be retried | |
case refreshAuthorization | |
/// Your access token is valid, retry the request | |
case retryRequest | |
/// Do nothing and let the request fail | |
case doNothing | |
} | |
/** | |
A protocol that helps ensure a `URLRequest`'s authorization is always up to date. ``AccessTokenVerification`` can be applied to a ``NetworkingSession``. Initialize a ``ReauthenticationHandler`` and set the instance of ``ReauthenticationHandler`` to a ``NetworkingSession``'s ``NetworkingRequestAdapter`` & ``NetworkingRequestRetrier`` | |
``` | |
// example usage | |
let reauthenticationHandler = ReauthenticationHandler(accessTokenVerifier: yourAccessTokenVerifier()) | |
let networkingSession = NetworkingSession(requestAdapter: reauthenticationHandler, | |
requestRetrier: reauthenticationHandler) | |
``` | |
- Attention: You are responsible for supplying and saving your own access token. | |
*/ | |
public protocol AccessTokenVerification { | |
/// This type should be the route/endpoint your personal server exposes to reauthenticate an expired access token | |
associatedtype ReauthenticationRoute: NetworkingRoute | |
/// The ``NetworkingRoute`` that will be used to refresh your authorization to your server. This ``NetworkingRoute`` should return an object that contains new authorization data for your server | |
/// | |
/// If ``shouldReauthenticate(urlRequest:dueTo:urlResponse:retryCount:)`` returns `false`, ``reauthenticationRoute-swift.property`` will be executed, and ``saveReauthentication(result:)`` will provide the result. | |
var reauthenticationRoute: ReauthenticationRoute { get } | |
/// Before a `URLRequest` is sent this function allows you to check the validity of your access token. | |
/// | |
/// Do nothing if your access token is valid | |
/// | |
/// - Throws: If your access token is expired, throw an `Error`. Throwing an error will trigger ``shouldReauthenticate(urlRequest:dueTo:urlResponse:retryCount:)`` with the error you threw. | |
func validateAccessToken() throws | |
/// Before a `URLRequest` is sent, this function asks if the request requires authentication | |
/// | |
/// - Parameters: | |
/// - urlRequest: The `URLRequest` that will be sent over the wire | |
/// | |
/// - Returns: A `Bool` indicating if the supplied `URLRequest` requires authentication | |
/// | |
/// If `false` is returned, ``isAuthorizationValid(for:)`` && ``setAuthorization(for:)`` will be skipped and the `URLRequest` will be sent | |
func isAuthorizationRequired(for urlRequest: URLRequest) -> Bool | |
/// Before a `URLRequest` is sent, this function asks if the `URLRequest`'s authentication is valid. | |
/// | |
/// - Parameters: | |
/// - urlRequest: The `URLRequest` that will be sent over the wire | |
/// | |
/// If `false` is returned, ``setAuthorization(for:)`` will be executed. If `true` is returned, the `URLRequest` will be sent. | |
/// | |
/// - Returns: A `Bool` indicating if the supplied `URLRequest` authorization is valid. | |
func isAuthorizationValid(for urlRequest: URLRequest) -> Bool | |
/// Before a `URLRequest` is sent, this function allows you to update the authorization of the `URLRequest` | |
/// | |
/// - Parameters: | |
/// - urlRequest: The `URLRequest` that will be sent over the wire | |
/// | |
/// An error can be thrown incase there was a problem updating the authorization | |
func setAuthorization(for urlRequest: inout URLRequest) throws | |
/// Your `URLRequest` has failed. Return a `Bool` incase the `URLRequest` should be retried or not. | |
/// | |
/// - Parameters: | |
/// - urlRequest: A`URLRequest` that failed | |
/// - error: The reason the `URLRequest` failed | |
/// - urlResponse: The `URLRequest`'s `HTTPURLResponse` | |
/// - retryCount: The number of times this particular `URLRequest` has been retried | |
/// | |
/// - Returns: A `Bool` indicating if the request requires reauthentication. If `true` the ``reauthenticationRoute-swift.property`` will be executed. Once the ``reauthenticationRoute-swift.property`` successfully finishes, the `URLRequest` will be retried. | |
/// | |
/// - Attention: If you never return `false` there is chance your `URLRequest` will attempt to retry in an infinite loop. | |
func determineReauthorizationMethod(urlRequest: URLRequest?, dueTo error: Error, urlResponse: HTTPURLResponse?, retryCount: Int) -> ReauthorizationMethod | |
/// Informs you of the result of ``reauthenticationRoute-swift.property``. Use this function to save your updated authorization data. | |
/// | |
/// - Parameters: | |
/// - result: The result of ``reauthenticationRoute-swift.property`` | |
/// | |
/// - Returns: A `Bool` indicating if saving your authorization data was successful or not. If saving your authorization data failed return `false`, and the request will not be retried. | |
/// | |
/// - Attention: It is best practice to save your newly aquired authorization data | |
func saveReauthentication(result: Result<ReauthenticationRoute.ResponseSerializer.SerializedObject, Error>) async -> Bool | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import PopNetworking | |
/** | |
A class that helps ensure a `URLRequest`'s authorization is always up to date. See ``AccessTokenVerification`` for more details. | |
``` | |
// example usage | |
let reauthenticationHandler = ReauthenticationHandler(accessTokenVerifier: yourAccessTokenVerifier()) | |
let networkingSession = NetworkingSession(requestInterceptor: reauthenticationHandler) | |
``` | |
*/ | |
public actor ReauthenticationHandler<AccessTokenVerifier: AccessTokenVerification>: NetworkingRequestInterceptor { | |
private var activeReauthenticationTask: Task<NetworkingRequestRetrierResult, Never>? | |
private let accessTokenVerifier: AccessTokenVerifier | |
public init(accessTokenVerifier: AccessTokenVerifier) { | |
self.accessTokenVerifier = accessTokenVerifier | |
} | |
// MARK: - RequestAdapter | |
public func adapt(urlRequest: URLRequest) throws -> URLRequest { | |
guard accessTokenVerifier.isAuthorizationRequired(for: urlRequest) else { return urlRequest } | |
try accessTokenVerifier.validateAccessToken() | |
if accessTokenVerifier.isAuthorizationValid(for: urlRequest) { | |
return urlRequest | |
} | |
var urlRequest = urlRequest | |
try accessTokenVerifier.setAuthorization(for: &urlRequest) | |
return urlRequest | |
} | |
// MARK: - RequestRetrier | |
public func retry(urlRequest: URLRequest?, | |
dueTo error: Error, | |
urlResponse: HTTPURLResponse?, | |
retryCount: Int) async -> NetworkingRequestRetrierResult { | |
let reauthorizationResult = accessTokenVerifier.determineReauthorizationMethod(urlRequest: urlRequest, | |
dueTo: error, | |
urlResponse: urlResponse, | |
retryCount: retryCount) | |
switch reauthorizationResult { | |
case .refreshAuthorization: | |
return await reauthenticate() | |
case .retryRequest: | |
return .retry | |
case .doNothing: | |
return .doNotRetry | |
} | |
} | |
private func reauthenticate() async -> NetworkingRequestRetrierResult { | |
if let activeReauthenticationTask = activeReauthenticationTask, !activeReauthenticationTask.isCancelled { | |
return await activeReauthenticationTask.value | |
} | |
else { | |
let reauthTask = createReauthenticationTask() | |
activeReauthenticationTask = reauthTask | |
return await reauthTask.value | |
} | |
} | |
private func createReauthenticationTask() -> Task<NetworkingRequestRetrierResult, Never> { | |
Task { | |
defer { activeReauthenticationTask = nil } | |
let reauthResult = await accessTokenVerifier.reauthenticationRoute.result | |
let saveWasSuccessful = await accessTokenVerifier.saveReauthentication(result: reauthResult) | |
switch reauthResult { | |
case .success where saveWasSuccessful: | |
return .retry | |
default: | |
return .doNotRetry | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This works perfectly fine, but I don't think this level of abstraction is appropriate. It feels too nuanced and hard for others to grasp the concept of how it works.