I've been looking for code that generates a signature for an Oauth header, and boy has that been painful. My target platform is iOS, and I need to do this for the TradeKing API. Basically their queries require OAuth (no need to authorize my app for personal use ahead of time using an Oauth workflow; I just need to sign each query with an oauth header using the keys that TradeKing provided me). Here's some example documentation: GetPost
The best example code I have found is the following: https://github.com/Christian-Hansen/simple-oauth1
I was able to follow his example and get the LinkedIn library login show up. I then adapted the code for a TradeKing REST query, and it failed because of an invalid signature. That has me concerned because the code that generates the signature is the most complicated part...and I'm not sure if I am using his code properly. In the code below I changed the oauth keys and secrets into Xs.
/* THE URL REQUEST */
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://api.tradeking.com/v1/market/ext/quotes.xml?symbols=aapl"]];
request.HTTPMethod = @"GET";
/* OAUTH FIELDS */
NSString *oauth_timestamp = [NSString stringWithFormat:@"%lu", (unsigned long)[NSDate.date timeIntervalSince1970]];
NSString *oauth_nonce = [NSString getNonce];
NSString *oauth_consumer_key = @"xxxxxxx";
NSString *oauth_token = @"xxxxxxx";
NSString *oauth_signature_method = @"HMAC-SHA1";
NSString *oauth_version = @"1.0";
NSMutableDictionary *standardParameters = [NSMutableDictionary dictionary];
[standardParameters setValue:oauth_consumer_key forKey:@"oauth_consumer_key"];
[standardParameters setValue:oauth_nonce forKey:@"oauth_nonce"];
[standardParameters setValue:oauth_signature_method forKey:@"oauth_signature_method"];
[standardParameters setValue:oauth_timestamp forKey:@"oauth_timestamp"];
[standardParameters setValue:oauth_version forKey:@"oauth_version"];
[standardParameters setValue:oauth_token forKey:@"oauth_token"];
NSString *parametersString = CHQueryStringFromParametersWithEncoding(standardParameters, NSUTF8StringEncoding);
/* OAUTH SIGNATURE */
NSString *request_url = @"https://api.tradeking.com/v1/market/ext/quotes.xml?symbols=aapl";
NSString *oauth_consumer_secret = @"xxxxxx";
NSString *oauth_token_secret = @"xxxx";
NSString *baseString = [@"GET" stringByAppendingFormat:@"&%@&%@", request_url.utf8AndURLEncode, parametersString.utf8AndURLEncode];
// append oauth token secret to consumer secret
NSString *secretString = [oauth_consumer_secret.utf8AndURLEncode stringByAppendingFormat:@"&%@", oauth_token_secret.utf8AndURLEncode];
NSString *oauth_signature = [self.class signClearText:baseString withSecret:secretString];
standardParameters[@"oauth_signature"] = oauth_signature;
/* CREATE HEADER */
NSMutableArray *parameterPairs = [NSMutableArray array];
for (NSString *name in standardParameters)
{
NSString *aPair = [name stringByAppendingFormat:@"=\"%@\"", [standardParameters[name] utf8AndURLEncode]];
[parameterPairs addObject:aPair];
}
NSString *oAuthHeader = [@"OAuth " stringByAppendingFormat:@"%@", [parameterPairs componentsJoinedByString:@", "]];
[request setValue:oAuthHeader forHTTPHeaderField:@"Authorization"];
/* REQUEST */
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSString *reponseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Response string: %@, error: %@", reponseString, error);
}];
And the response I get back is "signature_invalid." Anyway, I left out the portions related to url encoding and the signature generation because they would probably take too much space. I was wondering if I made a mistake here, or if there is something wrong with the actual function that creates the signature.
It turns out the signature creation part (HMAC-SHA1) was correct. I just needed to remove the symbols from the header before the doing the HTTP request, or else the OAUTH request would think that the signature would not match the request itself. The fixed code:
// this is a convenience function for oauth
- (NSData *)fetchDataForURL:(NSString *)url paramPairs:(NSArray *)paramPairs error:(NSError**)error response:(NSHTTPURLResponse**)response timeOut:(float)timeOut {
NSMutableString *mutableURL = [[NSMutableString alloc] init];
[mutableURL appendString:url];
int paramPairCount = 0;
for (OPTTradeKingParamPair *paramPair in paramPairs) {
if (paramPairCount > 0)
[mutableURL appendString:@"&"];
[mutableURL appendFormat:@"%@=", paramPair.param];
int argCount = 0;
for (NSString *arg in paramPair.args) {
if (argCount > 0)
[mutableURL appendString:@","];
[mutableURL appendFormat:@"%@", arg];
argCount++;
}
paramPairCount++;
}
//NSLog(@"URL request: %@", mutableURL);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:mutableURL] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];
request.HTTPMethod = @"GET";
// oauth fields
NSString *oauth_timestamp = [NSString stringWithFormat:@"%lu", (unsigned long)[NSDate.date timeIntervalSince1970]];
NSString *oauth_nonce = [NSString getNonce];
NSString *oauth_consumer_key = @"*****";
NSString *oauth_token = @"*****";
NSString *oauth_signature_method = @"HMAC-SHA1";
NSString *oauth_version = @"1.0";
NSMutableDictionary *standardParameters = [NSMutableDictionary dictionary];
[standardParameters setValue:oauth_consumer_key forKey:@"oauth_consumer_key"];
[standardParameters setValue:oauth_nonce forKey:@"oauth_nonce"];
[standardParameters setValue:oauth_signature_method forKey:@"oauth_signature_method"];
[standardParameters setValue:oauth_timestamp forKey:@"oauth_timestamp"];
[standardParameters setValue:oauth_version forKey:@"oauth_version"];
[standardParameters setValue:oauth_token forKey:@"oauth_token"];
NSMutableArray *paramPairKeys = [[NSMutableArray alloc] init];
for (OPTTradeKingParamPair *paramPair in paramPairs) {
NSString *key = paramPair.param;
NSMutableString *args = [[NSMutableString alloc] init];
int argCount = 0;
for (NSString *arg in paramPair.args) {
if (argCount > 0)
[args appendString:@","];
[args appendFormat:@"%@", arg];
}
[standardParameters setValue:args forKey:key];
[paramPairKeys addObject:key];
}
NSString *parametersString = CHQueryStringFromParametersWithEncoding(standardParameters, NSUTF8StringEncoding);
// use URL and remove ? (always at end of URL)
NSString *request_url = [url stringByReplacingOccurrencesOfString:@"?" withString:@""];
NSString *oauth_consumer_secret = @"*****";
NSString *oauth_token_secret = @"*****";
NSString *baseString = [@"GET" stringByAppendingFormat:@"&%@&%@", request_url.utf8AndURLEncode, parametersString.utf8AndURLEncode];
// append oauth token secret to consumer secret
NSString *secretString = [oauth_consumer_secret.utf8AndURLEncode stringByAppendingFormat:@"&%@", oauth_token_secret.utf8AndURLEncode];
NSString *oauth_signature = [self.class signClearText:baseString withSecret:secretString];
standardParameters[@"oauth_signature"] = oauth_signature;
// remove symbols portion for header before doing request
for (NSString* keyToRemove in paramPairKeys) {
[standardParameters removeObjectForKey:keyToRemove];
}
[standardParameters removeObjectForKey:@"symbols"];
NSMutableArray *parameterPairs = [NSMutableArray array];
for (NSString *name in standardParameters)
{
NSString *aPair = [name stringByAppendingFormat:@"=\"%@\"", [standardParameters[name] utf8AndURLEncode]];
[parameterPairs addObject:aPair];
}
parameterPairs = [NSMutableArray arrayWithArray:[parameterPairs sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]];
NSString *oAuthHeader = [@"OAuth " stringByAppendingFormat:@"%@", [parameterPairs componentsJoinedByString:@", "]];
[request setValue:oAuthHeader forHTTPHeaderField:@"Authorization"];
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];
[OPTCrashModule addErrorWithData:data error:*error];
return data;
}
How to call the code:
- (DataAPIReturnVal)findInfoForSymbols:(NSArray*)tickerSymbols returnedTickerInfos:(NSMutableArray *)tickerInfos
{
NSMutableString *symbols = [[NSMutableString alloc] init];
int index = 0;
for(NSString *tickerSymbol in tickerSymbols)
{
if (index > 0) [symbols appendString:@","];
OPTTickerInfo *tickerInfo = [[OPTTickerInfo alloc] init];
[tickerInfo setName:tickerSymbol];
[tickerInfos addObject:tickerInfo];
[symbols appendString:[tickerSymbol uppercaseString]];
index++;
}
NSMutableArray *paramPairs = [[NSMutableArray alloc] init];
OPTTradeKingParamPair *paramPair = [[OPTTradeKingParamPair alloc] initWithParam:@"symbols" args:@[symbols]];
[paramPairs addObject:paramPair];
DataAPIReturnVal retVal = DataAPIGeneralError;
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData * retData = [self fetchDataForURL:@"https://api.tradeking.com/v1/market/ext/quotes.json?" paramPairs:paramPairs error:&error response:&response timeOut:[tickerSymbols count]];
// ....and so on
}
Param pairs is simply an array of objects, where each object is a "param" string and and "args" array. Param can be something like "symbols" and the "args" can the actual symbol arguments i.e. fas, faz, msft, etc.
Extra stuff that people have requested:
- (NSString *)signClearText:(NSString *)text withSecret:(NSString *)secret
{
NSData *secretData = [secret dataUsingEncoding:NSUTF8StringEncoding];
NSData *clearTextData = [text dataUsingEncoding:NSUTF8StringEncoding];
unsigned char result[20];
hmac_sha1((unsigned char *)[clearTextData bytes], [clearTextData length], (unsigned char *)[secretData bytes], [secretData length], result);
//Base64 Encoding
char base64Result[32];
size_t theResultLength = 32;
Base64EncodeData(result, 20, base64Result, &theResultLength);
NSData *theData = [NSData dataWithBytes:base64Result length:theResultLength];
return [NSString.alloc initWithData:theData encoding:NSUTF8StringEncoding];
}
I need to spend some time packaging the code for general use. There is a bunch of other stuff I have to gut out before the general public can reuse it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With