Node.js（Express）とRenderでLINEログイン機能を実装する

## はじめに
[前回](https://qiita.com/course_k/items/4c7faedf11723af9bb6e)はLINEが用意してくれているデモアプリをつかって、LINEログイン機能を試しました。
今回は、Node.jでの実装を通して、内部でどのような処理をしているのかを確かめます。

### 前提
本記事は以下のことは完了している想定で進めます。
* Node.jsをインストール済であること
* GitHubアカウントをもっていること
* Renderアカウントをもっていること
* LINE関連の設定が終わっていること（設定詳細は[こちら](https://qiita.com/course_k/items/85bf9cfab971cb5b97ee#line%E9%96%A2%E9%80%A3%E3%81%AE%E8%A8%AD%E5%AE%9A)）
* LINEログインチャネルの作成方法がわかっていること（作成方法は[こちら](https://qiita.com/course_k/items/4c7faedf11723af9bb6e#line%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E7%94%A8%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB%E4%BD%9C%E6%88%90)）

## LINEログインの仕組み
LINEログインは以下のフローに沿って実施されます。
（出典：[ログインのフロー](https://developers.line.biz/ja/docs/line-login/integrate-line-login/#login-flow)）
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/e2c8932e-c7a3-1f69-feca-6b5d2c01f2fa.png'>

英語で書いてありますが、大きく分けて3つの処理があります。
### 1. ログインの要求
ユーザーがWebアプリ上でLINEログインをしようとすると、WebアプリはLINEのプラットフォームのログイン認証用のURLへリダイレクトします。
ここは開発者の実装が必要な部分です。

### 2. ログイン認証、認可
LINEプラットフォームは、ユーザーに対してログイン認証画面を表示させ、ユーザーはLINEの情報を入力して認証を行います。
ユーザーがアプリへのアクセスを許可すると、認可が完了します。
ここは開発者の実装が不要です。

認証、認可の処理はLINE側で実行してくれます。

### 3. ログイン情報の検証、情報の取得
認証、認可が完了すると、LINEプラットフォームはWebアプリへ再度リダイレクトします。
Webアプリ側ではリクエストの正当性の検証や、アクセストークン、プロファイル情報の取得などを行い、問題なければユーザーにログイン後の結果を表示させます。
ここは開発者の実装が必要です。

以上のように、ログイン認証についてはLINE側で実施してくれるため、開発者としては
* ログイン要求の処理
* 認証完了後の処理

の2つを実装すればよいことになります。

## Node.jsでログイン処理を実装する
なにもないところから処理を作っていきます。
なお、本処理ではOSにはMac、開発ツールとしてはVS Codeを使っています。
### 初期設定
#### package.jsonの生成
適当なフォルダを作成します。（ここでは「login-demo」）
ターミナルでフォルダに移動して
```bash
npm init
```
を実行します。

以下のように`package name`や`versison`などを聞かれます。
すべてデフォルトでよいのでenterキーを押して次々進みます。
最終的に問題ないか聞かれるので、「yes」を入力して処理を完了させます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/02df47ee-10e0-00ad-bc2d-3678888e6465.png)

完了すると、`package.json`ファイルが生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/c198f05e-7b1e-a05f-ba8a-e0416e0c1f8a.png)

#### package.jsonの編集
生成した`package.json`に以下の2項目を追加します。
```diff_json:package.json
{
  "name": "login-demo",
  "version": "1.0.0",
  "description": "",
+  "type": "module",
  "main": "index.js",
  "scripts": {
+    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.19.2"
  }
}
```

#### expressのインストール
expressというサーバーサイドの処理を書くのに便利なNode.jsのフレームワークをインストールします。
```bash
npm install express
```

実行が完了すると、以下のようなフォルダとファイルが生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/38f8e629-ba02-6df0-1c43-deb8897dae67.png)

以上で初期設定は完了です。

### ログイン処理の実装
`index.js`というファイルを作成します。
ここに具体的な処理を追加していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/18764eb8-f3fd-2566-9165-01680f2e033e.png)

#### リクエストを受け取る
ログインフローで確認したログイン要求と認証完了後の処理の他、アプリのルートパスへのリクエストを受け取る処理を書いていきます。

```javascript:index.js
import express from "express";
import path from "path";
import { fileURLToPath } from "url";

const app = express();
const __dirname = path.dirname(fileURLToPath(import.meta.url));

// ルートパスへのリクエスト
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "login.html"));
});

// ログイン要求のリクエスト
app.get("/login", (req, res) => {

});

// 認証完了後のリクエスト
app.get("/success", (req, res) => {
  res.status(200).send("success");
});

```
##### ルートパスへのリクエスト
アプリにアクセスされたらhtmlファイルを表示させます。
本来フロント側は別でサーバーを立ててやりますが、今回は簡易化のためにサーバー側で直接htmlを返します。
このファイルは後ほど作成します。

##### ログイン要求へのリクエスト
Webアプリでログインボタンを押したときに、LINEプラットフォームのログイン認証URLにリダイレクトさせる処理です。こちらは後ほど処理を作成するので一旦処理はなしにしています。

##### 認証完了後のリクエスト
本来はLINEプラットフォームからのリクエストを検証して、情報を取得する処理をしてから、ログイン成功画面を表示させますが、今回は単純にサーバーから「success」を返すだけにします。

#### LINEプラットフォームへのリダイレクト
ログインを要求されたときのリダイレクト処理を記載します。

```javascript:index.js(一部抜粋)
import crypto from "crypto";

/* 一部処理の記載を省略 */

// リダイレクトURL生成処理
const createRedirectUrl = () => {
  const bytes = crypto.randomBytes(32);
  const state = bytes.toString("base64url");

  const redirectUrl =
    "https://access.line.me/oauth2/v2.1/authorize?" +
    "response_type=code" +
    `&client_id=${process.env.CHANNEL_ID}` +
    `&redirect_uri=${process.env.CALLBACK_URL}` +
    `&state=${state}` +
    "&scope=profile%20openid";
  return redirectUrl;
};

/* 一部処理の記載を省略 */

// ログイン要求のリクエスト
app.get("/login", (req, res) => {
  const redirect = createRedirectUrl();
  res.redirect(redirect);
});

```
リダイレクトURL生成処理を作成し、それをログイン要求時の処理として追加しています。
リダイレクトのパラメータについて1つずつ確認します。

##### response_type
固定で`code`となります。OAuth2.0認証で使われるパラメータのようです。

##### client_id
LINEログインチャネルのチャネルIDです。
後ほど設定する環境変数から取得するようにしています。

##### redirect_uri
ログインフローで確認した、ログイン認証成功後にリダイレクトされるURLです。
これも環境変数から取得するようにします。

##### state
CSRF（クロスサイトリクエストフォージェリ）を防止するために使用されるパラメータです。
ログイン認証が成功すると、リクエストに同じstateが設定されて返却されます。
生成したstateと返却されたstateが一致するかを確かめることで、正当なリクエストかどうかを確認します。

ランダムに生成する必要があるので、今回はNode.jsの`crypto`モジュールを使用しています。
Node.jsに含まれているので`npm install`は不要です。

##### scope
Webアプリ側で使用したいユーザープロファイルの情報です。
ここに含めたパラメータについて、許可してもいいかユーザーに確認します。
5通りの指定方法がありますが、詳細は以下から確認してください。
（[ウェブアプリにLINEログインを組み込む - スコープ](https://developers.line.biz/ja/docs/line-login/integrate-line-login/#scopes)）

#### リッスンするポートの設定
```javascript:index.js
/* 一部処理の記載を省略 */

const PORT = 3000;

/* 一部処理の記載を省略 */

// リッスンするポートの設定
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
```
最後にサーバーがリッスンするポート番号を指定します。

サーバーの処理はこれで完了です。
<details><summary>全体のソースを表示</summary>

```javascript:index.js
import express from "express";
import path from "path";
import { fileURLToPath } from "url";

import crypto from "crypto";

const PORT = 3000;
const app = express();
const __dirname = path.dirname(fileURLToPath(import.meta.url));

// リダイレクトURL生成処理
const createRedirectUrl = () => {
  const bytes = crypto.randomBytes(32);
  const state = bytes.toString("base64url");

  const redirectUrl =
    "https://access.line.me/oauth2/v2.1/authorize?" +
    "response_type=code" +
    `&client_id=${process.env.CHANNEL_ID}` +
    `&redirect_uri=${process.env.CALLBACK_URL}` +
    `&state=${state}` +
    "&scope=profile%20openid";
  return redirectUrl;
};

// ルートパスへのリクエスト
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "login.html"));
});

// ログイン要求のリクエスト
app.get("/login", (req, res) => {
  const redirect = createRedirectUrl();
  res.redirect(redirect);
});

// 認証完了後のリクエスト
app.get("/success", (req, res) => {
  res.status(200).send("success");
});

// リッスンするポートの設定
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

```
</details>

### 画面表示用htmlの作成
ルートパスへアクセスされた際に表示させる「login.html」を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/3081c461-1c0e-b390-1235-4a350b0df440.png)

```html:login.html
<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Line Login Demo</title>
</head>
<body>
    <h3>LINE Login</h3>
    <a href="/login" id="login">Login</a>
</body>
</html>
```

複雑な見た目にする必要はないので、リンク押下でログイン要求の処理へ飛ばすようにしています。

以上でログイン処理の実装は完了です。

## Webアプリの公開
アプリができたので公開していきます。
大まかな流れを紹介します。

### Githubリポジトリに配置する
Githubに任意のリモートリポジトリを作成し、そこに作成したリソースを配置します。
これをベースとしてアプリをデプロイします。

### RenderでWebアプリを作る
#### Webアプリのデプロイ
Renderは無料でWebアプリなどを手軽に作れるサービスです。
Githubに公開しているリポジトリを元にWebアプリを作ることができます。

RenderのサイトからWeb Serviceの新規作成を行います。
作成元に先ほどのリポジトリを選択し、以下のように設定を行います。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/fbb151f4-c3f0-3ace-9c8f-9b7ba5add071.png'>
「Instance Type」は「Free」にしておきます。

「Environment Variables」には、リダイレクト生成処理の際に指定した`CHANNEL_ID`と`CALLBACK_URL`を設定します。
`CHANNEL_ID`はLINE DevelopersのLINEログインチャネル-チャネル基本設定から確認できます。
`CALLBACK_URL`は現時点ではわからない（WebアプリのURLはこれから発行される）ので、適当な値を入れておきます。後ほど修正します。

設定し終えたらWeb Serviceを作成します。

デプロイが完了するとURLが発行されます。
そのURLにアクセスすると、以下のような画面が表示されます。
<img width='40%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/6c83fb64-02c5-bac9-8f96-3a3f6d7d7e15.png'>

これでWebアプリの作成は一旦成功です。

#### 設定変更
リダイレクトURLを設定するため、先ほどの環境変数`CALLBACK_URL`を修正しましょう。
実装部分で作成した、ログイン成功後の処理にリダイレクトしたいので、生成されたWebアプリのURLの末尾に`/success`を追加したものを、`CALLBACK_URL`に設定してください。

<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/e35d2286-dead-2849-9603-bbbdeafa7893.png'>

設定を保存すると、再度デプロイが走ります。

### LINE ログインチャネルの設定
リダイレクト先のURLはLINE側にも設定する必要があります。
以下のようにコールバックURLに、環境変数に設定したものと同じURLを設定します。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/aa5cb3ab-7d4d-e747-92f8-7c1cc750ce31.png'>


これでアプリの公開設定は完了です。

## ログイン処理を試す
早速作成したログイン処理を試してみましょう

WebアプリのURLにアクセスします。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/3905852d-131a-3eba-ef49-8791249f8738.png'>

質素なリンクを押下すると、LINEの認証画面に移ります。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/b0e14f6c-1356-d236-c89c-5a11628a6301.png'>
QRコードでのログインもできるので、お好きな方でログインしてみてください。

ログインが成功すると、以下の画面が表示されます。
<img width='70%' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/251137/73f07a0b-60ff-dc7b-21a4-94a5a18bf663.png'>

全く何の意味もないログイン認証ですが、これで自作のアプリを通してログイン機能を試すことができました。

## まとめ
自分で一からログイン機能を作成することに挑戦してみました。
ログイン要求を受け取る処理、認証完了後の処理さえ書いてしまえば、簡単にログイン機能を実装できることがわかりました。
実際にはもっとたくさんの処理やチェックを盛り込む必要があるかもしれませんが、基本的にはあまり難しくなくログイン機能を実装できるので、とても便利ですね。

また、何度かRenderを使ってアプリのデプロイをしてきましたが、こちらも無料とは考えられないくらいの手軽さ、便利さです。

これからもいろいろな機能を試すのに使っていきたいと思いました。