@@ -9,23 +9,108 @@ import Foundation
99
1010// MARK: - Authenticator
1111
12+ /// Authenticators provide the concrete implementation of authentication,
13+ /// credential management and refreshing of expired credentials for
14+ /// `AuthenticationInterceptor`.
1215public protocol Authenticator : Sendable {
1316
17+ /// Persistent credential used to authorize requests.
18+ ///
19+ /// This is often a combination of access and refresh tokens,
20+ /// an API key, an authentication string, passphrase, etc..
21+ ///
22+ /// Credentials conforming to ``RefreshableCredential`` can indicate
23+ /// their validity and thus request a credential refresh early, resulting in better
24+ /// networking performance.
1425 associatedtype Credential
15-
26+
27+ /// Return the latest available credential from storage (eg. cache).
28+ /// - Returns: ``Credential`` if available or `nil`
1629 func getCredential( ) async -> Credential ?
30+
31+ /// Stores the new credential to storage (eg. cache).
32+ ///
33+ /// If the new credential is `nil`, this function MUST remove the
34+ /// cached credential from storage. Subsequent calls to ``getCredential()``
35+ /// must then return `nil`.
36+ ///
37+ /// - Parameter newCredential: New credential to store or `nil` if
38+ /// credential should be deleted.
1739 func storeCredential( _ newCredential: Credential ? ) async
18-
40+
41+ /// Applies the credential to a URL request.
42+ ///
43+ /// When using HTTP with Bearer authorization, this function is responsible for
44+ /// adding the `Authorization` header to the request.
45+ ///
46+ /// - Parameters:
47+ /// - credential: Credential to be added to the request
48+ /// - request: Request to modify
1949 func apply( credential: Credential , to request: inout URLRequest ) async throws ( NetworkError)
50+
51+ /// Refreshes the expired or invalid credential.
52+ ///
53+ /// This function is responsible for refreshing the credential - the way this is done
54+ /// is up to the implementation. Most often it will involve making a network call
55+ /// to a backend authorization service.
56+ ///
57+ /// - note: Parameter `credential` may contain a valid credential, if the expiration
58+ /// date is known and the credential is able to request a refresh. See ``RefreshableCredential``.
59+ ///
60+ /// - important: This session will block other requests while refresh is pending.
61+ /// As a result, all potential refresh requests must be handled by another network session.
62+ ///
63+ /// - Parameter credential: Credential that needs to be refreshed.
64+ /// - Returns: Refreshed and valid credential
65+ /// - Throws: This function can throw a ``NetworkError``, if refreshing the credential fails, or
66+ /// the credential cannot be refreshed.
2067 func refresh( credential: Credential ) async throws ( NetworkError) -> Credential
21- func didRequest( _ request: inout URLRequest , failDueToAuthenticationError: HTTPError ) -> Bool
68+
69+ /// Checks whether invalid response from the backend is an authentication error.
70+ ///
71+ /// The simplest implementation would be a check if the status code is `401 Unauthorized`.
72+ /// This implementation, however, has a flaw. For example - in case the system gates access
73+ /// to resources which require a certain level of authorization, it is possible that a resource is unavailable,
74+ /// but the client can authenticate with a higher level of permissions to access it.
75+ ///
76+ /// - important: It is **highly recommended** to inspect the backend response and decide
77+ /// whether the credential is invalid/expired, or the resource is not accessible to the current user
78+ /// and the credential itself is correct.
79+ ///
80+ /// - Parameters:
81+ /// - request: Failed URL request
82+ /// - failDueToAuthenticationError: Remote error containing the backend response and status code
83+ /// - Returns: `true` if it can be safely determined, that the failure occured due to invalid credential
84+ func didRequest( _ request: inout URLRequest , failDueToAuthenticationError error: HTTPError ) -> Bool
85+
86+ /// Verifies if a request is authenticated with a given credential.
87+ ///
88+ /// The simplest implementation in a system using HTTP with Bearer authorization
89+ /// would be checking if access token from ``Credential`` matches the token
90+ /// in `Authorization` header.
91+ ///
92+ /// - Parameters:
93+ /// - request: URL request to verify
94+ /// - credential: Credential which is expected to authenticate the request
95+ /// - Returns: `true` if request is authenticated using the `credential`, or `false` otherwise
96+ /// (if the request is unauthenticated or the credentials do not match).
2297 func isRequest( _ request: inout URLRequest , authenticatedWith credential: Credential ) -> Bool
98+
99+ /// Notifies the user that credential could not be refreshed and failed due to an error.
100+ ///
101+ /// This function is often responsible for changing the app state to show an alert,
102+ /// open the verification/login dialog, or cleaning up now invalid resources
103+ /// (eg. authorized image cache).
104+ ///
105+ /// - Parameter error: Remote error containing the backend response and status code
106+ /// when trying to refresh invalid credential, which could not be refreshed.
23107 func refresh( didFailDueToError error: HTTPError ) async
24108
25109}
26110
27111// MARK: - No authenticator
28112
113+ /// Empty implementation of authenticator for unauthorized network sessions.
29114public final class NoAuthenticator : Authenticator {
30115
31116 public typealias Credential = Void
@@ -43,8 +128,20 @@ public final class NoAuthenticator: Authenticator {
43128
44129// MARK: - Refreshable credential
45130
131+ /// Denotes that a credential is refreshable and is capable of telling whether a refresh
132+ /// is required before making an invalid network call.
133+ ///
134+ /// Conforming a `Credential` to this protocol is not required, however, it will
135+ /// improve the networking performance by refreshing the token early, before a failed
136+ /// API call.
46137public protocol RefreshableCredential {
47-
138+
139+ /// This property should return `true` if the credential knows that a refresh
140+ /// is required.
141+ ///
142+ /// In the simplest case it can contain logic comparing expiration date
143+ /// with the current date minus a time delta (eg. 5 minutes before expiration).
144+ ///
48145 var requiresRefresh : Bool { get }
49146
50147}
0 commit comments