つよく、やさしく、泥臭く生きていくブログ

日常とポエムと、ときどき技術

Mojolicious::LiteでLINEログイン

LINEログインしてプロフィール取るところまでできたので忘れないように書いておく。

LINEログインをして、ユーザのuserid、表示名、プロフィール画像を取得して、表示名と画像を表示するところまで。

Perl で Mojolicious::lite でheroku にデプロイしている。
もろもろチェックやなんやらは省略している。

  • 実質のファイルは

    • myapp.pl
    • templates/index.html.ep
    • templates/layouts/default.html.ep
    • cpanfile
      requires 'Mojolicious'; requires 'IO::Socket::SSL', '>=2.009';
    • app.psgi (空っぽ)
    • Procfile
      web: starman --preload-app --port $PORT myapp.pl psgi
  • プロフィールをとるまでは、こんな感じ。

    • ログイン認証させて認可コードを取得する
    • 認可コードを使ってアクセストークンを取得する
    • アクセストークンを使ってプロフィールを取得する
  • ログイン認証させるURLにはいくつかパラメータの指定が必要。 そのうち state は正しいアクセスかどうかを確認するのに使う。 ログイン認証するURLに含めたstateの値と、ログイン後に返ってくるstateの値が一致することを、こっちのアプリ側で検証してやらないといけない。もちろんアクセスごとにランダムな値を設定しないといけない。
    これって要するにMojoliciousのcsrf_tokenを使えばいいんじゃねということで使った。
    ログインボタンのURLに含めるstate=にはcsrf_tokenを入れる。
    csrf_tokenの検証は、普通なら元からあるメソッドでできるが、今回は直に、返ってきたstateの値とこっちのcsrf_tokenの値を比較した。

  • もろもろの値は環境変数に入れておく。heroku側で環境変数を設定する。

#!/usr/bin/env perl
use lib './lib';
use lib './local/lib/perl5';
use Mojolicious::Lite;
use Mojo::UserAgent;

helper is_ok_csrf_token => sub {
    my $self = shift;
    my $csrf_token = $self->session->{csrf_token};
    my $state = $self->param('state'); # csrf_token
    return 1 if $csrf_token eq $state;
    return;
};

helper get => sub {
    my ($self, $arg) = @_;
    my $url    = $arg->{'url'};
    my $header = $arg->{'header'};

    my $ua = Mojo::UserAgent->new();
    my $tx = $ua->get($url => $header);

    if ( my $res = $tx->success ) {
        return $res->json;
    }
};

helper post => sub {
    my ($self, $arg) = @_;
    my $url   = $arg->{'url'};
    my $param = $arg->{'param'};

    my $ua = Mojo::UserAgent->new();
    my $tx = $ua->post($url => form => $param);

    if ( my $res = $tx->success ) {
        return = $res->json;
    }
};

helper get_profile => sub {
    my $self = shift;

    my $access_token = $self->get_access_token;
    my $param = {Authorization => "Bearer $access_token"};
    my $url = 'https://api.line.me/v2/profile';

    my $result_hash = $self->get({
        url    => $url,
        header => $param,
    }) or return;

    return $result_hash;
};

helper get_profile => sub {
    my $self = shift;

    my $access_token = $self->get_access_token;
    my $param = {Authorization => "Bearer $access_token"};
    my $url = 'https://api.line.me/v2/profile';

    my $result_hash = $self->get({
        url    => $url,
        header => $param,
    }) or return;

    return $result_hash;
};

helper get_access_token => sub {
    my $self = shift;
    my $code = $self->param('code');

    my $token_url = 'https://api.line.me/oauth2/v2.1/token';
    my $post_param = {
        grant_type    => 'authorization_code',
        code          => $code,
        redirect_uri  => $ENV{CALL_BACK_URL},
        client_id     => $ENV{CHANNEL_ID},
        client_secret => $ENV{CHANNEL_SECRET},
    };

    my $result_hash = $self->post({
        url   => $token_url,
        param => $post_param,
    }) or return;

    my $access_token = $result_hash->{'access_token'};
    return $access_token if $access_token;
    return;
};

get '/' => sub {
    my $c = shift;

    if ( $c->param('code') && $c->is_ok_csrf_token ) {
        my $profile_hash = $c->get_profile;
        $c->stash('profile_hash' => $profile_hash);
    }

    $c->render(template => 'index');
};

app->secrets([$ENV{MOJO_SECRETS}]) if defined $ENV{MOJO_SECRETS};
app->start;
% layout 'default';
% title 'Welcome';
% my $profile_hash = stash('profile_hash');
<h1>LINE ログインテスト</h1>

<div>
  <a href="https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=<%= $ENV{CHANNEL_ID} %>&redirect_uri=<%= $ENV{CALL_BACK_URL} %>&state=<%= csrf_token %>&scope=profile">
LINEでログインする
  </a>

</div>

<div>
    printデバッグ用<%= stash('message') %>
</div>
<% if ($profile_hash) { %>
<div>
  <p><%= $profile_hash->{'displayName'} %></p>
  <img src="<%= $profile_hash->{'pictureUrl'} %>">
</div>
<% } %>