綺麗に死ぬITエンジニア

Laravel Passportで自身のJavaScriptからAPIを利用する方法

2017-11-02

Laravel Passportは、Laravel上にOAuth2によるAPI認証を簡単に実装できるライブラリです。

この、Laravel Passportを利用すると、自身のWebアプリケーション上に実装したAPIを、外部へと簡単にセキュアに公開できます。

しかしながら、自身のWebアプリケーションの画面上でも同一のAPI利用したい場合があるでしょう。そんな場合もいちいち認証処理を経由するのは面倒なので、Laravel Passportでは自身のJavaScriptからAPIを利用する方法が用意されています。今回はそれについて紹介します。

今回紹介している方法は、一部公式ドキュメントに方法の記載がありますが、そのとおりにやってもトークンの渡し方に不備があり認証が通らないことがあったので、その点を踏まえ公式ドキュメントよりも詳しく解説していきます。

前提

公式ドキュメント日本語翻訳サイト)を参考に、Laravel Passportのインストールおよび初期設定をしておきましょう。

実装方法

Laravel側で必要なことは、いたって簡単です。webミドルウェアグループにCreateFreshApiTokenミドルウェアを追加するだけです。app/Http/Kernel.phpを開き、以下のように書き加えましょう。

protected $middlewareGroups = [
    'web' => [
        // 他のミドルウェア…
        \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,  // 追加
    ],
    'api' => [
        // 他のミドルウェア…
    ],
];

そして、JavaScript側でX-Requested-Withヘッダーを送るようにしてやれば、設定完了です。

基本的にはこれだけで、Laravelはlaravel_tokenという名前のクッキーを送信し、それによってアクセストークンを明示的に渡さなくてもAPIを利用できるようになります。

この、laravel_tokenというクッキーの名称を変更したい場合は、こちらの記事を参照してください。

公式ドキュメントを見ると、この状態でJavaScriptからAPIリクエストを行うと、自動的にX-CSRF-TOKENヘッダーを送り、そのまま利用できるとの記載がありますが、これは恐らく誤記です。少なくとも、私の環境では。

公式ドキュメントでも、私の環境でも、JavaScriptのHTTPクライアントライブラリには、axiosを利用しています。axiosは、自動的にX-CSRF-TOKENヘッダーではなく、X-XSRF-TOKENヘッダーを送ります。

1字違いですがこれは誤字ではなく、X-CSRF-TOKENヘッダーは暗号化されていないトークン、X-XSRF-TOKENヘッダーは暗号化されたトークンを意味するようです。これが世界的な標準なのかはわかりませんが、少なくともLaravel上ではそのような住み分けがされています。

そして、Laravel Passportでは、X-CSRF-TOKENヘッダーしかサポートしていません。

つまり、axiosを使った場合、そのままでは利用できません。

この問題を解消する方法をいくつか挙げます。お好きな方法で実装しましょう。

metaタグを用意し、リクエストヘッダーを設定する

JavaScriptを実行する画面のBladeテンプレートの<head>内に、次のような<meta>タグを用意してください。

<meta name="csrf-token" content="{{ csrf_token() }}">

これで、<meta>タグのcontentに暗号化されていないトークンがセットされます。

そして、以下のように、HTTPリクエストヘッダーにX-CSRF-TOKENを含むよう設定すれば完了です。

axios.defaults.headers.common = {
	'X-CSRF-TOKEN': document.head.querySelector('meta[name="csrf-token"]').content,
    'X-Requested-With': 'XMLHttpRequest'
};

// または

var request = axios.create({
  headers: {
  	'X-CSRF-TOKEN': document.head.querySelector('meta[name="csrf-token"]').content,
    'X-Requested-With': 'XMLHttpRequest'
  }
});

恐らくこれが最も確実な方法です。

クッキーの自動暗号化を解除し、axiosの自動トークン送信機能を利用する

Laravelでは通常、EncryptCookiesミドルウェアによって、全てのクッキーが自動的に暗号化されます。そのままではX-CSRF-TOKENヘッダーに暗号化されていないトークンをセットするのが不可能なので、トークンのクッキーXSRF-TOKENだけ暗号化されないように設定します。

app/Http/Middleware/EncryptCookies.phpexceptプロパティにXSRF-TOKENを追加し、クッキーに送信されるトークンが暗号化されないようにします。

protected $except = [
    //
    'XSRF-TOKEN'
];

そして、以下のように、トークン送信用のHTTPリクエストヘッダーをX-CSRF-TOKENに設定すれば完了です。

axios.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
axios.defaults.headers.common = {
    'X-Requested-With': 'XMLHttpRequest'
};

// または

var request = axios.create({
  xsrfHeaderName: 'X-CSRF-TOKEN',
  headers: {
    'X-Requested-With': 'XMLHttpRequest'
  }
});

これはaxiosが、xsrfCookieName(デフォルトでXSRF-TOKEN)に設定したクッキーの値を、自動的にxsrfHeaderName(デフォルトでX-XSRF-TOKEN)に設定したヘッダーの値にセットして、リクエストに含めてくれる機能を利用したものです。

本来の使い方とは少しズレますが、少ない記述でクッキーだけで完結するため、こういった実装もありでしょう。

その他の実装方法

クッキーだけできちんと動作する仕組みを細かく作りたい場合は、新たにトークンクッキーを送信するミドルウェアを実装するといいでしょう。

ここでは細かく解説はしませんが、独自にミドルウェアを実装することによって、より細かい制御をすることが可能になります。

ちなみに

そもそもLaravel PassportでX-CSRF-TOKENヘッダーだけではなくX-XSRF-TOKENヘッダーにも対応すればいいのでは!と思い、X-XSRF-TOKENヘッダーに対応させてPull Requestしてみましたが、Taylor Otwell氏曰く、クロスドメインのクッキーポリシー上X-CSRF-TOKENヘッダーは改ざんを防止しますがX-XSRF-TOKENヘッダーは改ざんのリスクを伴うため採用しないとのことでした。

何とか代替案でもいいからX-XSRF-TOKENヘッダーでも動作するように、手軽に利用できるようにして欲しいものですが、こればかりは期待できなさそうですね。諦めてX-CSRF-TOKENヘッダーを使うようにしましょう。

まとめ

今回は、Laravel Passportで自身のJavaScriptからAPIを利用する方法を、JavaScript側の実装方法と併せて紹介しました。

様々なJavaScriptフレームワークの台頭により、SPA等を実装する場面が増え、APIもその分利用頻度が上がってきています。皆さんも是非、より簡単によりセキュアにAPIを実装できるLaravel Passport、利用してみてはいかがでしょうか。