BookmarkSubscribeRSS Feed
JanThomasLøwe
SAS Employee

Have you ever run into an API only accepting OAuth1.0 requests? While technically outdated and not widely used today, OAuth 1.0 is still around, and many older applications still use it.

 

If you like me often need to integrate to APIs for work or private use, you might have come across OAuth 1.0 and the hassles of calculating the signature.

 

I developed a little macro for easy access for making OAuth 1.0 http requests. The API I had to work with was not Flickr but used here for the example case.

 

Here is a curl example for making an OAuth 1.0 authenticated request to the Flickr API to return images that have been posted to a group I have joined.

 

 

 

 

curl -L -X GET 'https://www.flickr.com/services/rest?nojsoncallback=1&format=json&method=flickr.photos.search&group_id=2105945%40N24&extras=url_o&sort=date-posted-asc' \
-H 'Authorization: OAuth oauth_consumer_key="867e7e0fbab6cXXXXX690551d31f3ee2",oauth_token="72157720XXXXX8608-b2da3712d39e4a6d",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1701797698",oauth_nonce="BBBzcJnHAAT",oauth_version="1.0",oauth_signature="QO1kh7l4EcwNrVeHZLPxgAS%2BkuM%3D"'

 

 

 

 

First part is API endpoint including parameters, next part is the authorization parameters where last parameter is the calculated OAuth signature and the tricky part.

 

Initially I need a Flickr account for my example and register an API Key to my account. Then the OAuth flow has 3 steps I need to complete before making actual requests to the API:

  • Temporary Credentials Acquisition: The client gets a set of temporary credentials from the server.
  • Authorization: The user "authorizes" the request token to access their account.
  • Token Exchange: The client exchanges the short-lived temporary credentials for a long-lived token.

After a long-lived access token has been granted to me, I can start to make authenticated requests to the Flickr API.

 

Here is my developed macro which can be used for the requests for temporary credentials acquisition, token exchange and sending requests to the API using the long-lived token. 

 

                       		/*----------------------------------------------*/
%macro oauth1_0ApiRequest(  /*-------------_OAUTH 1.0 HTTP Requests---------*/
       http_method=,    	/* Required - get, post etc.					*/
       endpoint_url=,     	/* Required - use %nrstr() if url include &'s	*/
       response_file=,  	/* Required - filename reference to output file */
       oauth_consumer_key=, /* Required - API Consumer Key                  */
       consumer_secret=,   	/* Required - Secret used in signing key        */
       oauth_callback=,    	/* Optional - Needed in REQUEST TOKEN step      */
       oauth_token=,        /* Optional - Short-lived returned in REQUEST   */
							/* TOKEN STEP and long-lived returned in TOKEN  */
							/* EXCHANGE step 							    */
       token_secret=,       /* Optional - Same as oauth_token and			*/
	   						/* used in signing key      					*/
       oauth_verifier=      /* Optional - Returned in AUTHORIZATION step    */
	   						/* and used in TOKEN EXCHANGE step				*/
	   );             		/*----------------------------------------------*/

	%let oauth_signature_method = HMAC-SHA1;
	%let unix_timestamp_offset = %sysfunc(intck(seconds,'01jan1960:00:00:00'dt,'01jan1970:00:00:00'dt)); 
	%let sas_timestamp_utc = %sysfunc(floor(%sysfunc(tzones2u(%sysfunc(datetime())))));
	%let oauth_timestamp = %eval(&sas_timestamp_utc - &unix_timestamp_offset);
	%let oauth_nonce = %sysfunc(putc(%eval(&oauth_timestamp*1000),$14.));
	%let oauth_version = 1.0;
	%if &oauth_callback NE %then %let oauth_callback = %sysfunc(urlencode(&oauth_callback));
	
	%put &=http_method;
	%put &=endpoint_url;
	%put &=response_file;
	
	%let base_url = %scan(&endpoint_url,1,?);
	%put &=base_url;
	
	proc sql;
		create table parms as
		select lowcase(name) as key, value
		from dictionary.macros
		where lowcase(scope)='oauth1_0apirequest'
		and lowcase(name) in ("oauth_signature_method","oauth_timestamp","oauth_nonce","oauth_version","oauth_consumer_key","oauth_callback","oauth_token","oauth_verifier")
		and value NE "";
	;quit;
	
	%if %index(&endpoint_url,?)>0 %then %do;
		%let parameters = %nrstr(&)%qscan(&endpoint_url,2,?);
		%put &=parameters;
		data parameters;
		set parms(obs=0);
		%do i=1 %to %sysfunc(count(&parameters,&));
			key = "%scan(%scan(&parameters,&i,&),1,=)"; 
			value = "%scan(%scan(&parameters,&i,&),2,=)";
			output;
		%end;
		run;
		proc append base=parms data=parameters force;
		run;
	%end;
	
	proc sort data = parms; by key; run;
	
	data _null_;
	set parms end = eof;
	length parameter_string encoded_base_string $500.;
	retain parameter_string;
	if _n_ = 1 then 
		parameter_string = cats(key,'=',value);
	else
		parameter_string = cats(parameter_string,'&',key,'=',value);
	if eof then do;
		parameter_string = urlencode(strip(parameter_string));
			put parameter_string=;
		encoded_base_string = cats("%upcase(&http_method)",'&',urlencode("&base_url"),'&',parameter_string);
		put encoded_base_string=;
		if "&token_secret" NE "" then signing_key = cats(urlencode("&consumer_secret"),"&",urlencode("&token_secret"));
		else signing_key = cats(urlencode("&consumer_secret"),"&");
			put signing_key=;
		digest = hashing_hmac('sha1',strip(signing_key),strip(encoded_base_string));
		encDigest=urlencode(strip(put(input(strip(digest),$hex256.),$base64x64.)));
			put encDigest=;
		authorization1 = cats("'",'OAuth oauth_consumer_key="',"&oauth_consumer_key",'",oauth_signature_method="',"&oauth_signature_method",'",oauth_timestamp="',"&oauth_timestamp",'"');
		if "&oauth_callback" NE "" then oauth_callback = cats(',oauth_callback="',"&oauth_callback",'"'); else oauth_callback = "";
		if "&oauth_token" NE "" then oauth_token = cats(',oauth_token="',"&oauth_token",'"'); else oauth_token = "";
		if "&oauth_verifier" NE "" then oauth_verifier = cats(',oauth_verifier="',"&oauth_verifier",'"'); else oauth_verifier = "";
		authorization2 = cats(',oauth_nonce="',"&oauth_nonce",'",oauth_version="',"&oauth_version",'",oauth_signature="',strip(encDigest),'"',"'");
		authorization = strip(authorization1)!!strip(oauth_callback)!!strip(oauth_token)!!strip(oauth_verifier)!!strip(authorization2);
		call symput('authorization',authorization);
			put authorization;
	end;
	run;
	
	proc datasets noprint;
	delete parms;
	quit;
	
	proc http 
		method="%upcase(&http_method)" url="&endpoint_url" out=&response_file;
		headers 'Authorization'=&authorization
		;
	run;

%mend oauth1_0ApiRequest;

 

Back to my example case. Here is some code where I use my macro to complete the OAuth flow and then requests the 50 most recent images posted in a group for Border Collie fans that I am member of.

 

/* Flickr OAuth Authorization Flow and API request example. Windows version.*/
/* API Keys registered to my account (some characters are masked) */ %let apiKey = 867e7e0fbab6cXXXX690551d31f3ee2; %let apiSecret = f9f737XXXXa2a682; /* STEP 1 - TEMPORARY CREDENTIALS ACQUISITION */ %let endpoint = https://www.flickr.com/services/oauth/request_token; %let callback = http://example.com; filename out temp encoding='utf-8'; %oauth1_0ApiRequest( http_method=get, endpoint_url=&endpoint, response_file=out, oauth_consumer_key=&apiKey, consumer_secret=&apiSecret, oauth_callback=&callback ); data _null_; infile out; input; do i = 0 to count(_infile_,'&'); key = scan(scan(_infile_,i+1,'&'),1,'='); value = scan(scan(_infile_,i+1,'&'),2,'='); call symput(key,urldecode(value)); end; run; %put &=oauth_token; %put &=oauth_token_secret; filename out clear; /* STEP 2 - AUTHORIZATION: THE USER "AUTHORIZES" THE REQUEST TOKEN TO ACCESS THEIR ACCOUNT. */ * copy url from log to a browser, authorize the account and copy the oauth_verifier value returned in the callback URL.; %put https://www.flickr.com/services/oauth/authorize?oauth_token=&oauth_token; * or run below code, authorize the account and copy the oauth_verifier value returned in the callback URL.; options noxwait; data _null_; length auth $1000; auth = 'start chrome "' || "https://www.flickr.com/services/oauth/authorize?oauth_token=" || strip(symget('oauth_token')) || '"'; call system(auth); run; *paste in the oauth_verifier value; %let oauth_verifier = REPLACE-WITH-VERFIER; /*Received from above callback URL after request has been authorized.; /* STEP 3 - TOKEN EXCHANGE: THE CLIENT EXCHANGES THE SHORT-LIVED TEMPORARY CREDENTIALS FOR A LONG-LIVED TOKEN. */ %let endpoint = https://www.flickr.com/services/oauth/access_token; filename out temp encoding='utf-8'; %oauth1_0ApiRequest( http_method=get, endpoint_url=&endpoint, response_file=out, oauth_consumer_key=&apiKey, consumer_secret=&apiSecret, oauth_token=&oauth_token, token_secret=&oauth_token_secret, oauth_verifier=&oauth_verifier ); data _null_; infile out; input; do i = 0 to count(_infile_,'&'); key = scan(scan(_infile_,i+1,'&'),1,'='); value = scan(scan(_infile_,i+1,'&'),2,'='); call symput(key,urldecode(value)); end; run; %put &=oauth_token; %put &=oauth_token_secret; filename out clear; /* AUTHORIZATION IS COMPLETE. START MAKING ACTUAL REQUESTS TO THE API */ filename out temp encoding='utf-8'; %oauth1_0ApiRequest( http_method=get, endpoint_url=%nrstr(https://www.flickr.com/services/rest?nojsoncallback=1&format=json&method=flickr.photos.search&group_id=2105945@N24&extras=url_o&sort=date-posted-desc&per_page=100), response_file=out, oauth_consumer_key=&apiKey, consumer_secret=&apiSecret, oauth_token=&oauth_token, token_secret=&oauth_token_secret ); libname jsondata json fileref=out; proc copy in=jsondata out =work; run; filename out clear; /* macro to download images */ %let download_folder = c:\temp; %macro download(url); %put &url; filename image "&download_folder\%scan(&url,-1,/)"; %put "&download_folder\%scan(&url,-1,/)"; proc http clear_cache url="&url" method="get" out=image; run; filename image clear; %mend; /* loop and download images */ data _null_; set jsondata.photos_photo(obs=50); where url_o NE ""; put "downloads " url_o ".."; length s $1000; s = cats('%download(',url_o,');'); rc = dosubl(s); run; libname jsondata clear;

 

As a result I get a bunch of nice images downloaded of my favorite dog breed. Merry Christmas. 😊

 

JanThomasLwe_0-1701796900269.png