vadymmarkov / Malibu

:surfer: Malibu is a networking library built on promises
https://vadymmarkov.github.io
Other
414 stars 39 forks source link

Authorization with refresh tokens #42

Closed viktorgardart closed 8 years ago

viktorgardart commented 8 years ago

So the API i'm currently working against have tokens that does not last very long (30 mins). I'm also provided with a refreshtoken which I'm using to generate a new Accesstoken.

So when creating a request i want to check if the token has expired. IF the token has expired i want to request a new one and then continue with the previous request.

I have the flow to check if the token have expired or not working. But when requesting a new one i cant figure out how to "pause" the previous one and the continue it after i've received the new token.

Could someone point me into the correct direction here?

guillermo-carrasco commented 8 years ago

Hi there :)

Would be nice to get some insights on how we could tackle this problem. The refresh token checkings are done in a beforeEach request fashion, we would like to keep it since it keeps the code clean:

self.networking.beforeEach = { request in
  self.setAuthHeaders()
  return request
}

setAuthHeaders is the method that does all the work with the tokens, and what we would like is to sort of "pause" that return request until the headers are set, which will take a bit more time if the token has expired.

vadymmarkov commented 8 years ago

@viktorgardart @guillermo-carrasco Hi there! Thanks for reaching out. Seeing that Malibu is promise-based, the solution should come out of promises as well. You could build your own chain of requests, resolve, reject, throw corresponding errors and so on, for example:

struct AuthService {
  // Let's say you have some `authStorage` where you can get the current token from
  // and check if it's expired or not.
  func acquireToken() -> Promise<String> {
    let promise = Promise<String>()

    // Resolve with the current token unless it is expired
    guard authStorage.isExpired else {
      promise.resolve(authStorage.accessToken)
      return promise
    }    

    // Request a new token
    Malibu.networking(name: "auth").GET(TokenRequest())
      .validate()
      .toJSONDictionary()
      .then({ dictionary -> String in
        let accessToken: String
        // 1. Get tokens from dictionary
        // 2. Save tokens
        return accessToken
      })
      .done({ accessToken in
        promise.resolve(accessToken)
      })
      .fail({ error in
        // Handle errors
        promise.reject(error)
      })

    return promise
  }
  // ...
}

AuthService()
  . acquireToken()
  .then({ accessToken -> Ride in
    // Set token headers
    return Malibu.networking(name: "basic").GET(MyRequest())  
  })

On the other hand, it could be a new feature on Malibu Networking:

public var prepare: (Promise<Void> -> Void)?

So you can still use your AuthService, but only in one place to keep the code clean:

networking.prepare = { task in
  AuthService()
  . acquireToken()
  .done({ _ in
    task.resolve() 
  })
  .fail({ error in
    // Handle errors
    task.reject(error)
  })   
}

// Will be executed only when promise from `prepare` closure is resolved
// so you always have your actual valid token here
networking.beforeEach = { request in
  // set auth headers
  return request
}
viktorgardart commented 8 years ago

@vadymmarkov That prepare solution sounds like a good plan to implement. For now im sticking with your first solution you mentioned.

Thank you so much, and have a great weekend!