Moya - unable to call apis with authentication credentials - ios

I am developing an iOS app using django rest framework for apis. But currently I am not be able to getting ahead when calling apis with authentication credentials.
I succeeded in calling the api using Postman and curl by setting Header as Authentication Bearer <token>.. but I continuously failed at calling it from iOS app. I am using Moya for calling api. And I don't know what I should do next.
What I tried: (when calling Moya)
let token = "abcde12345sometoken"
let plugin = AccessTokenPlugin(tokenClosure: token)
let provider = MoyaProvider<AccountAPI>(plugins : [plugin])
provider.request(.getAccountProfile(oauth_id: oauth_id, provider: "facebook")) { (result) in
// doing something with result
}
and configured API as:
extension AccountAPI : TargetType, AccessTokenAuthorizable {
// codes conforming variables to TargetType protocol
public var authorizationType: AuthorizationType {
switch self {
case .getFacebookAccountToken:
return .none
default:
return .bearer
}
}
public var headers: [String: String]? {
switch self {
case .getFacebookAccountToken, .getEmailAccountToken: // post requests
return ["Content-type":"application/x-www-form-urlencoded"]
default:
return ["Content-type":"application/json"]
}
}
}
Is there anything I should consider when using Moya for authentication or maybe with Info.plist and so on?
Or the document says this approach is for JWT token, and maybe my method is not for JWT and something else..? Give me some advice!

For my case, I use
Moya 12.0.1
MultiTarget
example:
plugins = [AccessTokenPlugin(tokenClosure: {
let token = ...
return token
})]
MoyaProvider<MultiTarget>(
plugins: plugins
)
.request(MultiTarget(myAPI)) {
...
}
But it never calls tokenClosure
Solution
you need to add this extension
extension MultiTarget: AccessTokenAuthorizable {
public var authorizationType: AuthorizationType {
guard let target = target as? AccessTokenAuthorizable else { return .none }
return target.authorizationType
}
}
source: https://github.com/Moya/Moya/blob/master/Sources/Moya/Plugins/AccessTokenPlugin.swift#L62

After a few hours of trying this and that.. I found out that it was the api endpoint redirects itself based on the content-language.. so the header that I set is dead when being redirected. So either setting the i18n url in advance or setting the content-language header would solve my problem.

Related

Stuck with Api response Ktor

I am trying to build a KMM application using Ktor for our ApiServices. I have created a BaseApiClass where I have all of the api related code.
Code for BaseApiClass :-
class BaseAPIClass {
//Create Http Client
private val httpClient by lazy {
HttpClient {
defaultRequest {
host = ApiEndPoints.Base.url
contentType(ContentType.Application.Json)
header(CONNECTION, CLOSE)
}
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.ALL
}
install(HttpTimeout) {
requestTimeoutMillis = NETWORK_REQUEST_TIMEOUT
}
expectSuccess = false
// JSON Deserializer
install(JsonFeature) {
val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
}
serializer = KotlinxSerializer(json)
}
}
}
// Api Calling Functions I have few more similar to this but issue is random and comes in any of the api
#Throws(Exception::class)
suspend fun sampleApi(requestBody: RequestBody?) : Either<CustomException, BaseResponse<EmptyResponseModel>> {
return try {
val response = httpClient.post<BaseResponse<EmptyResponseModel>> {
url(ApiEndPoints.sample.url)
if (requestBody != null) {
body = requestBody
}
}
Success(response)
}
catch (e: Exception) {
Failure(e as CustomException)
}
}
Here's how I call the api from iOS app :-
val apiClass = BaseApiClass()
func callApi() {
apiClass.sampleApi(requestBody: .init(string: "value here")) { (result, error) in
result?.fold(failed: { (error) -> Any? in
// Error here
}, succeeded: { (result) -> Any? in
// Success here
})
}
}
Now here if I try to call similar few more api's with the same object i.e apiClass then after few calls it get stuck inside my function callApi it don't send even api request (Because I can't see Request Logs printed in my console) and because of that I cannot do any other operations as I don't get anything from api.
As soon as I change my screen or close the app and try to call the same api then it works good.
But instead of creating a object only at one time like this apiClass = BaseApiClass() if I try to do with BaseApiClass().sampleApi(request params here) {// completion handler here} it works fine I don't get any issues with this.
I am not sure what causes this to happen everything works good in Android this is faced only with iOS.
Try to set LogLevel.NONE in the install(Logging) block.
At the moment I resolved in this way because it seems a bug of Ktor.
See: https://youtrack.jetbrains.com/issue/KTOR-2711
It should be fixed in the version 1.6.0.
Are you using the multithreaded variant of the Coroutines library? The official docs state that you should use this variant when working with Ktor. See here
After all the efforts and trying a lot of debugging skills I got to understand that my completion handler in the shared module is never called even if I receive the response the response from api.
The only solution I have achieved is creating the different HTTP Client using expect and actual mechanism. By making separate clients I have not encountered the issue yet.
If you have any other answers or solutions I would be happy to have a look at it.

Refreshing an Access Token p2/OAuth2 iOS

I'm using p2/OAuth2 with Alamofire v4 as explained in documentation here
let sessionManager = SessionManager()
let retrier = OAuth2RetryHandler(oauth2: <# your OAuth2 instance #>)
sessionManager.adapter = retrier
sessionManager.retrier = retrier
self.alamofireManager = sessionManager // you must hold on to this somewhere
// Note that the `validate()` call here is important
sessionManager.request("https://api.github.com/user").validate().responseJSON { response in
debugPrint(response)
}
import Foundation
import OAuth2
import Alamofire
class OAuth2RetryHandler: RequestRetrier, RequestAdapter {
let loader: OAuth2DataLoader
init(oauth2: OAuth2) {
loader = OAuth2DataLoader(oauth2: oauth2)
}
/// Intercept 401 and do an OAuth2 authorization.
public func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {
if let response = request.task?.response as? HTTPURLResponse, 401 == response.statusCode, let req = request.request {
var dataRequest = OAuth2DataRequest(request: req, callback: { _ in })
dataRequest.context = completion
loader.enqueue(request: dataRequest)
loader.attemptToAuthorize() { authParams, error in
guard error?.asOAuth2Error != .alreadyAuthorizing else {
// Don't dequeue requests if we are waiting for other authorization request
return
}
self.loader.dequeueAndApply() { req in
if let comp = req.context as? RequestRetryCompletion {
comp(nil != authParams, 0.0)
}
}
}
}
else {
completion(false, 0.0) // not a 401, not our problem
}
}
/// Sign the request with the access token.
public func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
guard nil != loader.oauth2.accessToken else {
return urlRequest
}
return try urlRequest.signed(with: loader.oauth2) // "try" added in 3.0.2
}
}
Everything is working fine, but what I want to achieve is to avoid 401 errors by fetching an access token if expired before submitting a request.
Is it possible to achieve this approach ?
Thanks in advance,
Regards,
Well the standard behaviour in a mobile app should be as follows:
Login and get access token + refresh token
Optionally store tokens in secure storage so that logins on every app restart are avoided
Use access token to call the API and handle 401 responses via a token renewal
Use the refresh token to renew the access token when needed
Eventually the refresh token expires and the user has to login again
You can never completely avoid 401 responses from the API, and UIs need to handle this response specially. It is possible as an optimisation to refresh tokens silently in the background, before the 'exp' claim from the current access token is reached.
Out of interest there is an iOS Code Sample of mine that is easy to run, and which allows a kind of testing of the expiry events. This may give you some ideas on how to adapt your own solution.

How to intercept Moya request and return failure response without sending the request at all

I'm using Moya library to handle networking layer, and I already have a custom plugin that add an authentication token to the header.
What I want to do is to make this plugin cancel the request and return a failure response (or throw an error) if the token is not available yet.
P.S. I extended the protocol TargetType to add extra variable that indicates if the target needs authentication or not, so I need to access these data to determine if the authentication token is needed in the header or not.
this is a snapshot of my custom plugin:
struct AuthTokenPlugin: PluginType {
let tokenClosure:()->String?
func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
guard let target = target as? AuthorizebleTargetType, target.needsAuth else {
return request
}
guard let token = tokenClosure() else {
// Here where a failure response will be triggered or an error should be thrown
return ......
}
var request = request
request.addValue( "Token " + token, forHTTPHeaderField:"Authorization")
return request
}
}
P.S.2: throwing an error is not a good practice and it is not possible because the enclosing function "prepare(_:target:)" is not declared 'throws'.
I don't think that we can implement such logic with usage of protocol TargetType in cause his methods don't return Bool values and are not throw-marked.
Take a look at MoyaProvider init parameters. There is a requestClosure param in it. You can copy-paste and replace this parameter's default implementation with your own implementation which will check authorization header of Endpoint.
Default implementation of this closure:
final class func defaultRequestMapping(for endpoint: Endpoint, closure: RequestResultClosure) {
do {
let urlRequest = try endpoint.urlRequest()
closure(.success(urlRequest))
} catch MoyaError.requestMapping(let url) {
closure(.failure(MoyaError.requestMapping(url)))
} catch MoyaError.parameterEncoding(let error) {
closure(.failure(MoyaError.parameterEncoding(error)))
} catch {
closure(.failure(MoyaError.underlying(error, nil)))
}
}
UPD with my comment:
I suggest to check that if Endpoint has header with key “Authorization”, but it’s value is empty string, then call closure parameter with .failure case in requestClosure

ADALiOS AcquireToken()

I've got and tried a number of samples using ADALiOS. ADALiOS keeps changing its implementation as you know. Because I am a beginner of swift, I don't have any idea how to make samples from Internet work.
I tried a version of adal 3.0 (pre release).
as you can see, the below code was copied from one of samples downloaded. Every sample has got a complier errors such as 'missing argument label-policy...' or 'cannot convert ADAuthenticationResult...' in authContext.acquireToken() or acquireTokenwithResource() method regardless of its adal version. anyone can help me?
the error message is about completionBlock:....
thanks
authContext.acquireToken(withScopes: [Any](), additionalScopes: [Any](), clientId: clientId, redirectUri: redirectURL, identifier: id!, promptBehavior: prompt, extraQueryParameters: "", completionBlock: <#T##ADAuthenticationCallback!##ADAuthenticationCallback!##(ADAuthenticationResult?) -> Void#>){
if result.status.value != AD_SUCCEEDED.value {
// Failed, return error description
completionHandler(false, result.error.description)
}
else {
// Succeeded, return the acess token
var token = result.accessToken
// Initialize the dependency resolver with the logged on context.
// The dependency resolver is passed to the Outlook library.
self.dependencyResolver = ADALDependencyResolver(context: authContext, resourceId: self.outlookResource, clientId: self.clientId, redirectUri: self.redirectURL)
completionHandler(true, token)
}
}
I just got this working using adal 3.0 (pre release). Looks like ADAL API has changed when looking at the examples. I couldn't find acquireTokenwithResource() so I used:
acquireToken(withScopes: additionalScopes: clientId: redirectUri: identifier:, promptBehavior:)
I think the biggest issue I had was not registering the native app here: https://apps.dev.microsoft.com
After I did that and used clientId and redirectURI from apps.dev.microsoft, and also include the correct scope, I was able to authenticate and get back an accessToken.
My acquireToken Looks like this:
func acquireAuthToken(completion: ((AuthenticationResult) -> Void)?) {
let identity = ADUserIdentifier(id: "Default email address", type: ADUserIdentifierType(rawValue:1))
self.context.acquireToken(withScopes: ["User.Read"], additionalScopes: nil, clientId: AppData.sharedInstance?.clientId, redirectUri: URL(string: (AppData.sharedInstance?.redirectUriString)!), identifier: identity, promptBehavior: AD_PROMPT_AUTO) {
(result) in
if let handler = completion {
if result!.status == AD_SUCCEEDED {
self.accessToken = result!.token
handler(AuthenticationResult.Success)
}
else {
handler(AuthenticationResult.Failure(result!.error))
}
}
}
}
The context is just ADAuthenticationContext(authority: ppData.sharedInstance?.authority, .Authority, error: &error)
HTH

iOS/Swift: Good architecture approach for connecting REST APIs

I’m developing iOS Apps for quite a long time now. But in the end I was never satisfied with the architecture design for my network layer. Especially when it goes about connecting an API.
There exists a possible duplicate here, but I think my question is more specific as you will see.
Best architectural approaches for building iOS networking applications (REST clients)
I’m not looking for answers like "use AFNetworking/Alamofire". This question is regardless of which 3rd party framework is used.
I mean, often we have the scenario:
"Develop an app X that uses API Y"
And this includes mainly the same steps - everytime.
Implement login / registration
You get an authentication token, have to save it in the keychain and append it in every API call
You have to re-authenticate and re-send the API request which failed with a 401
You have error codes to handle (how to handle them centralized?)
You implement the different API calls.
One problem with 3)
In Obj-C I used NSProxy for intercepting every API Call before it was send, re-authenticated the user if the token expired and and fired the actual request.
In Swift we had some NSOperationQueue where we queued an auth call if we got a 401 and queued the actual request after successful refresh. But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1.
I like more the second approach - but is there a better solution?
Regarding 4)
How do you handle http status codes? Do you use many different classes for every error? Do you centralize general error handling in one class? Do you handle them all at the same level or do you catch server errors earlier? (Maybe in your API Wrapper of any 3rd party lib)
How are you developers trying to solve this problems? Have you figured out a "best match" design?
How do you test your APIs? Especially how do you do this in Swift (with no real mocking possibility?).
Of course: Every use case, every app, every scenario is different - there is no "One solution fits them all". But I think these general problems re-appear so often, so I’m tempted to say "Yes, for these cases - there could be one and more solutions - which you can reuse every time".
Looking forward to interesting answers!
Cheers
Orlando 🍻
But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1. I like more the second approach - but is there a better solution?
I am using a few layers for authenticating with an API.
Authentication Manager
This manager is responsible for all authentication related functionality. You can think about authentication, reset password, resend verification code functions, and so on.
struct AuthenticationManager
{
static func authenticate(username:String!, password:String!) -> Promise<Void>
{
let request = TokenRequest(username: username, password: password)
return TokenManager.requestToken(request: request)
}
}
In order to request a token we need a new layer called the TokenManager, which manages all things related to a token.
Token Manager
struct TokenManager
{
private static var userDefaults = UserDefaults.standard
private static var tokenKey = CONSTANTS.userDefaults.tokenKey
static var date = Date()
static var token:Token?
{
guard let tokenDict = userDefaults.dictionary(forKey: tokenKey) else { return nil }
let token = Token.instance(dictionary: tokenDict as NSDictionary)
return token
}
static var tokenExist: Bool { return token != nil }
static var tokenIsValid: Bool
{
if let expiringDate = userDefaults.value(forKey: "EXPIRING_DATE") as? Date
{
if date >= expiringDate
{
return false
}else{
return true
}
}
return true
}
static func requestToken(request: TokenRequest) -> Promise<Void>
{
return Promise { fulFill, reject in
TokenService.requestToken(request: request).then { (token: Token) -> Void in
setToken(token: token)
let today = Date()
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)
userDefaults.setValue(tomorrow, forKey: "EXPIRING_DATE")
fulFill()
}.catch { error in
reject(error)
}
}
}
static func refreshToken() -> Promise<Void>
{
return Promise { fulFill, reject in
guard let token = token else { return }
let request = TokenRefresh(refreshToken: token.refreshToken)
TokenService.refreshToken(request: request).then { (token: Token) -> Void in
setToken(token: token)
fulFill()
}.catch { error in
reject(error)
}
}
}
private static func setToken (token:Token!)
{
userDefaults.setValue(token.toDictionary(), forKey: tokenKey)
}
static func deleteToken()
{
userDefaults.removeObject(forKey: tokenKey)
}
}
In order to request a token we'll need a third layer called TokenService which handles all the HTTP calls. I use EVReflection and Promises for my API calls.
Token Service
struct TokenService: NetworkService
{
static func requestToken (request: TokenRequest) -> Promise<Token> { return POST(request: request) }
static func refreshToken (request: TokenRefresh) -> Promise<Token> { return POST(request: request) }
// MARK: - POST
private static func POST<T:EVReflectable>(request: T) -> Promise<Token>
{
let headers = ["Content-Type": "application/x-www-form-urlencoded"]
let parameters = request.toDictionary(.DefaultDeserialize) as! [String : AnyObject]
return POST(URL: URLS.auth.token, parameters: parameters, headers: headers, encoding: URLEncoding.default)
}
}
Authorization Service
I am using an Authorisation Service for the problem you are describing here. This layer is responsible for intercepting server errors such as 401 (or whatever code you want to intercept) and fix them before returning the response to the user. With this approach everything is handled by this layer and you don't have to worry about an invalid token anymore.
In Obj-C I used NSProxy for intercepting every API Call before it was send, re-authenticated the user if the token expired and and fired the actual request. In Swift we had some NSOperationQueue where we queued an auth call if we got a 401 and queued the actual request after successful refresh. But that limited us to use a Singleton (which I don’t like much) and we also had to limit the concurrent requests to 1. I like more the second approach - but is there a better solution?
struct AuthorizationService: NetworkService
{
private static var authorizedHeader:[String: String]
{
guard let accessToken = TokenManager.token?.accessToken else
{
return ["Authorization": ""]
}
return ["Authorization": "Bearer \(accessToken)"]
}
// MARK: - POST
static func POST<T:EVObject> (URL: String, parameters: [String: AnyObject], encoding: ParameterEncoding) -> Promise<T>
{
return firstly
{
return POST(URL: URL, parameters: parameters, headers: authorizedHeader, encoding: encoding)
}.catch { error in
switch ((error as NSError).code)
{
case 401:
_ = TokenManager.refreshToken().then { return POST(URL: URL, parameters: parameters, encoding: encoding) }
default: break
}
}
}
}
Network Service
The last part will be the network-service. In this service layer we will do all interactor-like code. All business logic will end up here, anything related to networking. If you briefly review this service you'll note that there is no UI-logic in here, and that's for a reason.
protocol NetworkService
{
static func POST<T:EVObject>(URL: String, parameters: [String: AnyObject]?, headers: [String: String]?, encoding: ParameterEncoding) -> Promise<T>
}
extension NetworkService
{
// MARK: - POST
static func POST<T:EVObject>(URL: String,
parameters: [String: AnyObject]? = nil,
headers: [String: String]? = nil, encoding: ParameterEncoding) -> Promise<T>
{
return Alamofire.request(URL,
method: .post,
parameters: parameters,
encoding: encoding,
headers: headers).responseObject()
}
}
Small Authentication Demo
An example implementation of this architecture would be a authenticate HTTP request to login a user. I'll show you how this is done using the architecture described above.
AuthenticationManager.authenticate(username: username, password: password).then { (result) -> Void in
// your logic
}.catch { (error) in
// Handle errors
}
Handling errors is always a messy task. Every developer has it's own way of doing this. On the web there are heaps of articles about error handling in for example swift. Showing my error handling will be of not much help since it's just my personal way of doing it, it's also a lot of code to post in this answer, so I rather skip that.
Anyway...
I hope I've helped you back on track with this approach. If there is any question regarding to this architecture, I'll be more than happy to help you out with it. In my opinion there is no perfect architecture and no architecture that can be applied to all projects.
It's a matter of preference, project requirements and expertise in within your team.
Best of luck and please do no hesitate to contact me if there's any problem!

Resources