Skip to content

Instantly share code, notes, and snippets.

@jshirley
Created June 8, 2009 15:20
Show Gist options
  • Save jshirley/125875 to your computer and use it in GitHub Desktop.
Save jshirley/125875 to your computer and use it in GitHub Desktop.
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;
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