窓を作っては壊していた人のブログ

この謎のブログタイトルの由来を知るものはもういないだろう

Firebase AuthenticationとAzure FunctionsでLINEログインを試す 〜LINEログインまで〜

Firebase Authenticationを実プロダクトに採用しているプロダクト多いんじゃないでしょうか。 メールアドレスはもちろん、TwitterGitHubと言ったエンジニア各位が持ってそうなアカウントを用いたFirebaseのアカウントの発行もサポートしています。

そんなFirebase Authenticationですが、日本で多く使われているLINEみたいなサービスは標準でサポートされていません。 今回はそういったサポートされていないOpenID Connect対応なサービスを使ったFirebaseアカウントの作成およびログインをやっていこうと思います。

完成しているプロジェクトは

github.com

こちらになります。 コード見ればわかるぜという方はこちらを参考にして下さい。

記事を書き始めたらちょっと大変だったので連載物にします!!!!!すぐ次のやつ書きます!!!!!

使用したサービス

  • Firebase Authentication
  • Firebase Firestore
  • Azure Functions

実装やっていく

LINEの公式ドキュメントを見ながら実装を進めていきましょう。

https://developers.line.biz/ja/docs/line-login/web/integrate-line-login/

こちらのドキュメントの「始める前に」と「チャネルを設定する」は既に完了している状態と仮定して進めていきます。

LINEログインに直接は関わりませんが、今回Firebase Authenticationも使うのでFirebaseのプロジェクトも作っていきます。

Firebaseのプロジェクト作る

Firebase Authenticationを使うのでFirebaseのプロジェクトを作りましょう。 LINEログインのURLを生成する際に使用するStateやNonceと言った値をユーザと紐付けるために今回はFirestoreを使用してみます。 そのためFirebaseのプロジェクトを作った際、Firestoreも使用できるようにしておきましょう。

またFirestoreはFirebaseのユーザと紐付けた形で使用します。 FirebaseのアカウントをLINEログインで作成するのにFirebaseのアカウントを作るとは…みたいな卵が先か鶏が先かみたいなことになります。

その問題を解消するためにFirebase Authenticationの匿名認証を今回は使用してみます。

https://firebase.google.com/docs/auth/?hl=ja

匿名認証の説明を引用してみます。

一時的な匿名アカウントを作成することで、ユーザーにログインを要求することなく認証できる機能を使用します。ユーザーが後から登録することにした場合は、匿名アカウントを通常のアカウントにアップグレードして、ユーザーが前回終了したところから操作を続行できるようにすることができます。

ユーザにアカウントの作成およびログインを意識させずにFirebaseアカウントを作成しやすそう、またアカウントがあるのでFirestoreを使いやすいと言ったメリットがあります。 ということで、Firebase Authenticationのログイン設定で匿名認証を有効にしておきましょう。

ログインのためのURLの作成

LINEの公式ドキュメントのログインフローでの(1)に該当する部分を実装し、(2)ます。

生成するURLに必須のパラメータは以下ドキュメントで確認します。

https://developers.line.biz/ja/docs/line-login/web/integrate-line-login/#spy-making-an-authorization-request

この必須パラメータで予め固定できるものは response_type, client_id, redirect_uri, scope です。 ユーザ(リクエスト?)固有にする必要があるのが state パラメータです。

この state パラメータはアプリケーション側で作っていきます。

実際のコードはこちらです。

https://github.com/yamachu/FirebaseAuthCustomTokenSample/blob/78b4a19ed14e95a605c04a6f2b5e186a6107446a/api/LineTokenVerifier.cs#L33-L51

from token in ParseAuthorizationToken(req) // 匿名認証のFirebase AuthのTokenを取得
let authTemp = AuthTemporary.GenerateRandomAuthTemporary() // Randomのstateを生成する
from _ in TryAsync(() => _authTempClient.StoreAuthTemporary(token.Uid, authTemp).ToUnit()) // Firebaseのアカウントに紐づけてstateをFirestoreに保存する
    .ToEither(e => e.Exception.IfNone(new Exception(e.Message)))
let url = _lineService.GetAuthorizeRequestURL(authTemp) // stateの値を埋め込んでURLを生成する
select url

流れとしては本当にシンプルですね。 詳細の実装はコードをご覧ください。

ここで必要なのはFirebaseユーザとここで生成したstateの値を紐付けて保存する、ただこれだけです。

あとはこのURLをユーザに踏んでもらいます。

フロントの実装はこんな感じ。

https://github.com/yamachu/FirebaseAuthCustomTokenSample/blob/78b4a19ed14e95a605c04a6f2b5e186a6107446a/front/src/index.tsx#L246-L283

Firebase Authenticationの匿名認証をして、そのTokenをアプリケーション側に送り生成されたURLに遷移するみたいなことを上記のコードでは行っています。

コールバックから情報を受け取る

ここではドキュメントの(3)と(4)を実装していきます。

ユーザが権限に同意した場合、コールバックURLにユーザがリダイレクトされます。 このコールバックされたページのクエリパラメータを用いてアプリケーション側でアクセストークンを取得していきます。

フロント側から codestatefriendship_status_changed をアプリケーションに送信します。

送信側のコードは

https://github.com/yamachu/FirebaseAuthCustomTokenSample/blob/78b4a19ed14e95a605c04a6f2b5e186a6107446a/front/src/index.tsx#L285-L323

ただ単にHeaderをパースして送ってるだけです。簡単。

アプリケーション側は

https://github.com/yamachu/FirebaseAuthCustomTokenSample/blob/78b4a19ed14e95a605c04a6f2b5e186a6107446a/api/LineTokenVerifier.cs#L60-L75

from token in ParseAuthorizationToken(req) // 匿名認証のFirebase AuthのTokenを取得
from requestBody in TryAsync(() =>
{
    using var sr = new StreamReader(req.Body);
    return sr.ReadToEndAsync();
}).ToEither(e => e.Exception.IfNone(new Exception(e.Message)))
from json in Try(() => System.Text.Json.JsonSerializer.Deserialize<LineTokenRequest>(requestBody, new System.Text.Json.JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
})) // リクエストパラメータのParse
.ToEither((e) => new Exception("invalid json object", e)).ToAsync()
from authTemp in TryAsync(() => _authTempClient.RestoreAuthTemporary(token.Uid)).ToEither(e => e.Exception.IfNone(new Exception(e.Message))) // Firebaseのアカウントに紐付いたstateをFirestoreから引っ張ってくる
from _ in (authTemp.State == json.State) // stateがアプリケーション側で作られたものとリクエストパラメータと一致するかの検証
    ? RightAsync<Exception, Unit>(Unit.Default)
    : LeftAsync<Exception, Unit>(new Exception("State is not matched"))
from lineIdentity in TryAsync(() => _lineService.GetLineIdentify(json.Code, authTemp.Nonce)) // リクエストパラメータの `code` パラメータを元にLINEのアクセストークンを取得
    .ToEither(e => e.Exception.IfNone(new Exception(e.Message)))

の流れで行っていきます。

ここで重要なのはアプリケーション側で生成された state パラメータが一致しているかの検証と、code パラメータからLINEのアクセストークンを取得するところです。

ここではアクセストークンを生成する部分を少し紹介します。

ここで使用されている _lineService.GetLineIdentify の実装は以下のとおりです。

https://github.com/yamachu/FirebaseAuthCustomTokenSample/blob/78b4a19ed14e95a605c04a6f2b5e186a6107446a/api/Services/LineService.cs#L44-L59

内容としては

  1. https://api.line.me/oauth2/v2.1/token に対してユーザから来た code パラメータとその他必要なパラメータを付与しリクエストを送ってアクセストークンやユーザの情報を取得する
  2. (Optionalだけど) https://api.line.me/oauth2/v2.1/verify に対して 1. で手に入れたアクセストークンを送信してtokenの検証を行う
    1. で手に入れたアクセストークン(JWT)をパースして、LINEのユーザIDを取得する

を行っています。

1のアクセストークン生成に必要なパラメータは

https://developers.line.biz/ja/docs/line-login/web/integrate-line-login/#spy-issue-access-token

を、

2のアクセストークンの検証に必要なパラメータに関しては

https://developers.line.biz/ja/docs/social-api/managing-access-tokens/#spy-verify-access-token

を参照して下さい。

以降の処理では1. で手に入れたLINEのユーザIDを使用していきます。

Custom Tokenでログイン

さて……ここらへんは次の記事で(一番見たい部分ではあるけど)

ということで一旦ここまでで〆ます