(AFNetworking) how to adjust a header in an AFHTTPRequestOperation request? - swift

How can I modify an AFHTTPRequestOperation header after creating it? In situations where an API request returns a 401, i need to refresh the access token and adjust every AFHTTPRequestOperation's header, and retry the same request operation with the updated access token.
Below is my code to process requests that were queued due to an access token is currently being refreshed. When a new access token is received, this method is called.
The method below works, but it just seems odd that creating a new variable of the operation request and adjusting the variable's header also changes the original request.
var authManager = AFOAuth2Manager()
func processHeldRequests() {
for operation: AFHTTPRequestOperation in heldRequests {
var token = tokenManager.getToken()
println("adjusting held operation's request header authorization to new token \(token!)")
var operationRequest: NSMutableURLRequest = operation.request as! NSMutableURLRequest
operationRequest.setValue("Bearer \(token!)", forHTTPHeaderField: "Authorization")
authManager.operationQueue.addOperation(operation)
}
heldRequests.removeAll(keepCapacity: false)
}

Related

How can I change the bearer token in Moya

the documentation shows how to make targets require bearer tokens, which I did like this
extension MyService: AccessTokenAuthorizable {
var authorizationType: AuthorizationType {
switch self {
case .resetPassword, .postTextBook, .bookmarkBook, .getBookmarks, .logout, .verify:
return .bearer
default:
return .none
}
}
}
then it shows how to add tokens to the providers, which I did like this
let token = "abc123"
let authPlugin = AccessTokenPlugin(tokenClosure: token)
let provider = MoyaProvider<MyService>(plugins: [authPlugin])
but when the token expires, how can I change the token? and does Moya offer a way to automate this process, where if I get a forbidden http response (meaning I am not authorized), it automatically requests a token?
The implementation details of authentication/authorization can be quite different for each API out there. This is the reason why Moya will not handle the auth for you.
That said, implementing your own authentication/authorization can be done in many ways. It will depend on your constraints and/or preferences. As of today, you can find a few solutions sparsely outlined in Moya documentation:
Use the PluginType to add your auth to the requests. But think that this can potentially be used to refresh the token if needed. You may also need to intercept the completion of the request to detect authorization errors and apply your preferred recovery scenario (eg. refresh the token and retry the call).
Same can be implemented using the endpointClosure and/or requestClosure.
You can also consider implementing Alamofire's RequestAdapter and RequestRetrier. Depending on your needs, this can make retries easier. However, on them you will not have straightforward access to your TargetType, so you may need to find a way to recognize the different auth methods needed (ie. your bearer or none).
A few direct references to their documentation:
Plugins
Endpoints
Authentication
Alamofire Automatic Validation
Also, I highly encourage anybody to learn/get inspiration from Eilodon's Networking source code.
for change/refresh token i used this
static func send(request: TargetType) -> PrimitiveSequence<SingleTrait, Response> {
return provider.rx.request(request)
.retry(1)
.observeOn(ConcurrentDispatchQueueScheduler.init(qos: .default))
.filterSuccessfulStatusAndRedirectCodes()
.retryWhen({ (errorObservable: Observable<Error>) in
errorObservable.flatMap({ (error) -> Single<String> in
if let moyaError: MoyaError = error as? MoyaError, let response: Response = moyaError.response {
if **check forbidden http responses here** {
return provider.rx.request(.refreshToken(*your refresh token here*))
.filterSuccessfulStatusCodes()
.mapString(atKeyPath: "*json path to new access token*")
.catchError { (_) in
logout()
throw error
}
.flatMap({ (newAccessToken) -> PrimitiveSequence<SingleTrait, String> in
changeAccessToken()
return Single.just(newAccessToken)
})
}
}
throw error
})
})
}
static func logout() {
// logout action
}
static func changeAccessToken() {
// set new access token
}

REST Hammock not adding content to post body

I've been fiddling a POST request that isn't working. What I've found is that the content of the request is empty even though I believe I'm adding it correctly (even though there aren't any docs available that I can find).
var json = JsonConvert.SerializeObject(payload);
var client = new RestClient
{
Authority = "",
Credentials = OAuthCredentials.ForProtectedResource(Registration.ClientKey,
Registration.ClientSecret,
AuthorizationToken.Token,
AuthorizationToken.Secret),
Method = WebMethod.Post,
Path = url
};
var request = new RestRequest();
request.AddHeader("Accept", ContentType);
request.AddHeader("Content-Type", ContentType);
request.AddPostContent(Encoding.UTF8.GetBytes(json));
response = client.Request(request);
I'm not having any trouble with authorization. I just kept getting a 400 error. After capturing the request with Fiddler, I noticed that content-length was 0. What am I missing? Do I need to do something in addition to AddPostContent? When I check the request object right before executing, there is content in PostContent, but it's as if Hammock isn't adding it to the request. I am using the latest full nuget (not ClientProfile).

How to add a http header when post request to a servicestack web api using servicestack swift plugin?

First below is a very straightforward code that post a req to the api.
let req = PurchaseRequest()
req.cellphone = "5101111111"
req.amount = 6
let client = JsonServiceClient(baseUrl: "http://example.com/webapi")
let response = try! client.post(req)
Now, how do I add a Http header to the request? For example, there is a http header called "Authorization" and we usually use this header to provide token, to authenticate users. How do I do that?
let client = JsonServiceClient(baseUrl: "http://example.com/webapi")
client.requestFilter = { (req:NSMutableURLRequest) in
req.addValue("FJEUJFJSKEJF#$"/*a token value*/, forHTTPHeaderField: "Authorization")
}
So I found the property called requestFilter of the jsonserviceclient, which I can then get the standard nsmutableurlrequest so the rest is easy, just plain old swift code, where I just add my token value to the Authorization http header. My code is doing exactly that.

NSHttpURLResponse contains different values on device than on simulator from xamarin iOS app

I am making an iPhone app on xamarin. I am encountering a very baffling issue. I am making an NSURLConnection to a link to get the cookie information from header and use that cookie to open other protected links. In connection:didReceiveResponse: method, the NSHttpURLResponse contains the "Set-Cookie" field to retrieve. I am getting the correct header information when running the application on simulator. But on device, the same code does not return the complete header with cookie. This prevents me from calling other links as well. If I run contact the same url from a native sample app in objective-c, I get the correct header information. It is only not working on device from xamarin app.
The code for creating a connection is
NSMutableUrlRequest request = new NSMutableUrlRequest(url);
request.HttpMethod = "GET";
request.ShouldHandleCookies = true;
request.Headers = header;
URLConnDelegate connectionDelegate = new URLConnDelegate (this);
NSUrlConnection connection = new NSUrlConnection (request, connectionDelegate, true);
And the code for receiving response is
public override void ReceivedResponse (NSUrlConnection connection, NSUrlResponse response)
{
NSHttpUrlResponse httpResponse = response as NSHttpUrlResponse;
Console.WriteLine (httpResponse.AllHeaderFields);
NSString cookieStr = httpResponse.AllHeaderFields.ValueForKey (new NSString ("Set-Cookie")) as NSString;
caller.setCookie (cookieStr);
NSMutableUrlRequest request = new NSMutableUrlRequest (new NSUrl(caller.urlString));
caller.loadRequest (request);
}
The set-cookie field is not coming when I run it from device.
Any help would be much appreciated.
Thanks

Invalid grant_type parameter or parameter missing on POST for requesting access token

I am struggling to get the access token on Quizlet (oauth2). Everything works fine so far, I can make the user accepting my app on Quizlet, get redirected, but when requesting the access token via NSURLConnection, I always get the following error:
2013-08-17 09:39:33.422 Abiliator[49549:c07] Returned data in string format: {"http_code":400,"error":"invalid_request","error_title":"Not Allowed","error_description":"Invalid grant_type parameter or parameter missing"}
Here the code for the user authentication (must be via browser according to spec):
- (void) authenticateQuizletUser
{
NSString *quizletRandomString = [abiliatorAppDelegate GetUUID];
NSString *authURLString = [#"https://quizlet.com/authorize/?response_type=code&client_id=" stringByAppendingString:#"<myID>&scope=read"];
authURLString = [authURLString stringByAppendingString:#"&state="];
authURLString = [authURLString stringByAppendingString:quizletRandomString];
authURLString = [authURLString stringByAppendingString:#"&redirect_uri=Abiliator://after_oauth"];
NSLog(#"Authentication URL sent: %#", authURLString);
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: authURLString]];
}
That works fine, as I mentioned. The app starts Safari and the user must acknowledge the request entering user id and password and the server redirects into my app, which I catch in the method below, which then throws the error described.
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if (!url) { return NO; }
NSString *URLString = [url absoluteString];
NSLog(#"Received URL: %#", URLString);
NSString *myURLQuery = [url query];
NSString *myAuthCode = [self getAuthorizationCodeFromURL:myURLQuery];
NSLog(#"Component1: %#", myAuthCode);
NSString *authPasswd = #"myPasswd";
NSString *username=#"myUserName";
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"https://api.quizlet.com/oauth/token"]];
request.HTTPMethod = #"POST";
[request setValue:#"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"Abiliator://after_oauth" forHTTPHeaderField:#"redirect_uri"];
// According to Quizlet API doc: You must set this (grant_type) to the string "authorization_code".
[request setValue:#"authorization_code" forHTTPHeaderField:#"grant_type"];
[request setValue:myAuthCode forHTTPHeaderField:#"code"];
NSString *authStr = [NSString stringWithFormat:#"%#:%#", username, authPasswd];
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", [authData base64EncodedString]];
[request setValue:authValue forHTTPHeaderField:#"Authorization"];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
return YES; }
Any help hihgly appreciated.
OAuth2 defines 4 ways to gain an access token. The most secure and most complicated is the Authorization Code Grant that is used by Quizlet as described here.
The Authorization Code Grant consists of two steps:
get an authorization code (the user authenticates here outside of your app before the flow passes back to you via redirect)
change the authorization code into an access token
You did the first call right. The problem with the second call is that you put the grant_type parameter in the wrong place of your request.
In this line you treat it as an HTTP header:
[request setValue:#"authorization_code" forHTTPHeaderField:#"grant_type"];
And here you also treat the authorization code as an HTTP header:
[request setValue:myAuthCode forHTTPHeaderField:#"code"];
But OAuth2 requires you to put both into the body of your request. Here is an example of a correct request:
POST /oauth/token/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 999
Authorization: Basic xxx
grant_type=authorization_code&code=theCodeYouGotInTheFirstStep&
scope=somescope&redirect_uri=theSameUriYouIncludedEarlier
(The stuff below the empty line is your request's body)
(I added the linebreak in the body only for readability - you must not include it in a request)
Bonus answer: Please keep in mind that OAuth2 is insecure by default: If you don't do a bit of extra work, your app is vulnerable to Cross-Site Request Forgery attacks as even mentioned in the OAuth2 RFC. To prevent this OAuth2 offers you the state parameter. You have to generate a non-guessable value for your state and include it in the first request. You must not fire the second request, if the state returned by the server is not the same you generated earlier.
Might be useful to see the rest of your implementation. How are you obtaining the code? Also, what credentials (authPasswd & username) are you using? These should be your app's (as opposed to the end user's). You get these in Quizlet's Dev Dashboard.
Note: this OAuth flow is not recommended for devices, as it requires secrets to be stored in the device. Using the implicit flow is, but I'm not sure Quizlet supports it.
Send your oauth parameters as payload. You should not be sending them as headers. For Example your request should looks like
POST /token
Headers:
Authorization: Basic R0cxSWJdHpINDVhang5Y0VlSUxxalBSOW5SU0NBWA==
Content-Type: application/x-www-form-urlencoded
Payload:
grant_type=authorization_code&code=Wirr951e&scope=READ&redirect_uri=www.google.com
I also struggled with similar issue, here is what worked for me.
Need to define Header right (if I explain it Karate script way),
* def keycloak_extension = "keycloak authorization to generate the access token put here"
* configure headers = {'Content-Type':"application/x-www-form-urlencoded",'Authorization':#("Basic " +keycloakAuthSecret)}
Given path keycloak_extension
And form field grant_type = 'client_credentials'
And request '{}'
When method post
And status 200
* def accessToken = response.access_token ```
How to encode using Base64 can use this link:
https://www.base64decode.org/
Can also refer to this video, to understand it better:
https://www.youtube.com/watch?v=k-Q-Kywk1O4