見出し画像

ちゃんとしたユーザ認証をLINEにゆだねる ~ IdP, passport-openidconnect ~

プログラム自学案内の 41 回目です。前回までの記事では、まやかしの認証のもとに認可の機能をつくりましたので、今回はちゃんとしたユーザ認証を導入します。前回までの記事はこちらからお読みください。


LINEによるユーザ認証

IdPって?

以前の記事で軽く触れた通り、認証処理というのは難しいものです。また、利用者にとっても、Webサイトごとにいちいちパスワードを考えるのは大変な面倒事です(パスワードの使い回しは危険ですしね)。

そこで、Webサービスでよく見るのが「Googleでサインイン」とか「GitHubでサインイン」なんてやつです。

Googleでサインイン (noteの場合)


Googleでアカウント作成(Render.comの場合)

ここで「○○でログイン」の○○に当てはまる、Googleや 𝕏 (twitter) などのことを、IdP (Identity provider) と言います。日本語に訳すと「身元同一性提供者」みたいな感じです。

このIdPの力を借りた、認証のやり方を紹介しようというわけです。そして、この記事では、IdPに Google や 𝕏 の代わりに LINEを使って、認証をしてみます。LINEを選んだ理由は、なんとなく日本で最もユーザー数が多そうだからです。

まずはLINEのチャネルを作成する

IdPを使うには、IdPに対して自アプリを登録する必要があります。LINEの場合はこの手続きを「チャネルの作成」と言うようです。

「LINE IdP」でググると、次のページがヒットします。

日本語ですので、一通り読んでみてください。そして、チャネルを作成するに記載されている内容にしたがって、「チャネル」を作ります。

passport-openidconnect Strategy を使う

チャネルを作成しましたら、いよいよ自分のアプリに認証の連携処理を作りこみます。

上述のページに記載されている、「ユーザーに認証と認可を要求する」以下の処理を行うわけですが、Passport.jsの部品を用いれば、あるていど、部品がうまいことやってくれます。Passport.jsでは、認証のためにIdPとやりとりをする部品を、 Strategy と言います。

Passport.jsのこちら( https://www.passportjs.org/packages/ )のページに、facebook, google, twitter など、さまざまなIdPに接続するためのStrategyがラインナップされています。検索窓に「line」とタイプすると、LINE用のStrategyもいくつか見つかるはずです。

しかし、この記事ではそれらの代わりに、 passport-openidconnect (https://www.passportjs.org/packages/passport-openidconnect/) というStrategyを使います。GitHubの★の数が多いのが、その理由です。LINEのサイトには OpenID Connect プロトコル をサポートしている、と書かれていますので、LINE専用のStrategyを使わなくても、これでできるはずなのです。

さっそくインストールします。

npm install passport-openidconnect

LINEからサインインするリンクを作る

ではまず画面からコーディングします。 LINEアカウントでサインインするためのリンクを追加します。

views/sign_in.html(抜粋)

<body>
    <h1> flatter-web サインイン </h1>
    <form action="sign-in/enter" method="post">
        なまえ:
        <input name="username" type="text" required autofocus>
        <br>
        パスワード(なにかしら入れてください):
        <input name="password" type="password">
        <br>
        <button type="submit">サインイン</button>
    </form>
    <!-- ここから追加 -->
    <hr>
    <a href="sign-in/line">LINEアカウントでサインイン</a>
    <!-- ここまで追加 -->

</body>
「LINEアカウントでサインイン」リンクを追加

こんな感じになりますね。

リンクじゃ味気ない、LINEだから緑のボタンにした方が良いんじゃないか、と思う方は、ぜひそのようなボタンにしてみてください。

LINE認証リンクとコールバックのルーティング

次に、ルーティングを二つ追加します。

controllers/router.js(抜粋)

// LINE認証のリンクのルーティングを追加
router.get('/sign-in/line', auth.sign_in_line())

// LINE認証のコールバックのルーティングを追加
router.get('/sign-in/line/cb', auth.sign_in_line_cb({
    successRedirect: "/", failureRedirect: "/sign-in"
}));

一つ目の「LINE認証のリンクのルーティング」は先ほど画面に作ったリンクのパスですが、二つ目の 「LINE認証のコールバックのルーティング」とは何でしょうか?

利用者として、IdPによる認証を行うときの経験を思いだしてみてください。「〇〇でログイン」ボタンを押すと、いったんブラウザの画面が〇〇(IdP)の画面に移り、認証が終わったらところで、元のアプリの画面に舞い戻りますよね。この、認証が終わったあと舞い戻ることになる、着地点となるパスがコールバックのルーティングなのです。

このパスは、LINEのチャネル管理画面からも、チャネルに設定する必要があります。こんな感じで設定します。

LINE認証から自アプリに舞い戻るときの着地点を設定

OpenID Connect による LINEとの連携

ここまで設定したら、いよいよpassport-openidconnectによるLINE認証との連携処理を書きます。コード例を示します。

auth/auth.js(抜粋)

const OpenIDConnectStrategy = require('passport-openidconnect');

const url = process.env.RENDER_EXTERNAL_URL || "http://localhost:3000";

const linestrategy = new OpenIDConnectStrategy({
    issuer: 'https://access.line.me',
    authorizationURL: 'https://access.line.me/oauth2/v2.1/authorize',
    tokenURL: 'https://api.line.me/oauth2/v2.1/token',
    userInfoURL: 'https://api.line.me/oauth2/v2.1/profile',
    clientID: process.env.LINE_CHANNEL_ID,
    clientSecret: process.env.LINE_CHANNEL_SECRET,
    callbackURL: `${url}/sign-in/line/cb`,
    scope: ["profile", "openid"],
},
    function verify(issuer, profile, cb) {
        return cb(null, profile.displayName);
    }
);

passport.use("line", linestrategy);

function sign_in_line() {
    return passport.authenticate("line");
}

function sign_in_line_cb({ successRedirect, failureRedirect }) {
    return passport.authenticate("line", {
        successRedirect: successRedirect,
        failureRedirect: failureRedirect,
    });
}

const session_authenticate = passport.authenticate("session");

module.exports = {
    sign_in: sign_in,
    sign_out: sign_out,
    sign_in_line: sign_in_line, //追加
    sign_in_line_cb: sign_in_line_cb, //追加
    session_authenticate: session_authenticate,
};

コードを読んでいただければわかるとおもいますが、このコードでは、二つつの環境変数 LINE_CHANNEL_ID、LINE_CHANNEL_SECRET を読んでいます。ですので、このアプリを動かすためには、この二つの環境変数を事前に設定する必要があります。なぜ、わざわざプログラムに直接書かずに、環境変数に設定しなければいけないかは、以前の記事で紹介していますので、気になる方は読んでみてください。

環境変数 LINE_CHANNEL_ID、LINE_CHANNEL_SECRET に設定すべき値は、LINEに作ったチャネルの基本設定画面から確認できます。

これで、LINEを使ったサインインができるようになったはずです。ぜひ、確かめてみてください。

以上、LINEを用いた「ちゃんとした」ユーザ認証を案内しました。

自前で認証する場合?

さて、上記紹介したLINEを使った認証もそこそこ難しかったと思います。その代わりに、IdPに頼らず自分で認証処理をすると、はたしてどれほど難易度が跳ね上がるのでしょうか?

そこで、この記事では参考までに、自分で認証機能を作る場合、考えなければいけない内容を紹介します。

そもそも認証機能として、どんな機能を作らなければいけないか

だいたいこれくらいの機能が必要になります(IdPはこれらのことを全部肩代わりしてくれています)。相当タイヘンなのが分かると思います。

  • ユーザIDとメールアドレスの登録機能

  • メールアドレスの検証機能

    • 仮登録されたメールアドレスにメールを送信し、メールをクリックしてもらい本登録とする

  • パスワードを ハッシュ化(hashing) して保管・照合する機能

  • パスワードを何回か間違えたとき、アカウントをロックする機能

  • パスワードを忘れた人のために、パスワードをリセットする機能

    • メールアドレスにリセット用のリンクを記したメールを送信

さて、多くのWebサービスは、IdPを使わずにもユーザ登録できますが、みないちいち、上述の機能を作りこんでいるのでしょうか?

全部やってくれるソフトもある(Keycloak)

ユーザ認証に必要な機能一式がそろった、無料のソフトウェアがあります。有名なものが Keycloak(https://www.keycloak.org/) です。

ただし残念ながらこれは、Node.jsライブラリではありませんので、使うにはNode.jsサーバーとは別にサーバーを構築しなければなりません。結構面倒で、それに多くの前提知識が必要になるため、この記事では使い方の案内を割愛させていただきます。

ハッシュ化って?

ところで、上に出てきた言葉、ハッシュ化 とはどういう意味でしょうか。調べていただければそれなりの答えは分かると思いますが、これを認証との関連で理解していただくために、さいごに、うんちくをひと語りしたいと思います。

うんちく:印章と印影

東洋では、古来から20世紀まで何千年ものあいだ、文書が本人によって書かれたことを証明する役割を、ハンコ が担ってきました。では、ハンコの一体どこに、本人が書いたことを証明する機能が備わっているのでしょうか。

彫られたハンコを 印章 、紙に押されたハンコを 印影 と言うそうですが、ハンコが持つ機能は、この印章と印影の両者の関係がかかわっています。ポイントは、ハンコの押印に 一方向性関数 のような性質が備わっている点にあります。

ハンコの押印行為の一方向性
  • 印章から印影はすぐに、いくつでも作れる。(押印)

  • 逆に印影から印章を作るのは、極めてむずかしい。

  • 印章は1つしかなく、複製できない。

  • 印章がないと印影は作れない。

このおかげで、印影を作ったのが、たった1つしかない印章を保管する本人である、ということが分かるだけでなく、本人以外の人々は印影同士を比較することで、本物の印章の持ち主によりハンコが押されたことを、本人や印章がなくても、確かめることができます。

たとえば銀行は預金者の届出印の印影を保管しますが、印章は預金者本人しか、持っていません。したがって、銀行員は口座を引き出そうとするのが顧客本人かどうかを確認できる一方、銀行員自身が顧客になりすまし、届出印を押印して勝手に顧客口座からお金を引き出すことはできないわけです。なかなかうまい仕組みだとおもいませんか?

ただし、最近は技術の進化により、「印影から印章を作る」ことも可能になり、ハンコによる認証の安全性が危うくなっているようです。

なぜ平文パスワードの保存やログ出力は悪なのか

一方、パスワードを用いた認証では、印章のかわりに平文のパスワードを、印影のかわりに ハッシュ化 されたパスワード(ハッシュ値 ともいいます)を用いるのが定石です。ここでのハッシュ化とは、平文のパスワードから、元のパスワードに復元しにくいような別の値を作りだすことを言います。

Webサービスなどのパスワード認証では、(銀行が銀行印の印影のみを保管・照合するように)ハッシュ値のみを保管・照合することによって、入力された平文パスワードの正しさを検証します。

パスワード認証

一方、平文のパスワードを保管して認証を行うシステムも残念ながらあるようです。これは危険であり悪であるとされています。なぜならこれは、銀行が 印章そのものを複製して保管するようなものだからです。全く不要であるどころか、極めて危うい行為ですよね。

なぜこんな危険行為がまかり通ってしまうかというと、平文のパスワードは、印章と違い、極めて容易に複製可能だからなのですね。

いまのところ、パスワード認証に用いられるハッシュ化関数の一方向性に限って言えば、ハンコよりも秀でていると思われているようです。しかし、平文のパスワードが極めて簡単に複製できるという点で、そもそもパスワード認証はハンコと比べて深刻な弱点を抱えていると私は思います。

おそらく、今現在広く使われているパスワードによる認証方式は過渡的なものであって、近い将来、別の方式にとって代わられるべきなのではないか、と私は予感しています。もしかしたら、今デジタル化の名のもとに滅ぼされようとしているハンコよりも先に、パスワード認証方式が滅ぶ可能性だってないとは言えないと思うのです。

以上うんちくでした。

まとめと次回予告

今回の記事では、LINEを用いたサインインの仕組みを作りました。これで、Webサービスに必要な技術を、大筋、案内し終えたのではないかと思います! 相当な超特急だったので、抜けも幾つかはあるとは思いますが。

次回は連載の最終回です。これまでの連載で紹介した内容を簡単に振り返ったうえで、ここまでの内容を学んだ「非ITエンジニア」の読者の皆さんに「ITで飯を食う」ことをおススメできるかどうかについて、あらためて検討してみたいと思います。

#コラム #プログラミング #認証 #LINE #passport .js

この記事が気に入ったらサポートをしてみませんか?