Nordaaker
Web Services with OAuth
What is OAuth?
• A simple open standard for API Authentication
Do we need it? We’ve got OpenID
★ Not a replacement, a complementary API.
★ OAuth is Authorization, Openid is Authentication
★ OpenID users don’t have passwords, so can’t ask them for that when they try to access the API
★ OAuth is token based, does not require password
A bit more about OAuth
★ Not a new idea - FlickrAuth, Google AuthSub, BBAuth, etc.
★ Open Standard - http://oauth.net/
★ Wide industry support
★ AOL, Eye-Fi, Facebook, Garmin, Google, LinkedIn, Ma.gnolia, Microsoft, MySpace, Plaxo, Pownce, Salesforce, Songbird, Veodia, and Yahoo!. and more!
★ Easy to understand
★ Easy to implement
The Usual Suspects
The Conversation
<me> Hey Appfresh update my iusethis count<appfresh> Lemme check with iusethis
<appfresh> Hey iusethis, can I update marcus’ apps?<iusethis> Here’s a ticket, give that to him,and I’ll check with him* iusethis hands appfresh a request token
<appfresh> Hey marcus, get this ticket stamped with iusethis so I can update your data for you, please<me> *meh*, allright
* appfresh opens a browser window to iusethis<iusethis> Marcus, you fine with appfresh updating your counts?<me> Go ahead already, I’m waiting here<iusethis> Ok, here’s a stamp for your ticket.* iusethis marks the token as approved
* appfresh hands the request token over to iusethis and gets an access token in return once iusethis sees that it is approved* appfresh updates my iusethis counts. *yay*
The data flow
★ Consumer must be registered with service
★ Consumer sends a signed OAuth request to the request_token endpoint, service provider returns a token as a post body in the response content
★ Consumer opens the user auth endpoint with the specified token. if web service, provide a callback to redirect back after auth. user verifies token, and returns to service
★ Consumer hits access token endpoint with request token and gets a valid access token
The OAuth request
★ POST/GET Request
★ oauth_consumer_key => 'foo',
★ oauth_consumer_secret => 'bar',
★ oauth_request_method => 'POST',
★ signature_method => 'HMAC-SHA1',
★ oauth_timestamp => '1191242090',
★ oauth_nonce => 'hsu94j3884jdopsl',
Adding this for your Catalyst based API
We need to store data somewhereI’ll use a DBIC model in this example:
DB::Consumer DB::RequestTokenDB::AccessToken
Then we just use Net::OAuth directly in the controller:
request_token method
my $oauth_params = $c->get_oauth_parameters(@params);my $consumer=$c->model('DB::Consumer')->find( $c->req->params->{consumer_secret}) or $c->detach('/usererror',["Consumer not registered"]);
my $request = eval { Net::OAuth::RequestTokenRequest->new( request_url => $c->req->uri, request_method => $c->req->method, consumer_secret => $consumer->secret, signature_key => $c->get_signature_key, ) }; # handle errors here$c->stash->{requesttoken}= $consumer->new_requesttoken($request);
verifying the request
sub verify_access : Local { my ($self,$c)=@_;
$c->res->redirect($c->uri_for(‘/login’) unless $c->user_exists;
my @params = qw/consumer_key signature_method signature timestamp nonce token version/; # find and validate token return $c->res->redirect('/login') unless $c->user_exists; if($c->req->params->{verify}) { $token->verified(1);$token update; }}
access_token
my ($self,$c)=@_;my $consumer= ... # like previous
my $request = eval { Net::OAuth::AccessTokenRequest->new(
request_url => $c->uri_for('/oauth/access_token'), request_method => $c->req->method, consumer_secret => $consumer->secret, token_secret => $request_token->secret, signature_key => $signature_key, }; $token= $consumer->get_access_token($oauth_params{token}) || $c->detach('/usererror',['Request token not found']); $c->stash->{access_token}=$token ->create_access_token($token);
A bit of work
★ In addition you need a method to verify that the access token was provided for api methods
★ A bit of a hassle right now
★ Will commit a server example to catalyst-trunk shortly
★ Watch for Catalyst-Controller-OAuth sometime in the near future.
★ Might also be a Reaction component
Jifty
Jifty::Plugin::OAuth makes this easy.
Add this to your config file :
And presto:
framework: Plugins: - OAuth: {}
Jifty Screenshot
Jifty Screenshot
Jifty continued
★ Make sure that /oauth/authorize requires login.
★ Make sure that /oauth/request_token and /oauth/access_token doesn’t.
★ In beta. Still has some limitations. Consumers has to be manually registered.
Example: GMail contacts at OAuth
Example: GMail contacts at OAuth
Behind the curtain
my $request = Net::OAuth ->request("request token")->new( $c->_default_request_params, request_url => $c->config->{request_token_endpoint}, extra_params => { scope=> $c->config->{request_scope}, });
$request->sign($c->_get_key);
my $res = $ua->request(GET $request->to_url);
Behind the curtain
$request = Net::OAuth->request('user auth')->new( token => $response->token, callback => $c->uri_for('/callback'),);
return $c->res->redirect($request->to_url($c->config->{user_auth_endpoint}));
Example: GMail contacts at OAuth
Behind the curtain
my $response = Net::OAuth->response('user auth') ->from_hash($c->req->params);
my $request = Net::OAuth->request("access token") ->new( $c->_default_request_params,request_url => $c->config->{access_token_endpoint},token => $response->token,token_secret => '');
$request->sign($c->_get_key);
my $res = $ua->request(GET $request->to_url);
Behind the curtain
$response = Net::OAuth ->response('access token') ->from_post_body($res->content);
$c->session->{token}= $response->token;
# use the tokenmy $request = Net::OAuth->request("protected resource")->new($c->_default_request_params,request_url => $c->config->{contacts_feed_url},token => $c->session->{token},token_secret => '',);
# sign, lwpmy $res = $ua->request(GET($request->request_url, Authorization => $request->to_authorization_header));
Get the Source
★ Ported from the Original CGI::App example included in Net::OAuth
★ Get the source in Catalyst repo
★ http://dev.catalyst.perl.org/repos/Catalyst/trunk/examples/OAuthExample
Nordaaker
Questions?
Nordaaker
Thank [email protected]