Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parallel refresh requests of OAuth2 access token with Swift p2/OAuth2

I am using https://github.com/p2/OAuth2 for connecting to the backend of my app via OAuth2 which works quite well.

The problem I have is when the access token expires and multiple requests happen at the same time some of them fail.

Parallel requests can be triggered from different parts of the app. For example when the app is launched the current location is sent to the server and a list of events is downloaded.

What would be the best way to make sure that no second refresh token request is made while the first is still running?

like image 776
Thomas Einwaller Avatar asked Jan 11 '16 14:01

Thomas Einwaller


People also ask

Can I use refresh token multiple times?

Refresh tokens never expire, unless revoked by the user. You should store it safely and permanently. You should definitely not go back and get new refresh tokens over and over, because only a certain number can be understanding per user/app combination, and eventually the older ones will stop working.

How do I get access token and refresh token OAuth2?

Step 1 − First, the client authenticates with the authorization server by giving the authorization grant. Step 2 − Next, the authorization server authenticates the client, validates the authorization grant and issues the access token and refresh token to the client, if valid.

Can refresh token be used as access token?

Once they expire, client applications can use a refresh token to "refresh" the access token. That is, a refresh token is a credential artifact that lets a client application get new access tokens without having to ask the user to log in again.

Which OAuth grant can support a refresh token?

You can get refresh tokens only for the OAuth 2.0: Authorization code flow.


1 Answers

Find your token lifetime and set buffer e.g 1-2 min and If your token needs refresh, save all requests when token is refreshing. After that execute all saved all requests. You can do this with DispatchQueue and DispatchWorkItem.

Example code below.

final class Network: NSObject {

    static let shared = Network()

    private enum Constants {
        static let tokenRefreshDiffrenceMinute = 1
        static let tokenExpireDateKey = "tokenExpireDate"
    }

    private(set) var tokenExpireDate: Date! {
        didSet {
            UserDefaults.standard.set(tokenExpireDate, forKey: Constants.tokenExpireDateKey)
        }
    }

    public override init() {
        super.init()

        if let date = UserDefaults.standard.object(forKey: Constants.tokenExpireDateKey) as? Date {
            tokenExpireDate = date
            print("Token found!")
        }
        else {
            print("Token not found!")
            isTokenRefreshing = true
            getToken {
                self.isTokenRefreshing = false
                self.executeAllSavedRequests()
            }
        }
    }


    private var isTokenRefreshing = false
    private var savedRequests: [DispatchWorkItem] = []

    func request(url: String, params: [String: Any], result: @escaping (String?, Error?) -> Void) {

        // isTokenRefreshing save all requests
        if isTokenRefreshing {

            saveRequest {
                self.request(url: url, params: params, result: result)
            }

            return
        }

        // if token expire
        if getMinutesFrom2Dates(Date(), tokenExpireDate) < Constants.tokenRefreshDiffrenceMinute {

            // open this flag for we need wait refresh token
            isTokenRefreshing = true

            // save current request too
            saveRequest {
                self.request(url: url, params: params, result: result)
            }


            // get token
            self.getToken { [unowned self] in
                self.isTokenRefreshing = false
                self.executeAllSavedRequests()
            }
        } else {
            //Alamofire.request ...

            DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
                DispatchQueue.main.async(execute: {
                    result(url, nil)
                })
            }
        }
    }

    private func saveRequest(_ block: @escaping () -> Void) {
        // Save request to DispatchWorkItem array
        savedRequests.append( DispatchWorkItem {
            block()
        })
    }

    private func executeAllSavedRequests() {
        savedRequests.forEach({ DispatchQueue.global().async(execute: $0) })
        savedRequests.removeAll()
    }

    private func getToken(completion: @escaping () -> Void) {
        print("Token needs a be refresh")
        // Goto server and update token
        DispatchQueue.global().asyncAfter(deadline: .now() + 1) { [unowned self] in
            DispatchQueue.main.async(execute: { [unowned self] in
                self.tokenExpireDate = Date().addingTimeInterval(120)
                print("Token refreshed!")
                completion()
            })
        }
    }

    private func getMinutesFrom2Dates(_ date1: Date, _ date2: Date) -> Int {
        return Calendar.current.dateComponents([.minute], from: date1, to: date2).minute!
    }
}
like image 92
Vicaren Avatar answered Sep 20 '22 18:09

Vicaren