見出し画像

素人が2020年までの1ヶ月でLINE BOTに挑戦する毎日note. 【Day 27:開発3日目_LINEログインの実装】

こんにちは。くろです。

12月1日から2019年残り1ヶ月でスケジュール調整BOTの開発に挑戦しています。今日は27日目です。

今日の記事は結構長くなったのですが、lineloginの導入を本当に素人からできるように記載しているので、ぜひご覧ください。

このlineloginが結構曲者で素人には本当にチンプンカンプンだったので、結構時間を取られてしまいました。。。

概要

開発アプリ:Googleカレンダーと連動してスケジュールを調整してくれるLINEBOT

BOTの概要:Day 2の記事に書いています。

BOTの仕様:機能一覧やフローチャート Day 10の記事に書いています。

BOTの開発環境:Day25の記事でアプリケーションの骨格を作りました。

昨日は日程調整依頼の「1,BOTをグループに招待した時に日程調整マイページへのリンクと共に挨拶メッセージを送る所を作ります。」をやりました。

今日は日程調整依頼の続きです。

「2,LINEログインしてマイページを表示する」のうちLINEログインの実装までになります。

2-0,マイページをちょっとだけ改変

header.ejsとnav.ejs、さらにmain.cssをちょっとだけいじって見栄えをちょっとだけ良くしておきました。

header.ejs

<title>幹事くん</title>

<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<link rel="stylesheet" type="text/css" href="/stylesheets/main.css" />


nav.ejs

<header>
   <div id="nav-drawer">
       <input id="nav-input" type="checkbox" class="nav-unshown">
       <label id="nav-open" for="nav-input"><span></span></label>
       <div class="page_title">幹事くん - マイページ</div>  
       <label class="nav-unshown" id="nav-close" for="nav-input"></label>
       <div id="nav-content">ここに中身を入れる</div>
   </div>
 </header>

main.css

@charset "UTF-8";

body {
 margin: 0px auto;
 padding: 0px;
 /* font-family: 'メイリオ', Meiryo,'ヒラギノ角ゴシック','Hiragino Sans','Hiragino Kaku Gothic ProN','ヒラギノ角ゴ ProN W3',sans-serif; */
 /* background: #303030; */
}

.center{
 text-align: center;
}

.button_red_big {
 width: 85%;
 display: inline-block;
 padding: 10px 20px;
 border-radius: 25px;
 text-decoration: none;
 color: #FFF;
 background-image: linear-gradient(45deg, #f76a35 0%, #e1568e 100%);
 transition: .4s;
 text-align: center;
 font-weight: bold;
 font-size: 40px;
 margin-top: 2%;
 margin-bottom: 2%;
}

.button_red_big:hover {
 background-image: linear-gradient(45deg, #FFC107 0%, #ff8b5f 100%);
}

.app_bar_bg {
 position: absolute;
 width: 100%;
 height: 100px;
 left: 0px;
 top: 0px;

 background: #212121;
 box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.24), 0px 0px 4px rgba(0, 0, 0, 0.12);
}

.page_title {
 position: relative;
 display: inline-block;
 left: 10%;
 vertical-align: center;  
 font-size: 40px;
 color: #FFFFFF;
 top: 5px;
}

/*ヘッダーまわりはサイトに合わせて調整してください*/
header {
 padding:30px;
 background: #212121;
 box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.24), 0px 0px 4px rgba(0, 0, 0, 0.12);

}

#nav-drawer {
 position: relative;
}

/*チェックボックス等は非表示に*/
.nav-unshown {
 display:none;
}

/*アイコンのスペース*/
#nav-open {
 display: inline-block;
 width: 50px;
 height: 50px;
 vertical-align: middle;
}

/*ハンバーガーアイコンをCSSだけで表現*/
#nav-open span, #nav-open span:before, #nav-open span:after {
 position: absolute;
 height: 8px;/*線の太さ*/
 width: 50px;/*長さ*/
 border-radius: 3px;
 background: #FFFFFF;
 display: block;
 content: '';
 cursor: pointer;
}
#nav-open span:before {
 bottom: -18px;
}
#nav-open span:after {
 bottom: -36px;
}

/*閉じる用の薄黒カバー*/
#nav-close {
 display: none;/*はじめは隠しておく*/
 position: fixed;
 z-index: 99;
 top: 0;/*全体に広がるように*/
 left: 0;
 width: 100%;
 height: 100%;
 background: black;
 opacity: 0;
 transition: .3s ease-in-out;
}

/*中身*/
#nav-content {
 overflow: auto;
 position: fixed;
 top: 0;
 left: 0;
 z-index: 9999;/*最前面に*/
 width: 90%;/*右側に隙間を作る(閉じるカバーを表示)*/
 max-width: 330px;/*最大幅(調整してください)*/
 height: 100%;
 background: #fff;/*背景色*/
 transition: .3s ease-in-out;/*滑らかに表示*/
 -webkit-transform: translateX(-105%);
 transform: translateX(-105%);/*左に隠しておく*/
}

/*チェックが入ったらもろもろ表示*/
#nav-input:checked ~ #nav-close {
 display: block;/*カバーを表示*/
 opacity: .5;
}

#nav-input:checked ~ #nav-content {
 -webkit-transform: translateX(0%);
 transform: translateX(0%);/*中身を表示(右へスライド)*/
 box-shadow: 6px 0 25px rgba(0,0,0,.15);
}


2-1,lineloginの実装

こちらのページを読みながら進めていきます。

https://developers.line.biz/ja/docs/line-login/overview/

・まずはチャネルを作成します。

任意のプロバイダーで作成しましょう。

https://developers.line.biz/console/channel/new?type=line-login

画像1

・リダイレクト先を設定する

lineloginの管理画面で設定できます。

heroku上の本アプリのtopページにしておきました。

・メールアドレス取得権限申請は飛ばしました。

・developersの内容が結構開発者向けな感じでなんとなくしかわからなかったのと、

・WEBアプリにLINEログインを組み込む。

ウェブログインの処理に伴うステップは、以下のとおりです。

1,必要なクエリパラメータを使用して、アプリからLINEログインの認可URLにユーザーを誘導します。
2,LINEのログインダイアログがブラウザで開き、ユーザーはログインして認証を受けます。LINEプラットフォームでユーザーの認証情報が検証された後、ユーザーはアプリから要求される権限を付与することに同意する必要もあります。
3,LINEプラットフォームからアプリに、redirect_uriを介してユーザーをリダイレクトします。このとき、認可コードとstateを含むクエリ文字列もアプリに送信されます。
4,アプリは、認可コードを使ってエンドポイントURL https://api.line.me/oauth2/v2.1/tokenにアクセストークンを要求します。
5,LINEプラットフォームがアプリからのリクエストを検証し、アクセストークンをアプリに返します。
6,取得したアクセストークンを使って、Social APIを呼び出すことができます。
(続く)
https://developers.line.biz/ja/docs/line-login/web/integrate-line-login/

ざっとhttps://developers.line.biz/ja/docs/line-login/web/integrate-line-login/を読んでみたのですが、ちょっと難しいなという感じだったので、別のnode.jsでLINEloginをWEBアプリに組み込んでいる記事を見つけてきたので、そっちを参考にして進めます。

まずはこの記事から。

https://qiita.com/noboru_i/items/6a34e218b4198bde4486

解説は記事に任せるとして、実行したことだけ書いていきます。

herokuアプリの環境変数設定

heroku config:set LINECORP_PLATFORM_CHANNEL_CHANNELID=(コンソールで確認できる"Channel ID")
heroku config:set LINECORP_PLATFORM_CHANNEL_CHANNELSECRET=(コンソールで確認できる"Channel secret")

requestモジュール導入

$ npm install request --save

コード追記

index.jsに追記

const request = require('request'); //lineloginで追加

app.
  app.disable('etag') //lineloginで追加

  //lineloginで追加
 .get('/login', (req, res) => {
   const query = qs.stringify({
     response_type: 'code',
     client_id: process.env.LINECORP_PLATFORM_CHANNEL_CHANNELID,
     redirect_uri: 'https://scheduler-linebot.herokuapp.com/callback',
     state: 'hoge', // TODO: must generate random string
     scope: 'profile',
   })
   res.redirect(301, 'https://access.line.me/oauth2/v2.1/authorize?' + query)
 })
 // .get('/callback', (req, res) => {
 //   res.send('code: ' + req.query.code)
 // })

 .get('/callback', (req, res) => {
   request
     .post({
       url: `https://api.line.me/oauth2/v2.1/token`,
       form: {
         grant_type: "authorization_code",
         code: req.query.code,
         redirect_uri: 'https://scheduler-linebot.herokuapp.com/callback',
         client_id: process.env.LINECORP_PLATFORM_CHANNEL_CHANNELID,
         client_secret: process.env.LINECORP_PLATFORM_CHANNEL_CHANNELSECRET,
       }
     }, (error, response, body) => {
       if (response.statusCode != 200) {
         res.send(error)
         return
       }
       request
         .get({
           url: 'https://api.line.me/v2/profile',
           headers: {
             'Authorization': 'Bearer ' + JSON.parse(body).access_token
           }
         }, (error, response, body) => {
           if (response.statusCode != 200) {
             res.send(error)
             return
           }
           res.send(body)
         })
     })
 })
 

一旦実行してみる。

ビルドの時にエラーになりました。

Error: Cannot find module 'request'

requestがないらしい、、、

色々記事を見ましたが、package.jsonの中のdevdependencyという所に"request"があってこれでいけているはずだと思っていましたが、いけないので、勘でdependenciesの方の最後に"request": "^2.88.0"を追記したら動くようになりました!

・これでちょっとコードは汚いですが、LINELOGINしてユーザーの情報を取ってきて表示することまではできました。

ただ、上記の記事の中で、こんな記述がありました。

今回やらなかったこと
・access_token / refresh_token
access_token は30日で有効期限切れとなります。
access_token が失効したあと、10日以内に refresh_token を利用すると、再度 access_token と refresh_token を取得できます。

・stateのチェック
CSRF対策のため、 /authorize にアクセスする際のstateパラメータにランダムな文字列を生成して、 /callback で返却されたstateとチェックする必要があります。
セッションなどに一時的に保存して、チェックする必要があります。

stateのチェックはセキュリティ上必須な気がするので、そこだけやろうと思います。

・stateのチェック

まずstate : 'hoge'に乱数を入れるようにします。

加えた行のその周りだけ書いています。

index.js

var onetime_state_code; //追加

//state認証
function state_code() {
 const l = 8;
 const c = "abcdefghijklmnopqrstuvwxyz0123456789";
 const cl = c.length;
 let r = "";
 for(let i=0; i<l; i++) {
   r += c[Math.floor(Math.random()*cl)];
 }
 return r;
}

app
 .get('/login', (req, res) => {
   onetime_state_code = state_code();
   const query = qs.stringify({
     response_type: 'code',
     client_id: process.env.LINECORP_PLATFORM_CHANNEL_CHANNELID,
     redirect_uri: 'https://scheduler-linebot.herokuapp.com/callback',
     state: onetime_state_code, // 追加
     scope: 'profile',
   })
   console.log("ワンタイムステート")
   console.log(onetime_state_code)
   res.redirect(301, 'https://access.line.me/oauth2/v2.1/authorize?' + query)
 })

乱数の生成とかはコピペしてきたやつです。

直接state: function(),って感じで関数を入れることもできそうでしたが、やり方がわからなかったのと、後で認証する時に変数になっている方が都合がいいかなと思ったので、代入したものを入れています。

これで一回デプロイして実行したら、普通にログインできました。

次は受け継いできたstateを/callbackで検証します。

if文を追加して、エラーが出たら不正なアクセスですというページを戻して上手くいった時だけその後のrequestの処理を行うように改変します。

index.j

app
  .get('/callback', (req, res) => {
   console.log("ワンタイムステート")
   console.log(onetime_state_code)
   const callback_state_code = req.query.state;
   console.log("コールバックステート")
   console.log(callback_state_code)
   if (onetime_state_code != callback_state_code) {
     res.send('不正なアクセスです。')
   console.log("state_code authentication failed")
   } else {
     request
     .post({
       url: `https://api.line.me/oauth2/v2.1/token`,
       form: {
         grant_type: "authorization_code",
         code: req.query.code,
         redirect_uri: 'https://scheduler-linebot.herokuapp.com/callback',
         client_id: process.env.LINECORP_PLATFORM_CHANNEL_CHANNELID,
         client_secret: process.env.LINECORP_PLATFORM_CHANNEL_CHANNELSECRET,
       }
     }, (error, response, body) => {
       if (response.statusCode != 200) {
         res.send(error)
         return
       }
       request
         .get({
           url: 'https://api.line.me/v2/profile',
           headers: {
             'Authorization': 'Bearer ' + JSON.parse(body).access_token
           }
         }, (error, response, body) => {
           if (response.statusCode != 200) {
             res.send(error)
             return
           }
           res.send(body)
         })
     })
   }
 })

そして、実行してみます。

まずは、普通に何も考えずにやるとloginがしっかり通りました。

次に、途中で間違ったstateをパラメータに入れてエラーになるか検証します。

ログイン画面のURLの中で「state%3D0xh4lg1z%」を「state%3D0xh4lg17%」に変えて再度ページを開きます。

画像2

※シークレットウィンドウでやらないと、ブラウザにデータが残っていてパラメータがリセットされなくて検証できなかったので、注意。

そして、この状態でログインしてみます。

画像4

しっかりエラーになりました!

画像3

herokuのlogにも、コンソールで設定していたエラーがしっかり出ています。

これでLINE Loginの導入は完了です。

いよいよログインした情報をDBに保存した上で、それを呼び出してマイページ化します。分量も多くなったので、明日の記事に記載します。

こんな感じ進められるんじゃないかなと思っています。

2-2,DBへユーザデータを保存

2-3,マイページ作成

2-4,DBからデータを取得してマイページに表示

今日はここまでです。いつも最後までありがとうございます。

下記はリファレンスとして調べたことをまとめています。用語など途中でわからないものがあれば一度参照下さい。

・認可を要求する

ユーザーを認証してアプリに権限を付与するよう要求するには、以下の認可URLに必須のクエリパラメータを付けてユーザーをリダイレクトします。ユーザーをリダイレクトするには、LINEログインボタンか直接リンクを使います。
認可URL:https://access.line.me/oauth2/v2.1/authorize

認可リクエストの例)
https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=1234567890&redirect_uri=https%3A%2F%2Fexample.com%2Fauth&state=12345abcde&scope=openid%20profile&nonce=09876xyz






この記事が参加している募集

noteのつづけ方

よろしければサポートお願いします! 頂いたサポートはクリエイター活動に活用させて頂きます。