Alamofire 5 (refined) Advanced Usage with OAuth2 token refreshing mechanism example

Created on 27 Feb 2020  路  13Comments  路  Source: Alamofire/Alamofire

What did you do?

Searched through issues & SO for possible explanations

What did you expect to happen?

To have new & more detailed example of handling OAuth2 (and similar) refreshing tokens mechanism to Advanced Usage docs

What happened instead?

With recent updated of Advanced Usage docs - code related to refreshing tokens was removed.

Alamofire Environment

Alamofire version: 5
Xcode version: 11.0
Swift version: 5.2
Platform(s) running Alamofire:
macOS version running Xcode:

Demo Project

Basically, here I have one question regarding this NSLock thing - why is it necessarily needed here? And what is changed in Alamofire 5 - maybe we can replace it with something?

    // MARK: - RequestRetrier

    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        lock.lock() ; defer { lock.unlock() }

        if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
            requestsToRetry.append(completion)

            if !isRefreshing {
                refreshTokens { [weak self] succeeded, accessToken, refreshToken in
                    guard let strongSelf = self else { return }

                    strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }

                    if let accessToken = accessToken, let refreshToken = refreshToken {
                        strongSelf.accessToken = accessToken
                        strongSelf.refreshToken = refreshToken
                    }

                    strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
                    strongSelf.requestsToRetry.removeAll()
                }
            }
        } else {
            completion(false, 0.0)
        }
    }
support

Most helpful comment

Hi @murthyveda2000, I haven't had a chance to get started on this yet. Once I do, I'll post back to this ticket. I'd like to get it built some time in April to ship with Alamofire 5.2, but I can't say for certain that I can make that happen yet.

All 13 comments

Like all locks, the lock there guards against other other callers modifying the instance at the same time. In this case, other retry attempts modifying state like requestsToRetry.

We hope to offer a prebuilt solution for this sort of retry scenario in the future, but the example wasn't great and was never supposed to be used in the production, yet it was often copy pasted into project code, so it was removed.

@jshier thanks for your response!

I think community would love an in-built mechanism that would support for generic authentication protocols. I'd love to work on that feature & propose ideas/PRs if it is feasible :)

Would you mind directing me into existing (or closed) issues/PRs that are directed the same way?
And how feasible is to start working on such a (big) feature?

Hi @nugmanoff, thank you for the interest.

With the complexity of this particular feature, I think it best to have someone on the core team put it together. We're planning to get this built as part of a 5.2 or 5.3 release with it most likely built by myself. It's going to be tricky to get all the threading built just right and to abstract the authentication logic in a simple way. There's also a few tricks with making sure to hop out of all the locks before calling the completion closures to avoid reentrant deadlocks where the completion closures may execute more requests. Anyways, there's a lot of non-obvious complexity to take into consideration which is again why we should probably have the core team take a stab at this one.

More to come, thanks again! 馃嵒

@cnoon Thank you for such a prompt response and great explanation of the matter at hand!

Good luck on this one!

@cnoon , is there a way to follow progress on this issue?
I'm looking to integrate Alamofire into my project but would like to wait until OAuth refreshTokens are easier to handle.

Hi @murthyveda2000, I haven't had a chance to get started on this yet. Once I do, I'll post back to this ticket. I'd like to get it built some time in April to ship with Alamofire 5.2, but I can't say for certain that I can make that happen yet.

Hi, I also havn't found a solution for a working refresh flow.
I always introducing deadlocks with the Sessions queues.
Looking forward on what you are working on.

For anyone interested, I just pushed up the first take on AuthenticationInterceptor in PR #3164. Feedback welcome!

@cnoon Thanks for this feature, but how can we handle concurrent calls ?
Exemple :
I have my accessToken expired, and two different call are fired at the same time "getUser" and "getActivities". Now both fire the refresh event and the last of two to reach the server failed (cause the first successfully get the new couple of tokens).

Ideally the first request to call "refresh" should lock every other requests until the reception of the response, and moreover propagate the new token

@GuillaumeBourge Concurrent calls should be handled automatically. The interceptor enqueues any additional requests that were made while refreshing and lets them move forward once the token is updated.

@GuillaumeBourge Concurrent calls should be handled automatically. The interceptor enqueues any additional requests that were made while refreshing and lets them move forward once the token is updated.

Thanks for the answer ! My bad i was not using the same instance of interceptor on different call..
I have a second question, credentials are retained and "managed" by the interceptor, am i right ?
How can i handle token persistance for app restart ?

  • save new tokens at the end of "refresh" function of the interceptor in KeyChain for exemple
  • start an unique instance at the application Start with Tokens from Keychain ?

No, your credentials are applied to URLRequests through some type conforming to the Authenticator protocol. That type would be responsible for accessing the credential from whatever storage you have. Since it also triggers the refresh, it would also be responsible for storing the new values.

@GuillaumeBourge you need to draw a line here - and understand Interceptor is actually composed from two distinct protocols Adapter & Retrier:

public protocol RequestInterceptor: RequestAdapter, RequestRetrier

Every supplements that you need to provide your request with - you do it in Adapter methods (thus you "adapt" the request)
While all of the retrying & refreshing goes into Retrier.

Was this page helpful?
0 / 5 - 0 ratings