I am trying to do an OAuth2.0 Authentication using Moya and when I do the request I get 401 statusCode.
I created OAuthHandler:
import Foundation
import p2_OAuth2
import Alamofire
class OAuth2Handler {
fileprivate let loader: OAuth2DataLoader
init(oauth2: OAuth2) {
loader = OAuth2DataLoader(oauth2: oauth2)
}
}
/**
The RequestRetrier protocol allows a Request
that encountered an Error while being executed to be retried.
*/
extension OAuth2Handler: RequestRetrier {
public func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401, let req = request.request {
var dataRequest = OAuth2DataRequest(request: req, callback: { _ in })
dataRequest.context = completion
loader.enqueue(request: dataRequest)
loader.attemptToAuthorize() { authParams, error in
self.loader.dequeueAndApply() { req in
if let comp = req.context as? RequestRetryCompletion {
comp(authParams != nil, 0.0)
}
}
}
}
else {
completion(false, 0.0)
}
}
}
/**
The RequestAdapter protocol allows each Request made on a SessionManager
to be inspected and adapted before being created.
*/
extension OAuth2Handler: RequestAdapter {
public func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
guard loader.oauth2.accessToken != nil else {
return urlRequest
}
return try urlRequest.signed(with: loader.oauth2)
}
}
Then instantiating one SessionManager and setting its adapter and retrier
let sessionManager = SessionManager()
let oauthHandler = OAuth2Handler(oauth2: oauth2)
sessionManager.adapter = oauthHandler
sessionManager.retrier = oauthHandler
Then I am passing the sessionManager to my provider
provider = MoyaProvider<Endpoint>(manager: sessionManager)
Lastly, when I do the request I see that it is unauthorised:
provider.request(.endpoint1). { request in
switch request {
case let .success(response):
print(response.statusCode)
case let .failure(error):
print("error")
}
}
But when I am doing it like this, I can see that the request is authorised :
sessionManager.request("Endpoint1 URL").validate().responseJSON { response in
if let res = response.value {
print(res)
}
}
I expect to have an authorised response in the end when I do the request from my provider, but I don't understand why this is happening.
Can someone help me figuring out how to do the OAuth2 Authorisation in Moya?
Have you ever used any of the Moya plugins before? That's how I do OAuth with Moya and it's much easier to use then what you have above.
There is a provided plugin Moya has for you to use just for this purpose
[Fixed] I forgot to add this to my Endpoit:
var validate: Bool {
return true
}
I am leaving this issue open for now if someone has other suggestions for this topic
Yeah, here is an example:
import Moya
import Result
public struct MoyaAppendHeadersPlugin: PluginType {
public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
var request = request
if let urlString = request.url?.absoluteString, urlString.hasPrefix(AppConstants.apiEndpoint) {
if let authToken = UserCredsManager.authToken {
request.setValue(String(format: "Bearer %@", authToken), forHTTPHeaderField: "Authorization")
}
}
return request
}
public func didReceive(_ result: Result<Moya.Response, Moya.MoyaError>, target: TargetType) {
}
}
Then, when you construct your MoyaProvider, you pass in an instance of the plugin which is explained in this doc
My example is a little more involved because what I am using the UserCredsManager that I created myself which is saying, "whenever the app has set the auth token in the keychain, use it. If it doesn't exist, don't append it" which allows me to not have to care about state of my app.
// You can use this Endpoint closure to add token with header
private let endpointClosure = { (target: MyAPI) -> Moya.Endpoint<MyAPI> in
let endpoint = MoyaProvider.defaultEndpointMapping(for: target);
switch target {
case .createuser,.refresh: //don't add token for this paths
return endpoint
default:
//If there is a token add a token to header
return endpoint.adding(newHTTPHeaderFields:["X-Api-Token": UserCredsManager.sharedInstance.getUser()?.token ?? ""])
}
}
If you want to retry your requests in case of failed request, try something like below
extension PrimitiveSequence where TraitType == SingleTrait, ElementType == Response {
/// Tries to refresh auth token on 401 errors and retry the request.
/// If the refresh fails, the signal errors.
public func retryWithAuthIfNeeded() -> Single<Response> {
return self.retryWhen{ (e: Observable<Error>) in
Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: { $1 }) //trying 3 times. You can change the number
.flatMap { i in
return MyProvider.rx.request(.refresh(token: "abc")) //whatever your token endpoint for refreshing
.filterSuccessfulStatusAndRedirectCodes()
.map(Token.self)
.catchError { error in
if case MoyaError.statusCode(let response) = error {
if response.statusCode == 401 {
// Logout
do {
try User.logOut()
} catch _ {
logger.warning("Failed to logout")
}
}
}
return Single.error(error)
}.flatMap { token -> Single<Token> in
do {
try token.saveInRealm()
} catch let e {
logger.warning("Failed to save access token")
return Single.error(e)
}
return Single.just(token)
}
}
}
}
}
Use it like below
let disposeBag = DisposeBag()
let MyProvider = MoyaProvider<MyAPI>(endpointClosure:endpointClosure,plugins: [NetworkLoggerPlugin(verbose: true, responseDataFormatter: JSONResponseDataFormatter)])
MyProvider.rx.request(.allthings)
.filterSuccessfulStatusCodes()
.retryWithAuthIfNeeded()
.map([Things].self)
.subscribe(onSuccess: { response in
print("Response")
print(response)
}) { error in
print("Error occured")
print(error)
}
.disposed(by: disposeBag)
Hey guys! I made a wrapper on TVDB API, and the API uses JWT token. This token expires after 24 hours. I managed to do the authentication and refresh the token automatically.
You can see more details here
This issue has been marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
This issue has been auto-closed because there hasn't been any activity for at least 21 days. However, we really appreciate your contribution, so thank you for that! 馃檹 Also, feel free to open a new issue if you still experience this problem 馃憤.
Most helpful comment
// You can use this Endpoint closure to add token with header
If you want to retry your requests in case of failed request, try something like below
Use it like below