1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
package WebApp;
# Simple web app example using Plack. To run it:
# plackup examples/oauth_web.psgi
use 5.14.1;
use Moo;
use Encode qw/encode_utf8/;
use HTML::Escape qw/escape_html/;
use Plack::Request;
use Plack::Response;
use Twitter::API;
has twitter_client => (
is => 'ro',
default => sub {
Twitter::API->new_with_traits(
traits => 'Enchilada',
# Net::Twitter example app credentials:
map(tr/A-Za-z/N-ZA-Mn-za-m/r, qw/
pbafhzre_xrl i8g3WVYxFglyotakTYBD
pbafhzre_frperg 5e31eFZp0ACgOcUpX8ZiaPYt2bNlSYk5rTBZxKZ
/),
# To use your own app credentials:
# consumer_key => 'your-app-key',
# consumer_secret => 'your-app-secret',
);
},
);
# In a production application, use something like Redis to store request token
# secrets. Twitter expires request tokens after 15 minutes. Your app should
# keep them just a bit longer to ensure you don't discard them before Twitter
# does. Maybe 20 minutes. In this simple demo, we won't worry about expiration.
has secret_cache => (
is => 'ro',
default => sub { {} },
);
# We only need it once, so remove it from the cache
sub get_secret { delete $_[0]->secret_cache->{$_[1]} }
sub set_secret { $_[0]->secret_cache->{$_[1]} = $_[2] }
sub uri_for {
my ( $self, $req, $path ) = @_;
my $uri = $req->base;
$uri->path($uri->path . $path);
return $uri;
}
my %route = (
'/' => 'home_page',
'/oauth_callback' => 'oauth_callback',
);
sub dispatch {
my ( $self, $req, $res ) = @_;
my $method = $route{$req->path} // 'not_found';
$self->$method($req, $res);
}
sub handle_request {
my ( $self, $env ) = @_;
my $req = Plack::Request->new($env);
my $res = Plack::Response->new(200);
$res->content_type('text/html; charset=utf-8');
$self->dispatch($req, $res);
$res->finalize;
}
sub to_app {
my $self = shift->new;
sub { $self->handle_request(@_) };
};
sub home_page {
my ( $self, $req, $res ) = @_;
my $client = $self->twitter_client;
# If we have access token/secret, display verify_credentials response
if ( my $credentials = $req->cookies->{'dont-do-this-at-home'} ) {
my ( $token, $secret ) = split /\s+/, $credentials;
my $r = $client->verify_credentials({
-token => $token,
-token_secret => $secret,
});
my $body = escape_html(
$client->json_parser->pretty(1)->encode($r)
);
$res->body("<pre>$body</pre>");
}
else {
# Otherwise, prompt the user to authenticate
my $r = $client->oauth_request_token({
callback => $self->uri_for($req, 'oauth_callback'),
});
my ( $token, $secret ) = @{$r}{qw/oauth_token oauth_token_secret/};
# Save the request token and secret; we'll need them later.
$self->set_secret($token, $secret);
my $url = $client->oauth_authentication_url({
oauth_token => $token,
});
$res->body(qq{<a href="$url">Authenticate with Twitter</a>});
}
}
sub oauth_callback {
my ( $self, $req, $res ) = @_;
my $token = $req->param('oauth_token');
my $verifier = $req->param('oauth_verifier');
if ( $token && $verifier ) {
# The user authenticated!
# Get our cached request token secret
my $secret = $self->get_secret($token) // die 'missing secret';
my $r = $self->twitter_client->oauth_access_token({
token => $token,
token_secret => $secret,
verifier => $verifier,
});
# DON'T DO THIS AT HOME!
#
# In a production app, you will store the access_token and
# access_token_secret in a database. They can be used to make Twitter
# API calls on behalf of the authenticated user. Ideally, you should
# treat them like you would user names and passwords. Encrypt them.
#
# For our simple demo, since we don't have a permanent data store,
# we'll store them in a session cookie.
$res->cookies->{'dont-do-this-at-home'} = join ' ',
$$r{oauth_token}, $$r{oauth_token_secret};
$res->redirect($self->uri_for($req, ''));
return;
}
my $home = $self->uri_for($req, '');
if ( $token = $req->param('denied') ) {
# The user canceled the authentication request and select "return to
# the application".
$self->get_secret($token); # discard; no longer valid or useful
$res->body(qq{You denied us access. <a href="$home">Go home</a>});
return;
}
# /oauth_callback was requested without the expected parameters; let's just
# redirect to the root page
$res->redirect($home);
}
sub not_found {
my ( $self, $req, $res ) = @_;
my $home = $self->uri_for($req, '');
$res->status(404);
$res->body($req->path_info
. qq{ does not live here, try <a href="$home">the main page</a>});
}
my $app = __PACKAGE__->to_app;
|