0

Siesta デコレータを使用して、ログインしたユーザーが 401 を取得したときに authToken が自動的に更新されるフローを有効にしようとしています。認証には Firebase を使用します。

Siesta のドキュメントには、Siesta リクエストをチェーンする方法に関する簡単な例がありますが、ここで非同期の Firebase getIDTokenForcingRefresh:completion:を機能させる方法を見つけることができませんでした。問題は、Siesta が常に Request またはRequestChainActionが返されることを期待していることですが、これは Firebase 認証トークン更新 API では不可能です。

リクエスト チェーンは主に Siesta のみのユース ケースで行われることを理解しています。しかし、FirebaseAuth のような非同期のサードパーティ API を使用する方法はありますか?

コードは次のとおりです。

init() {
    configure("**") {
        $0.headers["jwt"] = self.authToken
        
        $0.decorateRequests {
          self.refreshTokenOnAuthFailure(request: $1)
     }  
  }

func refreshTokenOnAuthFailure(request: Request) -> Request {
  return request.chained {
    guard case .failure(let error) = $0.response,  // Did request fail…
      error.httpStatusCode == 401 else {           // …because of expired token?
        return .useThisResponse                    // If not, use the response we got.
    }

    return .passTo(
      self.createAuthToken().chained {             // If so, first request a new token, then:
        if case .failure = $0.response {           // If token request failed…
          return .useThisResponse                  // …report that error.
        } else {
          return .passTo(request.repeated())       // We have a new token! Repeat the original request.
        }
      }
    )
  }
}

//What to do here? This should actually return a Siesta request
func createAuthToken() -> Void {
  let currentUser = Auth.auth().currentUser
  currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
    if let error = error {
      // Error
      return;
    }
    self.authToken = idToken
    self.invalidateConfiguration()
  }
}

編集:

Adrianの提案された回答に基づいて、以下の解決策を試しました。それでも期待どおりに動作しません:

  • request() .postを使用してリクエストを送信します
  • 解決策では、コールバックで「リクエストがキャンセルされました」というエラーが発生します
  • createUserのコールバックが呼び出された後、元のリクエストが更新された jwt トークンとともに送信されます
  • createUserのコールバックが応答のために呼び出されないため、正しい jwt トークンを使用したこの新しい要求は失われます。その場合、 onSuccessに到達することはありません。

元のリクエストが更新された jwt トークンで送信された後にのみ createUser のコールバックが呼び出されるようにするにはどうすればよいですか? これが私の機能しない解決策です-何か提案があれば幸いです:

 // This ends up with a requestError "Request Cancelled" before the original request is triggered a second time with the refreshed jwt token.
    func createUser(user: UserModel, completion: @escaping CompletionHandler) {
    do {
        let userAsDict = try user.asDictionary()
        Api.sharedInstance.users.request(.post, json: userAsDict)
            .onSuccess {
                data in
                if let user: UserModel = data.content as? UserModel {
                    completion(user, nil)
                } else {
                    completion(nil, "Deserialization Error")
                }
        }.onFailure {
            requestError in
            completion(nil, requestError)
        }
    } catch let error {
        completion(nil, nil, "Serialization Error")
    }
}

API クラス:

    class Api: Service {
    
    static let sharedInstance = Api()
    var jsonDecoder = JSONDecoder()
    var authToken: String? {
        didSet {
            // Rerun existing configuration closure using new value
            invalidateConfiguration()
            // Wipe any cached state if auth token changes
            wipeResources()
        }
    }
    
    init() {
        configureJSONDecoder(decoder: jsonDecoder)
        super.init(baseURL: Urls.baseUrl.rawValue, standardTransformers:[.text, .image])
        SiestaLog.Category.enabled = SiestaLog.Category.all
        
        configure("**") {
            $0.expirationTime = 1
            $0.headers["bearer-token"] = self.authToken
            $0.decorateRequests {
                self.refreshTokenOnAuthFailure(request: $1)
            }
        }
        
        self.configureTransformer("/users") {
            try self.jsonDecoder.decode(UserModel.self, from: $0.content)
        }
        
    }
    
    var users: Resource { return resource("/users") }
    
    func refreshTokenOnAuthFailure(request: Request) -> Request {
        return request.chained {
            guard case .failure(let error) = $0.response,  // Did request fail…
                error.httpStatusCode == 401 else {           // …because of expired token?
                    return .useThisResponse                    // If not, use the response we got.
            }
            return .passTo(
                self.refreshAuthToken(request: request).chained {          // If so, first request a new token, then:
                    if case .failure = $0.response {
                        return .useThisResponse                  // …report that error.
                    } else {
                        return .passTo(request.repeated())       // We have a new token! Repeat the original request.
                    }
                }
            )
        }
    }
    
    func refreshAuthToken(request: Request) -> Request {
        return Resource.prepareRequest(using: RefreshJwtRequest())
            .onSuccess {
                self.authToken = $0.text                  // …make future requests use it
        }
    }
}

RequestDelegate:

    class RefreshJwtRequest: RequestDelegate {

    func startUnderlyingOperation(passingResponseTo completionHandler: RequestCompletionHandler) {
        if let currentUser = Auth.auth().currentUser {
            currentUser.getIDTokenForcingRefresh(true) { idToken, error in
                if let error = error {
                    let reqError = RequestError(response: nil, content: nil, cause: error, userMessage: nil)
                    completionHandler.broadcastResponse(ResponseInfo(response: .failure(reqError)))
                    return;
                }
                let entity = Entity<Any>(content: idToken ?? "no token", contentType: "text/plain")
                completionHandler.broadcastResponse(ResponseInfo(response: .success(entity)))            }
        } else {
            let authError = RequestError(response: nil, content: nil, cause: AuthError.NOT_LOGGED_IN_ERROR, userMessage: "You are not logged in. Please login and try again.".localized())
            completionHandler.broadcastResponse(ResponseInfo(response: .failure(authError)))
        }
    }
    
    func cancelUnderlyingOperation() {}

    func repeated() -> RequestDelegate { RefreshJwtRequest() }

    private(set) var requestDescription: String = "CustomSiestaRequest"
}
4

1 に答える 1