記事のゴール
Firebase Authenticationを使って認証するBear.Sunday用のモジュールを実装したときに得た知見をまとめる
JWT認証がよくわからない
ちゃんとRFC7519があった
JWTの基礎的なところはライブラリがすでに存在していると思うのでそちらを利用するとして、
どのClaimに何を入れるべきなのか、結構ライブラリによってもズレていたので覚え書き。
ユーザ識別子はどこに格納するべきか
sub
が一般的?
4.1.2. "sub" (Subject) Claim
The "sub" (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique.
-----
「サブ」(サブジェクト)クレームは、JWTの主題。JWTのクレームは通常、ステートメントです。主題について。サブジェクト値は、発行者のコンテキストでローカルに一意であるか、グローバルに一意である。
たまにaud
を使っているライブラリもあったが、aud
はJWTを処理するアプリケーションの識別子だと思われる。
参考:
https://tools.ietf.org/html/rfc7519
https://www.iana.org/assignments/jwt/jwt.xhtml
デバッグ
JWTのデバッグをするならhttps://jwt.io/が便利
IDaas?
Identity as a Serviceの略称で、アイデンティティ(Identity)の管理をSaaSやIaaSなどと同じくクラウドにて管理するサービスのこと
簡単に言うと、
「アプリケーションで必要とする認証まわりの機能(ユーザー管理・権限管理など)を提供してくれる」
クラウドサービス。
JWTはどう送る?
決まっていない?RFC7519には以下のように記載があるが、それに限らない。
JSON Web Token (JWT) is a compact claims representation format intended for space constrained environments such as HTTP Authorization headers and URI query parameters.
-----
JSON Web Token(JWT)は、HTTP AuthorizationヘッダーやURIクエリパラメーターなど、スペースに制約のある環境向けのコンパクトなクレーム表現形式です。
ライブラリによってはAuthorizationヘッダに入れてるものもあれば、Cookieに入れているもの、クエリストリングに入れるもの、それらすべて許容するものなど様々。
体感だがRFC6750に沿う形でAuthorization: Bearer
を使っているライブラリが多かったように思う。
参考:
https://tools.ietf.org/html/rfc6749
https://tools.ietf.org/html/rfc6750
トークンを利用した認証・認可 API を実装するとき Authorization: Bearer ヘッダを使っていいのか調べた
エラーレスポンスはどういう形と内容にするべき?
RFC6750に準拠するのであれば、レスポンスに「WWW-Authenticate」ヘッダーを含めなければならない。
参考:
https://tools.ietf.org/html/rfc6750
トークンを利用した認証・認可 API を実装するとき Authorization: Bearer ヘッダを使っていいのか調べた
テスト
ベストプラクティス
実装ではなく、インターフェイスをテストする
フェイククラスを好む。スタブはOK。モックには批判的な意見もあり複雑なものを避ける。
tl;dw: Stop mocking, start testing
・Share mocks among test modules.
・Maybe you don’t need a mock: if an object is cheap, then don’t mock it.
・If you need a mock, have exactly one well-tested mock.
-----
・テスト間でモックを共有しましょう。(N個のモックオブジェクトの修正を必要としないように)
・オブジェクトがチープならモック化するのをやめましょう。必要ありません。
・モックが必要な場合は、十分にテストされたモックを1つだけ用意してください。
テストダブル
テストダブル (Test Double) とは、ソフトウェアテストでテスト対象が依存しているコンポーネントを置き換える代用品のことです。テストダブルは以下のパターンがあります。
スタブ (テスト対象にダミーデータを与える)
モック (下位モジュールを正しく利用しているかを実際のモジュールを用いないで検証)
フェイク (実際のオブジェクトに近い働きをするがより単純な実装を使う)
スパイ (実際のオブジェクトに対する入出力の記録を検証)
まだよくわからない…上記の4つに加えてダミーオブジェクトもあるらしい。
ダミーオブジェクト(厳密にはテストダブルではないらしい)
Dummy objects are objects that the System Under Test (SUT) depends on, but they are actually never used. A dummy object can be an argument passed to another object, or it can be returned by a second object and then passed to a third object. The point is, our tested class never actually use these dummy objects. At the same time, the object must resemble a real object; otherwise, the receiver may refuse it.
-----
ダミーオブジェクトは、テスト対象システム(SUT)が依存するオブジェクトですが、実際には使用されません。ダミーオブジェクトは、別のオブジェクトに渡される引数にすることも、2番目のオブジェクトから返されて3番目のオブジェクトに渡すこともできます。重要なのは、テストされたクラスが実際にこれらのダミーオブジェクトを使用しないことです。同時に、オブジェクトは実際のオブジェクトに似ている必要があります。そうしないと、受信者が拒否する場合があります。
class CarController
{
public function getReadyToGo(Electronics $electronics, Lights $lights)
{
$electronics->turnOn($lights);
return true;
}
}
class CarControllerTest extends TestCase
{
public function testItCanGetReadyTheCar()
{
$SUT = new CarController();
$electronics = new Electronics();
$dummyLights = $this->createMock(Lights::class);
$this->assertTrue($SUT->getReadyToGo($electronics, $dummyLights));
}
}
スタブ
実際のオブジェクトを置き換えて、設定した何らかの値を (オプションで) 返すようなテストダブルのことをスタブといいます。スタブを使うと、SUT が依存している実際のコンポーネントを置き換え、SUTの入力を間接的にコントロールできるようにすることができます。 これにより、SUTが他の何者も実行しないことを強制させることができます。
class CarController
{
public function goForward(Electronics $electronics, StatusPanel $statusPanel = null)
{
$statusPanel = $statusPanel ?? new StatusPanel();
if ($statusPanel->engineIsRunning() && $statusPanel->thereIsEnoughFuel()) {
$electronics->accelerate();
}
}
}
class CarControllerTest extends TestCase
{
public function testItCanAccelerate()
{
$SUT = new CarController();
$electronics = new Electronics();
$stubStatusPanel = $this->createMock(StatusPanel::class);
$stubStatusPanel->method('engineIsRunning')->willReturn(true);
$stubStatusPanel->method('thereIsEnoughFuel')->willReturn(true);
$SUT->goForward($electronics, $stubStatusPanel);
}
}
ここではスタブのメソッドが何回呼ばれる、などはテストしない。それはモックの役割になる。
モック
実際のオブジェクトを置き換えて、(メソッドがコールされたことなどの) 期待する内容を検証するテストダブルのことをモックといいます。モックオブジェクトはSUTの間接的な出力の内容を検証するために使用する観測地点です。一般的に、モックオブジェクトにはテスト用スタブの機能も含まれます。まだテストに失敗していない場合に、間接的な出力の検証用の値をSUTに返す機能です。したがって、モックオブジェクトとはテスト用スタブにアサーション機能を足しただけのものとは異なります。それ以外の用途にも使うことができます。
class CarController
{
public function goForward(Electronics $electronics, StatusPanel $statusPanel = null)
{
$statusPanel = $statusPanel ?? new StatusPanel();
if ($statusPanel->engineIsRunning() && $statusPanel->thereIsEnoughFuel()) {
$electronics->accelerate();
}
}
}
class CarControllerTest extends TestCase
{
public function testItCanAccelerate()
{
$SUT = new CarController();
$mockElectronics = $this->createMock(Electronics::class);
$mockElectronics->expects($this->once())->method('accelerate');
$stubStatusPanel = $this->createMock(StatusPanel::class);
$stubStatusPanel->method('engineIsRunning')->willReturn(true);
$stubStatusPanel->method('thereIsEnoughFuel')->willReturn(true);
$SUT->goForward($electronics, $stubStatusPanel);
}
}
スタブにアサーションをつけたらモック、と思いがちだけど目的が全く違うってことかな?
スタブはSUTへの入力を固定する、モックはSUTから依存するオブジェクトへの出力を検証する。
スパイ
使うべきタイミングがよくわからなかったので割愛
フェイク
フェイクオブジェクトは実際に動作するよう実装されてはいるが、手抜きがされているので製品版には向かない(InMemoryDatabaseが良い例である)。
いろんな意味に捉えている人がおり、スタブとほぼ同じ意味で使われていることも多い。
BEAR.SundayのFakeディレクトリ以下の実装を見ると、上記のフェイクの意味+スタブの意味+テストダブル以外の意味、があるように思える。
PHP-CS-Fixer
出たエラーの意味がわからないとき
$ php-cs-fixer describe <name>
または