【Scala×Play Framework】Action Refinerを利用した認証機能

# はじめに

この記事では
Play Frameworkにおいて、`Action Refiner` を利用した認証機能の実装方法についてお伝えします。

#### 大まかな手順

1. `Action Refiner`を継承したアクションオブジェクトを作成する

2. アクションオブジェクト内に`refine`メソッドを定義する
  2-1. リクエストの属性に値を格納する

3. `Action`とアクションオブジェクトをチェーンで繋ぎ、コントローラーで使用する

## Action Refiner とは
- リクエストをすぐにインターセプトして結果(`Result`型の値)を返す
- 新しいリクエスト(`Request`型の値)で処理を続行する

上記のどちらの処理を行うかを判別して、実行してくれます。

この特性を利用して、**認証機能**を実装することが出来ます。

定義元：https://github.com/playframework/playframework/blob/8f081f6cf77afcee9e2680572fee566ea529ea3b/core/play/src/main/scala/play/api/mvc/Action.scala#L514

# 実装手順
今回の実装例としては、
ユーザーのログイン認証機能について実装していきます。
## 1.`Action Refiner`を継承したアクションオブジェクトを作成する
`extends`で継承することができます。

▼実装コード
```scala
object UserAction extends ActionRefiner[Request, Request] {}
```


## 2.アクションオブジェクト内に`refine`メソッドを定義する
`Action Refiner`トレイトに定義されている`refine`メソッドを利用します。

`refine`メソッドは抽象メソッドの為、自分で定義して利用することで、`Action Refiner`の恩恵を受けることができます。
※ 戻り値が`Future[Either[Result, P[A]]]`になるように定義する。

定義元：https://github.com/playframework/playframework/blob/8f081f6cf77afcee9e2680572fee566ea529ea3b/core/play/src/main/scala/play/api/mvc/Action.scala#L522

▼実装コード
```scala
object UserAction extends ActionRefiner[Request, Request] {
  def refine[A](request: Request[A]): Future[Either[Result, P[A]]] = Future.successful {
    // ログインしている　　　　　=> Right(newRequest)
    // ログインしていない　　=> Left(Results.Redirect("/login_form"))
    request.session.get("userId")
    .map(userId => {
      for {
        // userを取得する
        user <- UserRepository.findById(userId)
      } yield {
        // 取得したuserをリクエストの属性に格納する(後述で説明します)
        val newRequest: Request[A] = request.addAttr(UserAttr.User, user)
        Right(newRequest)
      }
    })
    .getOrElse(Future.successful(Left(Results.Redirect("/login_form"))))
  }
}
```

### 2-1.リクエストの属性に値を格納する
参考：https://www.playframework.com/documentation/2.8.x/Highlights26#Request-attributes

リクエストには値を書き込むことができます。
以下の手順で実装します。

#### 2-1-1.属性KEYを定義・作成する
```scala
// リクエストの属性を格納する場所(キー)を作る
object UserAttrs {
  val User: TypedKey[User] = TypedKey.apply[User]("user")
}
```

`TypedKey.apply[格納する型]` => `TypedKey`を作成する事ができる。

※ `TypedKey`の名前は引数に渡した文字列となる。（上のコードで言うと`"user"`）


定義元：https://github.com/playframework/playframework/blob/20bb90532fe30cf933daa544dd61e301e7dacbe2/core/play/src/main/scala/play/api/libs/typedmap/TypedKey.scala#L63 


#### 2-1-2.リクエストに属性KEYと値のセットを書き込む（リクエストに属性を付与する）
```scala
// リクエストのTypedMapに属性KEYと値のセットを格納する
// 取得したuserをリクエストの属性に格納する
val newRequest: Request[A] = request.addAttr(UserAttr.User, user)
```

定義元：https://www.playframework.com/documentation/2.8.4/api/scala/play/api/mvc/Request.html#addAttr[B](key:play.api.libs.typedmap.TypedKey[B],value:B):play.api.mvc.Request[A]


`TypedMap` => 「TypedKey」と「値」を格納する不変のマップ。

例）
```scala
// 型
TypedMap(TypedKey[String] -> String)

// キーのタイプ（[]の部分）は、属性のタイプを示す
キーの型：  TypedKey[String]
値の型：  　　　String
```


#### 2-1-3.コントローラーでリクエストの属性値を取得する
```scala
//　属性値を取得する
newRequest.attrs(UserAttrs.User)
```

**2-1-2.** で書き込んだ値を取得する方法です。(後述のコントローラー側で使用します)


定義元：https://www.playframework.com/documentation/2.8.4/api/scala/play/api/mvc/Request.html#attrs:play.api.libs.typedmap.TypedMap



## 3.`Action`とアクションオブジェクトをチェーンで繋ぎ、コントローラーで使用する
参考：https://www.playframework.com/documentation/ja/2.3.x/ScalaActionsComposition#%E3%81%B2%E3%81%A8%E3%81%BE%E3%81%A8%E3%82%81%E3%81%AB%E3%81%99%E3%82%8B

`andThen` を使ってアクション関数をつなぎ、１つのアクションを作成することができます。

▼実装コード
```scala
def view = (Action andThen UserAction) { request =>
  val user: User = request.attrs(UserAttrs.User)
    Ok("User " + user.name)
  }
```


### 『注意するポイント』
#### 先頭のみ`Action Builder`で始まるアクションにしましょう。
※ 上記の実装コードで言うと、`Action`を指します。


`Action Builder`には
- 関数合成のためのcompose処理 (これは他の種別にもある)
- 非同期処理に必要なコンテクスト管理
- リクエストをパースする処理

などが含まれており、

チェーンする後続定義として、
- 非同期処理に必要なコンテクスト管理
- リクエストをパースする処理

の定義機能を含むのは冗長であるからです。



そのような理由から、
`Action Builder`定義は、基本的には最初の1個目であり、
`andThen`で後に使うようには設計されていません。


## まとめ
`Action Refiner`の使い方について紹介しました。
このように、`Action Refiner`を使用することで、認証機能を実装することができます。

`Action Refiner`以外にも、
- <a href='https://github.com/playframework/playframework/blob/8f081f6cf77afcee9e2680572fee566ea529ea3b/core/play/src/main/scala/play/api/mvc/Action.scala#L533'>ActionTransformer</a>
- <a href='https://github.com/playframework/playframework/blob/8f081f6cf77afcee9e2680572fee566ea529ea3b/core/play/src/main/scala/play/api/mvc/Action.scala#L552'>ActionFilter</a>

というトレイトもありますので、ぜひ調べて使用してみて下さい。