Created
June 8, 2009 15:20
-
-
Save jshirley/125875 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package MyApp::Model::Facebook; | |
use warnings; | |
use strict; | |
use parent 'Catalyst::Model::Adaptor'; | |
__PACKAGE__->config( | |
class => 'WWW::Facebook::API', | |
args => { | |
app_path => 'canvas-app-path/', | |
secret => 'sekrit', | |
api_key => 'apikey', | |
desktop => 0 | |
}, | |
# This has to be publicly visible, as facebook talks to it as well | |
callback => 'http://yourapp.com/callback' | |
); | |
sub mangle_arguments { return %{$_[1]}; } | |
1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package Catalyst::Authentication::Credential::Facebook; | |
use warnings; | |
use strict; | |
use Carp; | |
use parent 'Class::Accessor::Fast'; | |
use Catalyst::Exception (); | |
__PACKAGE__->mk_accessors(qw/ _config model /); | |
=head1 NAME | |
Catalyst::Authentication::Credential::Facebook - Handle authentication via Facebook | |
=head1 VERSION | |
Version 0.01 | |
=cut | |
our $VERSION = '0.01'; | |
=head1 SYNOPSIS | |
This module handles the basic authentication and setup of the user object based | |
upon their Facebook account. In many ways it is similar to OpenID as far as | |
the request cycle, but since the Facebook platform is a platform, you actually | |
need a Facebook model. See L<Catalyst::Helper::Model::Facebook> for creating | |
a Facebook model. | |
In MyApp.pm: | |
use Catalyst qw/ | |
Authentication | |
Session | |
Session::Store::FastMmap | |
Session::State::Cookie | |
/; | |
MyApp->config( | |
'Plugin::Authentication' => { | |
default_realm => 'facebook', | |
realms => { | |
facebook => { | |
credential => { | |
class => 'Facebook', | |
config => { | |
model_class => 'Facebook' | |
} | |
}, | |
} | |
} | |
} | |
); | |
=head1 A Simple Facebook Model | |
L<Catalyst::Model::Adaptor> is very good at creating a conduit for the | |
L<WWW::Facebook::API> library. So, lets use that: | |
package MyApp::Model::Facebook; | |
use warnings; | |
use strict; | |
use parent 'Catalyst::Model::Adaptor'; | |
__PACKAGE__->config( | |
class => 'WWW::Facebook::API', | |
args => { | |
secret => 'somesecretherereallyitisasecretssshhhh', | |
api_key => 'iwonttellyoubutmaybeforsomecheeseiwill', | |
desktop => 0 | |
} | |
); | |
# This line is important: | |
sub mangle_arguments { return %{$_[1]}; } | |
1; | |
Now, with that model class and this authentication mechanism, you can | |
authenticate your users through Facebook: | |
sub facebook : Local { | |
my ( $self, $c ) = @_; | |
if ( $c->authenticate(undef, 'facebook') ) { | |
# Now, $c->user exists and $c->model('Facebook') is primed | |
$c->res->body('<h1>Success!</h1>'); | |
} | |
} | |
=head1 DESCRIPTION | |
Please read the following sections. It will help with understanding how to use | |
this credential. Facebook operates different than most other credentials since | |
it requires a previously configured application, and Facebook takes over the | |
entire process so things can go wrong and it's difficult to debug. | |
=head1 Interacting with Facebook | |
The authenticate method in the Facebook credential works slightly different than | |
in other credentials. Since there are, by definition, no fields you can pass | |
to the C<authenticate> method because Facebook handles everything in terms of | |
collecting the identifying information. | |
This Facebook credential has two modes of operations. One is analogous to a | |
desktop application, where you simply use Facebook to authenticate the user and | |
control is returned back to Catalyst and you have a C<<$c->user>> object | |
populated after they are authorized. | |
The other is that you're writing a Facebook application (as in, one that lives | |
inside of facebook). | |
The two modes can exist simulataneously in a single Catalyst application, so | |
there is simply a flag you pass into C<authenticate> to instruct the Credential | |
on how to behave. | |
$c->authenticate({ return_to_catalyst => 1 }, 'facebook'); | |
Setting C<return_to_catalyst> to 1 will make your application act in a | |
stand-alone fashion (the desktop model), returning control back to Catalyst | |
after the authentication call succeeds. This mode REQUIRES A CACHE PLUGIN. | |
You must load L<Catalyst::Plugin::Cache> or it will fail. | |
You must also configure your Facebook application accordingly (inside of the | |
Facebook developers application at L<http://www.facebook.com/developers/apps.php>. | |
=head1 Configuring Facebook | |
Your Facebook application configuration is relatively simple. Edit your | |
Facebook Applications Settings and ensure you have the Canvas Page URL, and | |
select "Use FBML" or "Use iframe" based on how you want your markup to be. | |
The very important part is the Callback URL. This should be the full URL | |
to your Facebook authentication action. If you don't set this properly, | |
Facebook will not be able to return control to Catalyst and if you use the | |
C<return_to_catalyst> action things will fail. Miserably. | |
=cut | |
sub new : method { | |
my ( $class, $config, $c, $realm ) = @_; | |
my $self = { | |
_config => { %$config, %{$realm->{config}} } | |
}; | |
bless $self, $class; | |
$self->model( $self->_config->{model_class} || 'Facebook' ); | |
return $self; | |
} | |
=head2 authenticate | |
=cut | |
sub authenticate : method { | |
my ( $self, $c, $realm, $authinfo ) = @_; | |
my $client = $c->model( $self->model ); | |
croak "Can't find facebook model class: " . $self->model . " ($client)\n" | |
unless $client and $client->isa('WWW::Facebook::API'); | |
if ( my $fb_sig = $c->req->params->{fb_sig} ) { | |
my $ident = $client->canvas->get_user( $c->req ); | |
my $fb_params = $client->canvas->get_fb_params( $c->req ); | |
if ( $fb_params->{session_key} ) { | |
$client->session_key( $fb_params->{session_key} ); | |
} | |
unless ( $ident ) { | |
if ( "$fb_params->{added}" eq "0" and $fb_params->{api_key} ) { | |
my $uri = $client->get_add_url( next => $c->req->uri ); | |
$c->log->debug("Failed getting identity from Facebook, redirecting to Approval page: $uri") if $c->debug; | |
if ( $fb_params->{in_canvas} ) { | |
$c->res->body(qq{<fb:redirect url="$uri"/>}); | |
$c->detach; | |
} else { | |
$c->res->redirect($uri); | |
$c->detach; | |
} | |
} | |
} | |
my $user = +{ uid => $ident, %$fb_params }; | |
my $user_obj = $realm->find_user( $user, $c ); | |
if ( ref $user_obj ) { | |
my $return = $self->_config->{return_label} || 'return_to_catalyst'; | |
my $token = $c->req->params->{auth_token}; | |
if ( $authinfo->{$return} ) { | |
$user_obj->in_canvas(0); # Force this to not be in canvas | |
$c->cache->set($token, $user_obj); | |
my $uri = $c->req->uri; | |
$c->res->body(qq{<fb:redirect url="$uri"/>}); | |
$c->detach; | |
} else { | |
return $user_obj; | |
} | |
} else { | |
$c->log->debug("Facebook identity failed to load with find_user; bad user_class? Try 'Null.'") if $c->debug; | |
return; | |
} | |
} | |
my $session; | |
if ( my $token = $c->req->params->{auth_token} ) { | |
$c->log->debug("Checking session from auth token: $token") if $c->debug; | |
$client->auth->get_session( $token ); | |
my $session_uid = $client->session_uid; | |
$c->log->debug("Got $session_uid, checking cache for token: " . $c->cache->get($token)); | |
if ( $session_uid and my $user_obj = $c->cache->get($token) ) { | |
$c->log->debug("Got token $token and user: $session_uid with cache: $user_obj"); | |
$c->log->debug($client->session_key); | |
if ( $user_obj->uid == $session_uid ) { | |
return $user_obj; | |
} | |
} | |
return; | |
} | |
unless ( $session ) { | |
my $uri = $client->get_add_url;#( next => $c->req->uri ); | |
$c->log->debug("Getting Facebook Auth URI: $uri") if $c->debug; | |
$c->res->redirect( $uri ); | |
$c->detach; | |
} | |
} | |
1; | |
=head1 AUTHOR | |
J. Shirley, C<< <cpan at coldhardcode.com> >> | |
=head1 SEE ALSO | |
L<Catalyst> - Catalyst MVC Framework | |
L<Catalyst::Plugin::Authentication> - The Authentication Plugin | |
=head1 BUGS | |
Please report any bugs or feature requests to C<bug-catalyst-authentication-credential-facebook at rt.cpan.org>, or through | |
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Catalyst-Authentication-Credential-Facebook>. I will be notified, and then you'll | |
automatically be notified of progress on your bug as I make changes. | |
=head1 SUPPORT | |
You can find documentation for this module with the perldoc command. | |
perldoc Catalyst::Authentication::Credential::Facebook | |
You can also look for information at: | |
=over 4 | |
=item * RT: CPAN's request tracker | |
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Catalyst-Authentication-Credential-Facebook> | |
=item * AnnoCPAN: Annotated CPAN documentation | |
L<http://annocpan.org/dist/Catalyst-Authentication-Credential-Facebook> | |
=item * CPAN Ratings | |
L<http://cpanratings.perl.org/d/Catalyst-Authentication-Credential-Facebook> | |
=item * Search CPAN | |
L<http://search.cpan.org/dist/Catalyst-Authentication-Credential-Facebook> | |
=back | |
=head1 ACKNOWLEDGEMENTS | |
Thanks to Jay Kuri for his hard work on L<Catalyst::Plugin::Authentication> | |
Thanks to the Catalyst Core Group for making Catalyst what it is today. | |
Thanks to the Catalyst Documentation Group for inspiring me to write and release | |
software. | |
=head1 COPYRIGHT & LICENSE | |
Copyright 2008 J. Shirley, all rights reserved. | |
This program is free software; you can redistribute it and/or modify it | |
under the same terms as Perl itself. | |
=cut | |
1; # End of Catalyst::Authentication::Credential::Facebook |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment