クライアントサイドとサーバーサイドとIDaaS（FirebaseAuth等）を用いた認証フローの整理

# 0. はじめに
　サービスを作っていると、避けて通れないのがログイン認証です。無料で誰でも使えるサービスであっても、ユーザーの一意性を確保するために多くのサービスではログインを求められることが多いです。その場合のログイン方法として古くからメールアドレスとパスワードによる認証がありますが、昨今ではそれらに加え、GoogleやFacebookなどのアカウントでログインするソーシャルログインという方法も用いられるようになってきました。ソーシャルログインは各サービスごとに個別に実装することもできますが、今はFirebase Authentication（以下、Firebase Auth）やAuth0などのIDaaS（Identity as a Service）を用いることも多くなっています。加えて、クライアントサイド（フロントエンドやネイティブアプリ）側とサーバーサイド（バックエンド）が分かれる構成をとることも多く、その際の登場人物は、クラウアントサイド、サーバーサイドに加え、認証のためのIDaaSが加わります。登場人物が増えることにより、連携する際にも考慮すべき事項が増えることになります。
　本記事では、「Cookieとsessionを用いたWebにおけるシンプルなログインフロー」という従来からあるメールアドレスとパスワードによる認証のフローから始まり、主にネイティブアプリとの連携の様に「Cookieを利用できない場合にJWTを利用した場合のフロー」、さらには、「Firebase AuthなどのIDaaSを用いたログインフロー」（クライアントサイド、サーバーサイド、IDaaSの連携フロー）について自分なりに整理したものを記載します。
　また、初回登録時やログイン時だけでなく、ログイン後にセッションを維持したままの画面遷移時やAPIでの情報取得時のフローについても記載します。多くの場合、ログイン後一定時間は再度ログイン認証を必要とせずにサービスを利用することができますが、サーバーサイド側としてはユーザーが正しくログインしたかどうかを検証した上で情報を返す必要があります。その場合のフローをしっかり把握しておくことはログイン認証を実装する上で非常に重要であり、おろそかにするとセキュリティリスクにもなる箇所だと思います。
　本記事で私の認識を表に出すことにより、認識が間違っている部分を指摘していただいて修正することも意図しておりますので、間違っている点があれば優しくご教授いただけましたら幸いです。

# 1. Cookieとsessionを用いたWebにおけるシンプルなログインフロー
昔ながらのメールアドレスとパスワードによる認証フローです。Railsの場合だと、[devise](https://github.com/heartcombo/devise)や[sorcery](https://github.com/Sorcery/sorcery)を用いた場合のフローになります。
## 1-1. 未ログイン時にログインする場合（既に新規登録済）
```mermaid
sequenceDiagram
  participant C as Client
  participant S as Server
  participant CS as CacheServer
  participant RDB
  C->>+S: ログイン
  S->>-C: Set-Cookie
  Note over C: Cookieにsession情報(public_id)保存
  C->>+S: ログイン後のページへアクセス
  Note over S: Cookieから取得したpublic_idからprivate_id取得
  S->>+CS: private_idからsession情報Request
  CS->>-S: session情報
  Note over S: session情報からuser_id情報取得
  S->>+RDB: user_idをもとにSQLで情報Request
  RDB->>-S: user情報
  S->>-C: 有効なuserであればログイン後のページへ
```

## 1-2. ログイン後の再アクセス時（Cookieの有効期限が切れる前）
すでにCookieが存在するので、「1-1. 未ログイン時にログインする場合（既に新規登録済）」の最初の「ログイン」部分がないだけのフローになります。
```mermaid
sequenceDiagram
  participant C as Client
  participant S as Server
  participant CS as CacheServer
  participant RDB
  C->>+S: サイトへアクセス
  Note over S: Cookieから取得したpublic_idからprivate_id取得
  S->>CS: private_idからsession情報Request
  CS->>S: session情報
  Note over S: session情報からuser_id情報取得
  S->>+RDB: user_idをもとにSQLで情報Requet
  RDB->>-S: user情報
  S->>-C: 有効なuserであればログイン後のページへ
```

Cookie情報は、ChromeであればディベロッパーツールのApplication→Storage→Cookiesで確認することができます。
以下画像でNameが_myapp_sessionsとなっていて、bc14から始まるValueがCookieに保存されたSession情報です。<sup>[1](http://)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/f0956e47-5c06-74a5-352c-4d77419075e6.png)</sup>
大切なのは、`redis_store`や`active_record_store`では<sup>[備考](https://qiita.com/yuyasat/items/0c0b3b6710e0e8006565#備考)</sup>、このCookie内に保存されているsession情報はsession情報そのものではなく、セッションサーバーに保存してあるsession情報のキーとなるもの（private_id）を変換したもの(public_id)です。CookieのValue（上画像で`bc14`から始まるもの）からprivate_idを復元するには、`Digest::SHA256.hexdigest`を利用します[^1]。この処理からもわかるように、private_idが把握できれば、public_idがわかってしまいます。

[^1]: https://omohikane.com/rails_rack_redis_session_store/

```ruby:リスト１：private_idからpublic_idへの復元
Digest::SHA256.hexdigest("bc1483d7e368e27b734exxxxxxxxxxxx")
=> "bac8aa16202e44df3c97441bfb734c5e396d90e44ecxxxxxxxxxxxxxxxxxxxxx"
```
実際に、`active_record_store`を用いた場合に、セッション情報が保存されているデータベースを確認してみた結果がリスト２になります（config/application.rbで、`ActiveRecord::SessionStore::Session.table_name = 'my_sessions'`
のように設定し、ActiveRecord::Baseを継承したMySessionクラスを用意しています）。`session_id`カラムにprivate_idが含まれた値が格納されているのがわかります（rack gem内で`"#{ID_VERSION}::#{hash_sid(public_id)}"`となっているので`2::`がついていますが[^2]）

[^2]: https://github.com/rack/rack/blob/v2.2.7/lib/rack/session/abstract/id.rb#L25

```ruby:リスト２：保存されたセッション情報の確認
MySession.last
=> #<MySession:0x000000010eff76c0
 id: 1,
 session_id: "2::bac8aa16202e44df3c97441bfb734c5e396d90e44ecxxxxxxxxxxxxxxxxxxxxx",
 data:
  "{\"value\":{\"last_action_time\":\"2023-04-21T11:21:53.332+09:00\",\"_csrf_token\":\"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=\",\"user_id\":\"1\",\"login_time\":\"2023-04-21T11:21:49.982+09:00\"}}",
 created_at: Fri, 21 Apr 2023 11:21:49.966523000 JST +09:00,
 updated_at: Fri, 21 Apr 2023 11:21:55.357842000 JST +09:00>
```
Cookieにsecure属性をつけることで、httpsのときのみブラウザはサーバーにCookieを送るようになり、もし盗み取られたとしても暗号化されているので、攻撃者は読み取ることができなくなります。また、サーバー側でsessionを管理できているので、sessionを無効化することで、強制的にログアウトさせることも可能です。


# 2. Cookieを利用できない場合にJWTを利用した場合のフロー
 「1」では、Webの場合のログインフローで、Cookieが利用できる場合のログインフローについて紹介しました。ここでは、モバイルアプリ向けなど、WebではなくCookieが利用できない場合のフローについて紹介します。認証周りのサーバーサイドとクライアントとのやり取りについては、昨今はJSON Web Token(JWT)と呼ばれるものがよく利用されます。JWTは認証の文脈でよく出てきますが、JWT自体は、JSONデータをURLセーフかつ、コンパクトにする方法として規定されたものになります[^3]ので認証以外でも用いられますし、用いることができます。
[^3]: https://developer.mamezou-tech.com/blogs/2022/12/08/jwt-auth/

ここでは一例として、Railsで認証でJWTを用いる場合に比較的採用される[devise-jwt](https://github.com/waiting-for-dev/devise-jwt)というgemを用いた場合の挙動について説明したいと思います。devise-jwtは内部的には、[warden-jwt_auth](https://github.com/waiting-for-dev/warden-jwt_auth)を利用しており、

> This gem is just a replacement for cookies when these can't be used. As cookies, a token expired with warden-jwt_auth will mandatorily have an expiration time. If you need that your users never sign out, you will be better off with a solution using refresh tokens, like some implementation of OAuth2.

とあるように、Cookieを置き換えできるものであり、Cookieが使えない時に利用するものと言えます。
ここで改めて、JWTについての詳細は記載しませんが、ネット上の情報を見ているとnode.jsでの例ですが、以下のようにメールアドレスを用いてJWTにしていたりします。この場合、一般に、JWT自体は改ざんについては何も規定されておらず、改ざんされてしまってはセキュリティリスクになってしまうため、JSON Web Signature(JWS)という規定を用います。
```javascript:リスト３：メールアドレスを用いてJWT生成（node.jsでの例）
JWT.sign({ email: "test@example.com" }, "SECRET_KEY", { expiresIn: "24h" })
```
リスト３において生成された値は、例えば、以下のようになり、JWSの値となります。
```リスト４：リスト３のJWT.signで生成された値
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InRlc3QxQHRlc3QuY29tIiwiaWF0IjoxNjg0MTMxOTI4LCJleHAiOjE2ODQyMTgzMjh9.PU3g4lJnllhX5Oz8no0kbf6Bniceq0pr8T24miUUDeQ
```
リスト４の値は、`.`（ドット）で区切られており、ドットで区切られた一つ目の部分（下記の赤い部分）がヘッダ部、２つ目の部分（同青い部分）がペイロード部（もともとのJWT）、３つ目の部分（同緑の部分）がシグニチャと呼ばれ、改竄されていないかをチェックするためのものになります。
> <font color="red">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9</font>.<font color="blue">eyJlbWFpbCI6InRlc3QxQHRlc3QuY29tIiwiaWF0IjoxNjg0MTMxOTI4LCJleHAiOjE2ODQyMTgzMjh9</font>.<font color="green">PU3g4lJnllhX5Oz8no0kbf6Bniceq0pr8T24miUUDeQ</font>

経験的に、このドットで連結された値(JWS)をJWTと呼ぶケースもあるので、文脈にって認識を正しくしておくことが必要です。
JWS自体は、改竄を検知することはできるものの、デコードされてしまえば、ヘッダ部、ペイロード部は中身を見ることができます。試しに、リスト４をデコードしてみます。簡単に行うには、https://jwt.io/ へアクセスしてリスト４の値を貼り付けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/ba0c7f48-c09b-0ce0-165e-017343dbb2f3.png)
このように、JWSが盗まれデコードされた場合、メールアドレスの値が把握されてしまいます。繰り返しになりますが、JWS自体は暗号化して読み取り不可にするものではなく、`SECRET_KEY`を用いて、改竄されていないかをチェックするものになります。したがって、`SECRET_KEY`が漏れてしまうと意味をなさないので取り扱いに注意が必要です。
devise-jwtでは、メールアドレスのような個人情報はJWSには含めません。代わりに、`jti`という値を用いています。試しに

```text:リスト５：devise-jwtを利用した際のJWSの一例
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwic2NwIjoidXNlciIsImF1ZCI6bnVsbCwiaWF0IjoxNjIwNDkzOTUzLCJleHAiOjE2MjA0OTc1NTMsImp0aSI6IjlmZjkzMDA2LTAxNTMtNDc5YS1hYjY2LTZiMDBhOWU2NjM1ZCJ9.K6oHIUI0AuZ4HfDV1iElFe9OZoMh_st3l1rfhD0PIqY
```
というJWSをデコードしてみると、ペイロード部は次のようになります。
```text:リスト６
{
  "sub": "1",
  "scp": "user",
  "aud": null,
  "iat": 1620493953,
  "exp": 1620497553,
  "jti": "9ff93006-0153-479a-ab66-6b00a9e6635d"
}
```
という値になります。jtiはJWT IDのことで、認証させるモデル（多くの場合、`User`）の属性（テーブルのカラム）に、`jti`というカラムを追加され、JWTをデコードしたpayload部のjtiの値からuserを引っ張ってきます。したがって、JWTがデコードされても、センシティブな情報を取得することはできません。

## 2-1 JWTによるログインフロー
devise-jwtを用いた場合のログインフローについて下図に示します。
```mermaid
sequenceDiagram
  participant C as Client
  participant S as Server
  participant CS as CacheServer
  participant RDB
  C->>+S: ログイン情報（メールアドレス、パスワード等）・・・(1)
  S->>-C: JWT（HTTPのヘッダーのAuthorization）
  Note over C: CookieやSharedPreferencesにsession情報(public_id)保存・・・(2)
  C->>+S: ログイン後に取得できる情報のリクエスト・・・(3)<br>（CookieやSharedPreferencesに保存したpublic_idをヘッダーに格納）
  Note over S: JWTの検証
  Note over S: public_idからprivate_id取得
  S->>+CS: private_idからsession情報を取得するためのRequest
  CS->>-S: session情報
  Note over S: session情報からuser_id情報取得
  S->>+RDB: user_idをもとにSQLで情報Request
  RDB->>-S: user情報
  S->>-C: 有効なuserであればリクエストした情報返却
```
- (1)のリクエストおよびレスポンス例が以下になります。レスポンスヘッダーの`Authorization`内で`Bearer #{token}`の形で返却され、クライアントサイドはこの値をCookieやSharedPreferencesに格納しておきます。（devise-jwt自体はCookieが利用できない場合に利用することが多いと思いますが、Cookieが利用できる場合でもdevise-jwtを利用することは可能です。）
```bash:リスト７：リクエスト例
curl -XPOST -i -H "Content-Type: application/json" -d '{ "user": { "email": "test@example.com", "password": "12345678" } }' http://localhost:3000/users/sign_in
```
```text:リスト８：レスポンス例
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Content-Type: application/json; charset=utf-8
Vary: Accept, Origin
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwic2NwIjoidXNlciIsImF1ZCI6bnVsbCwiaWF0IjoxNjIwNDkzOTUzLCJleHAiOjE2MjA0OTc1NTMsImp0aSI6IjlmZjkzMDA2LTAxNTMtNDc5YS1hYjY2LTZiMDBhOWU2NjM1ZCJ9.K6oHIUI0AuZ4HfDV1iElFe9OZoMh_st3l1rfhD0PIqY
ETag: W/"4f46b654dd4b5ef6187f2663ef5a55c4"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 498ef50b-4bbb-44b6-9b39-dd45d03aa7b4
X-Runtime: 0.293374
Transfer-Encoding: chunked

{"message":"ログインに成功しました。"}
```
- (2) クライアントサイドがnext.jsなどWebで利用しているのであればCookie、ネイティブアプリであれば、SharedPreferences等に入れることになると思います。

- (3) `Authorization`ヘッダーに`Bear`をつけて`JWS`を送信することで、サーバーサイド側は`warden-jwt_auth`にて認証させることができます[^4]。
[^4]:https://github.com/waiting-for-dev/warden-jwt_auth/blob/v0.8.0/lib/warden/jwt_auth/header_parser.rb#L16-L22

```bash:リスト９：curlを用いたリクエスト例
curl -XGET -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwic2NwIjoidXNlciIsImF1ZCI6bnVsbCwiaWF0IjoxNjIwNDkzOTUzLCJleHAiOjE2MjA0OTc1NTMsImp0aSI6IjlmZjkzMDA2LTAxNTMtNDc5YS1hYjY2LTZiMDBhOWU2NjM1ZCJ9.K6oHIUI0AuZ4HfDV1iElFe9OZoMh_st3l1rfhD0PIqY" -H "Content-Type: application/json" http://localhost:3000/private_data
```

## 2-2 ログイン後の再アクセス時
ログイン後の再アクセス時には、CookieやSharedPreferencesで保存しているJWSを`Authorization`ヘッダーに`Bear`をつけて`JWS`を送信することで、セッション情報を取得します。
```mermaid
sequenceDiagram
  participant C as Client
  participant S as Server
  participant CS as CacheServer
  participant RDB
  C->>+S: ログイン後に取得できる情報のリクエスト・・・(3)<br>（CookieやSharedPreferencesに保存したpublic_idをヘッダーに格納）
  Note over S: JWTの検証
  Note over S: public_idからprivate_id取得
  S->>+CS: private_idからsession情報するためのRequest
  CS->>-S: session情報
  Note over S: session情報からuser_id情報取得
  S->>+RDB: user_idをもとにSQLで情報Request
  RDB->>-S: user情報
  S->>-C: 有効なuserであればリクエストした情報返却
```

# 3. Firebase AuthなどのIDaaSを用いたログインフロー
　「１」「２」では、主にクライアントサイドとサーバーサイドのやり取りについて紹介しましたが、ここでは、Firebase AuthやAuth0などのIDaaSを用いた場合のフローについて考えていきたいと思います。シンプルなメールアドレスとパスワードによる認証であれば、自前で実装したほうが楽な場合が多いと思いますが、IDaaSを用いる利点としては、ソーシャルログインやSSOなど、多様なログイン方法に対応する必要がある場合など、自前で実装するよりも実装コストを削減できると思います。
　さらに、ユーザー管理以外にもサービスを提供するにあたってさまざまなデータやロジックが存在し、その場合はサーバーサイドにRailsやLaravel, Spring Bootといったフレームワークを用いて構築することになると思います。その場合の認証、さらに、認証後にリクエストごとに毎回認証しなくても済むようにセッション管理する場合のフローはどうなるでしょうか。
ここでは、クライアントサイド、サーバーサイドに加え、認証でFirebase　AuthなどのIDaaSを用いた場合の認証やログインフローについて考えてみたいと思います。とりあげるフローは初回登録時の認証および、ログイン後にセッションを維持し続けるケースになります。他の記事を見ていると、初回登録時の認証およびサーバーサイドでのJWSの検証に関しての記事が多く、認証してログインした後、毎回認証させるわけにはいかないので、本記事では、セッションを維持して一定期間は再度ログインしなくても済むようにするための処理フローについても考えていきます。
　なお、ちょっとでもセキュリティに自信がないなら、 Firebase Authentication を検討しようという意見も頷けますが（特にバックエンドを別に用意せず、フロントエンドだけの場合など）、Firebase　Authを使う場合でも気をつけなければいけないことはあります。Flatt Securityさんが、
[Firebase Authentication 7つの落とし穴 - 脆弱性を生むIDaaSの不適切な利用](https://blog.flatt.tech/entry/firebase_authentication_security)という記事で記載していることも確認の上、加えてセキュリティ診断サービスを利用するなどして自分たちが作ったサービスがセキュリティ的に問題がないか確認してからローンチすることが賢明です。

## 3-1. 初回登録時
初回登録時のフローは以下のようになります。

```mermaid
sequenceDiagram
  participant C as Client
  participant S as Server
  participant IDaaS
  participant RDB
  C->>+IDaaS: ログインリクエスト・・・(1)
  IDaaS->>-C: JWSやユーザー情報・・・(2)
  C->>+S: JWS・・・(3)
  S->>+IDaaS: JWKsリクエスト・・・(4)
  IDaaS->>-S: JWKs
  Note over S: JWSの検証・・・(5)
  Note over S: JWSをデコードしてPayload部に入っているuser_id取得
  S->>+RDB: user_idをkeyとして、ユーザーレコード生成
  RDB->>-S: user情報
  S->>-C: 有効なuserであれば<br>リクエストした情報返却
```

初回登録時は、ClientにFirebaseAuthenticationなどのIDaaSを実装し、例えば、GoogleアカウントやFacebookなどで認証します(1)。認証に成功したら、FirebaseAuthからClientには、JWSやユーザー情報などがClientに返却されます(2)。ClientはIDaaSから返却されたJWSをServerにPostします(3)。
JWSを受け取ったServerは、そのJWSが正しいかどうかを検証します。FirebaseAuthの場合、検証するためには公開鍵（JSON Web Keys; JWKs）を取得する必要があり、`https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com`へリクエストして取得します[^5]。JWTライブラリを用いて、Clientから送られたJWSを検証します。rubyの場合、[ruby-jwt](https://github.com/jwt/ruby-jwt) gemを利用するといいと思います。
[^5]: https://firebase.google.com/docs/auth/admin/verify-id-tokens

この時、Firebaseの公式ドキュメントによると、ruby-jwtの`JWT.decode`における第三引数をtrueにして署名を確認する（nodeのjsonwebtokenであれば、`const JWT = require("jsonwebtoken");JWT.verify`だけでなく、`exp`や`iat`なども制約に従っているか確認する必要があるようです[^5]。参考までに、`exp`や`iat`なども制約の確認を省いたruby-jwtを用いた場合のJWSの検証のための実装例をリスト１０に示します。
```ruby:リスト１０：ruby-jwtを用いたJWS検証の実装例
# token: リクエストから取得したJWS。
# kidやalgorithmを把握するため、検証なしで（第三引数をfalseにして）デコードする。
decoded_token = JWT.decode(token, nil, false)

# ruby-jwtを使うと、JWTのHeader部が配列の２番目に入ってくる
kid = decoded_token.dig(1, "kid")
algorithm = decoded_token.dig(1, "alg")

# 公開鍵（JWKs）の取得
jwks_url = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com"
response = Typhoeus.get(jwks_url) # 好きなHTTPクライアントをご利用ください。

# 公開鍵をデコードするために不要な文字を削除。この時、複数の公開鍵がGoogleのAPIから返却されるので、
# kidから利用する方を取得する。
jwks_string = JSON.parse(response.body)[kid].gsub(/-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----/, '').strip

# JWSの検証
JWT.decode(token, OpenSSL::X509::Certificate.new(Base64.decode64(jwks_string)).public_key, true, { algorithm: algorithm })
```
検証後は、デコードしたJWS内に入っているユニークなidを元にユーザーのレコードを生成します。

## 3-2. ログイン後および再アクセス時（Webの場合）
次に、初回会員登録後、セッションを維持したままリクエストした場合のWebにおけるフローになります。
```mermaid
sequenceDiagram
  participant C as Client
  participant S as Server
  participant IDaaS
  participant RDB
  participant CS as CacheServer
  alt 新規登録時と同じフロー
  C->>+IDaaS: ログインリクエスト
  IDaaS->>-C: JWSやユーザー情報
  C->>+S: JWS
  S->>+IDaaS: JWKsリクエスト
  IDaaS->>-S: JWKs
  Note over S: JWSの検証
  Note over S: JWSをデコードしてPayload部に入っているuser_id取得
  end
  S->>+RDB: user_idをもとにSQLで情報Request
  RDB->>-S: user情報
  Note over S: 有効なuserであるか確認
  S->>-C: Set-Cookie・・・(6)
  Note over C: Cookieにsession情報(public_id)保存
  alt セッション維持
  C->>+S: ログインユーザーのみが取得できる情報のリクエスト
  Note over S: Cookieから取得したpublic_idからprivate_id取得
  S->>+CS: private_idからsession情報Request
  CS->>-S: session情報
  Note over S: session情報からuser_id情報取得
  S->>+RDB: user_idをもとにSQLで情報Request
  RDB->>-S: user情報
  S->>-C: 有効なuserであればログイン後のページへ
  end
```
ログイン認証部分に関しては、初回登録時のフローと大きな違いはありません。違いとしては、ユーザーが既に登録済みであるので、レコードを生成するのではなく、RDBから情報を取得するというところになります。
その後のフローが変わっており、Set-Cookieヘッダーフィールドを用いてClient（ブラウザ）にsession情報を保持します(6)。ここから先は、「1. Cookieとsessionを用いたWebにおけるシンプルなログインフロー」と変わらないので説明はそちらをご覧ください。なお、このパターンの場合、ClientとサーバーサイドでFQDNが異なるため、CORSの設定などを正しく行う必要がある場合があります。

## 3-3. ログイン後の再アクセス時（スマホアプリなどCookieを使わない場合）
最後に、初回会員登録後、セッションを維持したままリクエストした場合のCookieを使わない場合のフローになります。
```mermaid
sequenceDiagram
  participant C as Client
  participant S as Server
  participant IDaaS
  participant RDB
  participant CS as CacheServer
  alt 新規登録時と同じフロー
  C->>+IDaaS: ログインリクエスト
  IDaaS->>-C: JWSやユーザー情報
  C->>+S: JWS
  S->>+IDaaS: JWKsリクエスト
  IDaaS->>-S: JWKs
  Note over S: JWSの検証
  Note over S: JWSをデコードしてPayload部に入っているuser_id取得
  end
  S->>+RDB: user_idをもとにSQLで情報Request
  RDB->>-S: user情報
  Note over S: 有効なuserであるか確認
  S->>-C: JWT（HTTPのヘッダーのAuthorization）
  Note over C: CookieやSharedPreferencesにsession情報(public_id)保存
  alt セッション維持
  C->>+S: ログイン後に取得できる情報のリクエスト・・・(3)<br>（CookieやSharedPreferencesに保存したpublic_idをヘッダーに格納）
  Note over S: JWTの検証
  Note over S: public_idからprivate_id取得
  S->>+CS: private_idからsession情報取得するためのRequest
  CS->>-S: session情報
  Note over S: session情報からuser_id情報取得
  S->>+RDB: user_idをもとにSQLで情報Request
  RDB->>-S: user情報
  S->>-C: 有効なuserであればリクエストした情報返却
  end
```
こちらも、ログイン認証部分に関しては、初回登録時のフローと大きな違いはありませんし、ユーザーが既に登録済みであるので、レコードを生成するのではなく、RDBから情報を取得するというところも、Cookieを利用する場合と変わりません。
ログイン認証後、HttpのAuthenticationヘッダーでsession情報（private_id）を送るのではなく、JWTを送るようにします。これは、「2.1 JWTによるログインフロー」での仕組みと同じものになります。従って、ここから先の流れも、「2.1 JWTによるログインフロー」と変わらないのでフローの説明はそちらをご覧ください。
本記事では、具体的な実装については記載しませんが、私の手元では、devise-jwt gemを用いてこの仕組みを実現しました。つまり、IDaaSから取得したJWTをClient経由で渡してもらい、その検証をした後のセッション管理は、サーバーサイド側で行うということです。その際に、セッション管理用に、IDaaSから取得したものとは異なるJWTを用いてセッション管理（private_idを発行しJWTに格納し、HTTPのAuthenticationヘッダーに格納して渡す）をするというわけです。私は主にRailsを主戦場として仕事をしていますので、Railsにおける実際の実装法についてはまた別の記事で紹介したいと思います。

# 4. 最後に
認証機能というのは一度作ってしまうとあまり触る機会はないかもしれません。特にセキュリティが経験が浅いエンジニアに手放しで任せられるケースというのは少ないと思います。また、レビュアーも自ら実装する場合には気づけても、レビューでは漏れてしまう瑕疵というのも存在します。多くの場合、仕事で必要になったタイミングで学んだり考えたりすれば済むケースも多いのですが、じっくり考える時間を作ったほうがいい場合もあります。私の場合、IDaaSを利用した場合のフローについて腰を据えて考えたことがなかったので、考える時間を作ってみました。ですので、間違っている可能性やより良いフローもあると思います。その場合はぜひ優しくご教授いただけましたら幸いです。

# 備考
Railsにおけるsession_storeの設定については、`Rails.application.config.session_store`で設定されており、私がこの記事を書くために準備したリポジトリでは、`active_record_store`を使っているので、以下のような設定になっています。
```ruby
Rails.application.config.session_store :active_record_store, key: '_myapp_sessions', secure: Rails.env.production?, expire_after: 12.hour
```
他の設定については、[Rails のキャッシュ機構](https://railsguides.jp/caching_with_rails.html#%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5%E3%82%B9%E3%83%88%E3%82%A2)をご覧ください。簡易的な説明を以下の表にまとめますが、本番環境では、`redis_store`、`mem_cache_store`、`active_record_store`のどれかを使うと思います。開発環境やtest環境では、この３つ以外を利用するケースもあると思います。
|種類|説明|
|:---:|----|
|redis_store|session情報の保持にredisを利用する。redis gemを利用。|
|mem_cache_store|session情報の保持にmemcachedを利用する。dalli gemを利用。|
|active_record_store|session情報をRDBに保存する。activerecord-session_storeを利用。|
|cookie_store|session情報をCookieに保存する。Cookie情報が盗まれるとログインできてしまうので本番環境で利用するのはお勧めされない。session情報はサーバー側で保持しておく。|
|file_store|session情報をサーバー上のファイルに保存する。本番環境では複数サーバーを立てるケースがほとんどで、共有ファイルシステムを利用することもできるが、本番で利用することはほぼない。|
|memory_store|session情報をサーバー上のメモリ上に保存する。本番環境では複数サーバーを立てるケースがほとんどなので本番で利用することはほぼない。|

# 参考
- https://medium.com/ruby-daily/a-devise-jwt-tutorial-for-authenticating-users-in-ruby-on-rails-ca214898318e
- https://qiita.com/mr-hisa-child/items/5ed2ae2fe4c86d4bb5c7