Authentication is a bit tricky to follow, and ultimately you may want to leave the details to one of the Flickr API kits (covered later in the chapter). However, you may still be interested in working through the details at least once so that you know what’s going on below the hood before you use someone else’s library. Besides, there will be other authentication schemes out there besides Flickr’s that you will want to use. Getting a solid handle on Flickr’s authentication scheme is good preparation for more quickly understanding those other authentication schemes.
You can find the specification for Flickr authentication here:
http://www.flickr.com/services/api/auth.spec.html
There are three types of authentication cases to handle with Flickr:
Web applications
Desktop applications
Mobile applications
Each scheme is different because of the differing natures of each type of application. For example, the author of a web application can configure it to have a URL through which Flickr would be able to communicate. It’s hard to guarantee that a desktop application would have a URL through which such communication could happen. In this book, I cover only the specific case of authentication for web applications. Once you understand this case, you will be able to understand the others without much problem.
Three parties are involved in the authentication dance:
Flickr
A third-party application that is using the Flickr API and that I refer to as the app
A person who is both a user of the app and a Flickr user
Authorization is required when an app is calling a Flickr method that requires a
permission level of read
, write
, or
delete
—anything but none. Through authorization, the app is granted a
specific permission level by the user to
access the Flickr API on the user’s behalf. Flickr creates a
token that ties a specific app with a specific user and a
specific permission level to embody this authorization act. The authentication dance is
all about how that token gets created, used in conjunction with specific API calls, and
can be managed and possibly revoked by the user. The details are a bit complicated
because this process must also fulfill certain design criteria, which you can surmise
from how the authorization scheme is designed:
The three parties need to be reliably and securely identified and associated in the process of authorization.
The user must be able to undo an authorization act given to a specific app.
The protocol must be done using HTTP and not HTTPS. That is, all the parameters being passed are visible to potential third-party interlopers. In other words, knowledge of the token itself should not allow another app to have the token’s permissions.
The app should not need to know anything a priori about a person’s Flickr identity to secure permission.
The current Flickr authorization scheme is not the first one it used. In the early days of Flickr, users granted the power to an app to act on their behalf by giving the apps their Flickr username and password. Doing so meant that in order to revoke an app’s permission, users would have to change their Flickr password. Of course, doing that would also instantly revoke permissions of other third-party apps with knowledge of the user’s password. The new authorization scheme is meant to correct obvious problems with the old scheme. Why should you as a user have to use your Flickr password for anything other than your dealings with Flickr? Why should revoking permissions to one app mean revoking permissions to other apps?
Let’s now look at the authorization scheme used with Flickr. We first need to set up some permissions.
Let’s now get down to the details of the process of authentication for
web-based applications, keeping the authorization design criteria in mind.
Let’s have a specific example in mind. The app, which I will call
ContactLister, displays the list of contacts for a given Flickr user. It
specifically uses the flickr.contacts.getList
method, which
requires authorization with read
permission. (A Flickr user’s
contacts list is private.) Let’s also make up a hypothetical user called
Lois.
For ContactLister to get permission from Lois, why couldn’t ContactLister just directly display a screen asking Lois to give it permission to read her contact list—and then relay that fact to Flickr? For starters, how does ContactLister prove to Flickr that Lois did in fact give ContactLister permission to access her photos? In the old days of Flickr, ContactLister would have Lois’s Flickr username and password. At that time, ContactLister might as well have been Lois since it’s the Flickr username/password that Flickr used to authenticate a user.
The solution that Flickr came up with is based on that Flickr needs to
establish unambiguously that Lois is with full knowledge of (that is, not being
tricked into) giving ContactLister (and not some other third-party app)
read
(and not some other) permission. To do that, Flickr needs
to mediate communication between Lois and ContactLister.
So instead of ContactLister directly prompting Lois for permission,
ContactLister directs Flickr to prompt Lois for read
permission by
formulating the following Flickr URL that it directs Lois to:
http://flickr.com/services/auth?api_key={api_key}&perms={perms}&api_sig={api_sig}
Let’s look at the various arguments. You are familiar with the
api_key
; perms
would be set to read
in this circumstance.
The part that is new in this chapter is the api_sig
. It is
the act of calculating the api_sig
and attaching it to method
calls in the Flickr API that we refer to as signing the
call. The purpose of signing a call is to reliably establish the identity of
the signer, the one formulating the URL. Why isn’t the
api_key
enough to establish the identity of the caller? In
some circumstances, it would be if no one but the author of ContactLister
and Flickr knew this api_key
. On another level, Flickr API keys
are sent unencrypted every time a call is made to the Flickr API, akin to
passwords being sent in plain text. Hence, the api_key
alone is
an insufficient foundation for signing this call. You shouldn’t be
able to easily fake a signature.
When you sign up for a Flickr API key, in addition to getting a
key
, you get a corresponding string: secret
.
As the name implies, you are supposed to keep secret
secret so
that in theory only you and Flickr know it. Go to http://www.flickr.com/services/api/keys/
to see your
own keys and secrets.
ContactLister has to use this secret to calculate the api_sig
and thereby sign the call. The api_sig
is calculated according
to the following algorithm for any Flickr API call:
Make a signature string that starts with the secret followed by a
concatenation of all the name/value pairs of the arguments to be
passed to Flickr, sorted alphabetically by name—excluding the
api_sig
but including method
. The
values need to UTF-8 encoded but not URL-encoded.
The api_sig
is then the hexadecimal digest of the md5
hash of the signature string.
The following is a Python function that takes a secret and a dictionary of
name/value pairs and returns the corresponding api_sig
:
def calcSig(secret,params): import md5 l = params.keys() l.sort() hash = '' for key in l: hash += str(key) + params[key].encode('utf-8') hash = secret + hash api_sig = md5.new(hash).hexdigest() return api_sig
Let’s first run through a concrete example and then discuss how this process constitutes signing the call. Consider the following sample key and secret:
Key: 020338ddabd2f41ae7ce9413a8d51429
Shared secret: f0fc085289c7677a
The signature string is then as follows:
{secret}api_key{api_key}perms{perms}
which is as follows:
f0fc085289c7677aapi_key{api_key}permsread
The md5 hexadecimal digest of the string is then as follows:
f9258a76e4ad3cb5fa40bd8b0098d119
Therefore, the signed call is as follows:
http://flickr.com/services/auth?api_key={api_key}&perms=read&api_sig=f9258a76e4ad3cb5fa40bd8b0098d119
So when ContactLister directs Lois to go to this URL, Flickr first
determines the integrity of this call by performing the same signature
calculation as ContactLister did in the first place: find the secret that
corresponds to the api_key
, sort all the parameters by key
(except for the api_sig
parameter), form the signature string,
and then compare it to the value of the api_sig
parameter. If
two match up, then Flickr can conclude the call did indeed come from
ContactLister because presumably the author of ContactLister is the only one
other than Flickr who knows the key/secret combination.
You might ask, why can’t someone take the api_sig
from
the call and reverse the md5 calculation to derive the secret? Although
it’s straightforward to calculate the md5 hash of a string, it’s
much more difficult computationally to go in the other direction. For the
purposes here, you should think of this reverse direction for md5 as
practically—but not theoretically—impossible. Moreover, using
md5 makes it difficult to change the parameters of the call. If you change,
say, perms=read
to perms=delete
, you get a
different api_sig
, which is very hard to calculate without
knowing secret
.
Note | |
---|---|
md5, it turns out, does have limitations as a cryptographic hash
function. Researchers have demonstrated how to take an md5 hash and
create another string that will give you the same md5 hash. Can this
weakness be used to issue fake Flickr calls? I don’t know; see
|
At any rate, assuming a properly signed call to http://flickr.com/services/auth
, Flickr now knows
reliably that it is indeed ContactLister asking for read
permission. Remember, though, that the end goal, a token, ties three things
together: an app, a permission level, and a user. The call reliably ties the app
and permission together for Flickr. However, the call has no explicit mention of
a user at all. There’s no parameter for user_id
, for
instance.
That ContactLister doesn’t have to pass to Flickr anything about
Lois’s Flickr account is a virtue—not a problem. Why should a
third-party app have to know anything a priori about
a person’s relationship to Flickr? So, how does Flickr figure out the user
to tie to the request by ContactLister for the read
permission? The
fact is that it’s Lois—and not someone else—who uses the
authorization URL:
http://flickr.com/services/auth?api_key={api_key}&perms=read&api_sig=f9258a76e4ad3cb5fa40bd8b0098d119
When Lois loads the authorization URL in her browser, Flickr then determines the user in question. If Lois is logged in, then Flickr knows the user in question is Lois. If no one is logged in to Flickr, then Lois will be sent through the login process. In either case, it’s Flickr that is figuring out Lois’s identity as a Flickr user and taking care of her authenticating to Flickr. In that way, Flickr can establish to its own satisfaction the identity of the user involved in the authorization dance—rather than trusting ContactLister to do so.
Now that Flickr knows for sure the identity of the app, the permission level
requested, and the user involved, it still needs to actually ask Lois whether
it’s OK to let ContactLister have the requested read
permission. If Lois had not already granted ContactLister such permission, then
Flickr presents to Lois a screen that clearly informs her of
ContactLister’s request. The fact that such a display comes from Flickr
instead of ContactLister directly should give Lois some confidence that Flickr
can track what ContactLister will do with any permissions she grants to it and
thereby hold the authors of ContactLister accountable.
Assuming that Lois grants ContactLister read permission, Flickr must now
inform ContactLister of this fact. (Remember, the permission granting is
happening on the Flickr site.) Flickr communicates this authorization act by
sending the HTTP GET
request to the callback-URL
for
ContactLister with what Flickr calls a frob. Flickr knows
the callback-URL
to use because part of registering a web
application to handle authorization is specifying a callback URL at the
following location:
http://www.flickr.com/services/api/keys/{api-key}/
where the api-key
is that for the app. In other words,
ContactLister must handle a call from Flickr of the following form:
callback-URL?frob={frob}
A frob is akin to a session ID. It lets ContactLister know that some form of
authorization has been granted to ContactLister. To actually get the token that
ContactLister needs to use the requested read
permission,
ContactLister needs to use flickr.auth.getToken
to exchange the
frob for the token. Frobs aren’t meant to be the permanent representation
of an authorization act. Frobs expire after 60 minutes or after
flickr.auth.getToken
is used to redeem the frob for a token.
This exchange ensures that ContactLister receives a token and that Flickr knows
that ContactLister has received the token. Note that
flickr.auth.getToken
is also a signed call with two mandatory
arguments: api_key
and frob
—in addition to
api_sig
, of course. The returned token is expressed in the
following form (quoting from http://www.flickr.com/services/?api/?flickr.auth.getToken.html
):
<auth> <token>976598454353455</token> <perms>write</perms> <user nsid="12037949754@N01" username="Bees" fullname="Cal H" /> </auth>
Note that it’s the token that tells ContactLister the details of what is being authorized: the Flickr user and the permission granted. Now, ContactLister knows the Flickr identity of Lois—without ever needing Lois to tell ContactLister directly.
ContactLister can now actually make the call to
flickr.contacts.getList
. How so? In addition to signing a call
to flickr.contacts.getList
, ContactLister adds the appropriate
authorization information by adding the following argument to the call and
signing it appropriately:
auth-token={token}
We should note moreover that Lois, like all users, can revoke any permission she had previously granted here:
http://flickr.com/services/auth/list.gne
It’s nice for Lois to know that she doesn’t have to convince ContactLister to stop accessing her account. She just tells Flickr.
That’s the narrative of how to do Flickr authorization for web applications. Now let’s look at it implemented in PHP. There are two pieces of code. The first generates the authorization URL. (To use it, use your own API key and secret.)
<?php $api_key = ""; $secret = "f0fc085289c7677a"; $perms = "read"; function login_link($api_key,$secret,$perms) { # calculate API SIG # sig string = secret + [arguments listed alphabetically name/value -- # including api_key and perms] $sig_string = "{$secret}api_key{$api_key}perms{$perms}"; $api_sig = md5($sig_string); $url = "http://flickr.com/services/auth?api_key={$api_key}&perms={$perms}&api_sig={$api_sig}"; return $url; } $url = login_link($api_key,$secret,$perms); ?> <html> <body><a href="<?php print($url);?>">Login to Flickr</a></body> </html>
To confirm that you have things set up correctly, if you run the app, you should get a prompt from the Flickr site asking for access (see Figure 6-2).[103]
Figure 6.2. Figure 6-2.Flickr authorization screen. (Reproduced with permission of Yahoo! Inc. ® 2007 by Yahoo! Inc. YAHOO! and the YAHOO! logo are trademarks of Yahoo! Inc.)
The second piece of code is the authentication-handling script whose URL is the callback URL registered to the API key. It reads the frob, gets the token, and then lists the contacts of the user (a type of access that demonstrates that authorization is working, since without authorization, an app will not be able to access a user’s contact list). To try this yourself, you will need to create this file and then enter its URL in the Callback URL field of your app’s key configuration screen at Flickr:[104]
<?php ##insert your own Flickr API KEY here $api_key = "[API_KEY]"; $secret = "[SECRET]"; $perms = "read"; $frob = $_GET['frob']; function getResource($url){ $chandle = curl_init(); curl_setopt($chandle, CURLOPT_URL, $url); curl_setopt($chandle, CURLOPT_RETURNTRANSFER, 1); $result = curl_exec($chandle); curl_close($chandle); return $result; } function getContactList($api_key, $secret, $auth_token) { # calculate API SIG # sig string = secret + [arguments listed alphabetically name/value -- # including api_key and perms]; don't forget the method call $method = "flickr.contacts.getList"; $sig_string = "{$secret}api_key{$api_key}auth_token{$auth_token}method{$method}"; $api_sig = md5($sig_string); $token_url = "http://api.flickr.com/services/rest/?method=flickr.contacts.getList&api_key={$api_key}&auth_token={$auth_token}&api_sig={$api_sig}"; $feed = getResource($token_url); $rsp = simplexml_load_string($feed); return $rsp; } function getToken($api_key,$secret,$frob) { # calculate API SIG # sig string = secret + [arguments listed alphabetically name/value -- # including api_key and perms]; don't forget the method call $method = "flickr.auth.getToken"; $sig_string = "{$secret}api_key{$api_key}frob{$frob}method{$method}"; $api_sig = md5($sig_string); $token_url = "http://api.flickr.com/services/rest/?method=flickr.auth.getToken&api_key={$api_key}&frob={$frob}&api_sig={$api_sig}"; $feed = getResource($token_url); $rsp = simplexml_load_string($feed); return $rsp; } $token_rsp = getToken($api_key,$secret,$frob); $nsid = $token_rsp->auth->user["nsid"]; $username = $token_rsp->auth->user["username"]; $auth_token = $token_rsp->auth->token; $perms = $token_rsp->auth->perms; # display some user info echo "You are: ", $token_rsp->auth->user["fullname"],"<br>"; echo "Your nsid: ", $nsid, "<br>"; echo "Your username: ", $username,"<br>"; echo "auth token: ", $auth_token, "<br>"; echo "perms: ", $perms, "<br>"; # make a call to getContactList $contact_rsp = (getContactList($api_key,$secret,$auth_token)); $n_contacts = $contact_rsp->contacts["total"]; $s = "<table>"; foreach ($contact_rsp->contacts->contact as $contact) { $nsid = $contact['nsid']; $username = $contact['username']; $realname = $contact['realname']; $s = $s . "<tr><td>{$realname}</td><td>{$username}</td><td>{$nsid}</td></tr>"; } $s = $s . "</table>"; echo "Your contact list (which requires read permission) <br>"; echo "Number of contacts: {$n_contacts}<br>"; echo $s; ?>
Note | |
---|---|
Uploading photos to Flickr is a major part of the Flickr API that is not
covered in this book. I suggest reading the documentation ( |