Skip to content

Instantly share code, notes, and snippets.

@djk12587
Last active September 17, 2022 15:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djk12587/4d7c099be8f135973d7b287a22ab4fd0 to your computer and use it in GitHub Desktop.
Save djk12587/4d7c099be8f135973d7b287a22ab4fd0 to your computer and use it in GitHub Desktop.
PopNetworking - Reauthentication (Deprecated)
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
}
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
}
}
}
}
@djk12587
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment