RailsでOpenID connectを用いたLINEログイン

### 前提
これまでrailsで構築している自社のサービスにTwitterログイン、Facebookログインと実装してきてその流れでLINEログインを実装しようとしたところ少し詰まったので情報を共有しておく。

Twitterログイン、FacebookログインではOmniauthを用いた認証で実装しており、LINEでもこれは使えるのだがそうするとサービス固有のUIDしかとることができない。で、サービスとしてはメールアドレスが必須情報なので仮に実装するならfakeメールアドレスでも登録させるしかないかなと思っていたのだが、どうもLINEも申請を上げて通ればメールアドレス情報を取得できるらしい。ただ、その場合認証はOmniauthではなくOpenID connectでやらないとダメみたいだ。

なので、OpenID connectで認証を通すことにする。このタイミングでいろいろな記事を漁ったが、よくわからない側面が多い。わかんね〜〜どうしたらいいんだ〜〜と下記のLINE公式ドキュメントを読んでいたら必要なことは全部書いてあった。
[https://developers.line.biz/ja/docs/line-login/web/integrate-line-login/](https://developers.line.biz/ja/docs/line-login/web/integrate-line-login/)

### まず必要なこと。
まず必要なのがLINEの開発用公式アカウントを作ることだ。これには電話番号が必須なので、仮に開発用のメールアドレスしかない場合はその時点でSIMかなにかを契約する必要に迫られる。これがまあ非常に面倒くさい。メールアドレスで登録させてくれ。ログインは下記から設定。
[https://developers.line.biz/ja/](https://developers.line.biz/ja/)

### 各種環境設定
その次は各種環境設定になる。LINEの場合はとりあえずプロバイダーを作って、そのあと具体的なサービス（今回はLINEログイン）を選択して、アプリ名称とかを決める。最初の状態ではユーザのメールアドレスを取得できるようになっていないので、必要なら申請を上げる。

この申請がまためんどうで、ログインする時に「私達はこれこれこういう目的のために、あなたのメールアドレスを取得します。決して悪用しないよ！」ということを注意書きを書いたページをユーザに提示する必要があり、そのページのスクショをとって相手に送る必要があるのだ。ただ何らかの画像認識かなんかが動いているのか一瞬で申請通る。通ると下記のようになる。
<img width="410" alt="スクリーンショット 2019-04-26 15.17.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161772/00c4f590-4b72-3bbf-23a2-ad80fd8dd4f7.png">

申請はこんなふうなページをいったんつくって送った(デザイン性死)
![スクリーンショット 2019-04-25 17.49.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/161772/c33366fe-0a22-a369-1300-d8b5365e4bbb.png)

### リダイレクトする
Log inボタンが押下されたタイミングでLINE側に情報を飛ばす。受け渡す先はauthorizeまでは全部いっしょで、パラメータの例としては下記のようなかんじ。クライエントIDとかは開発者アカウントでSNSログインを開設した時にページに出てくるのでそれを参照し、リダイレクトURLは自ページのものなので適当に設定したいとこに設定する。

```
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%20email
```

そうするとLINEのログイン画面が出てきて、scopeで設定したものが「本当に情報取得してもええんやな？」と許可を求められるのでOKを押して認証が通るとResponseが返る。この時、ユーザが拒否を選ぶとエラーレスポンスが返ってくるのでそれ用の処理も必要なら書いておく。

```http
HTTP/1.1 302 Found
Location : https://client.example.org/cb?code=abcd1234&state=0987poi&friendship_status_changed=true
```

続いて、上記Responseで返ってきたcodeを使って```https://api.line.me/oauth2/v2.1/token```に対してアクセストークンを要求する。例をあげると下記のようなかんじ

```rb
    uri = URI.parse("https://api.line.me/oauth2/v2.1/token")
    request = Net::HTTP::Post.new(uri)
    request.content_type = "application/x-www-form-urlencoded"
    request.set_form_data(
      "client_id" => "xxxx",
      "client_secret" => "xxx",
      "code" => params[:code],
      "grant_type" => "authorization_code",
      "redirect_uri" => "http://localhost:8000/line_logins",
    )
    
    req_options = {
      use_ssl: uri.scheme == "https",
    }
    
    response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
      http.request(request)
    end
```

上記のアクセストークン取得が通ると下記のようなResponseが返ってくる。

```json
{
    "access_token": "bNl4YEFPI/hjFWhTqexp4MuEw5YPs...",
    "expires_in": 2592000,
    "id_token": "eyJhbGciOiJIUzI1NiJ9...",
    "refresh_token": "Aa1FdeggRhTnPNNpxr8p",
    "scope": "profile",
    "token_type": "Bearer"
}
```

この段階ではまだユーザのメールアドレスは取得できていない。Responseの中にあるid_tokenはJson Web Token（JWT）でRubyの場合はJWTライブラリを使って復号化してやるのが楽だ。

```rb
JWT.decode(JSON.parse(response.body)["id_token"],"#{client_secret}")
```

返却されるのは下記のような値。これでようやくユーザのemailを取得することができる……。

```rb
    [
      {
        "iss" =>"https://access.line.me",
        "sub"=>"Ud5d0fe24be72b8736232werwb3c3b586e",
        "aud"=>"1566666665",
        "exp"=>1556666660,
        "iat"=>1556666666,
        "email"=>"honyarara@gmail.com"
      },
      {
        "typ"=>"JWT",
        "alg"=>"HS256"
      }
    ]
```