Bearer認証についてまとめる
目次
- 目次
- Bearer認証(トークン認証)とは
- Bearerトークンとは
- BearerトークンとPoPトークンの違い
- Bearer認証のフロー
- Bearer認証のメリットデメリット
- クッキーとローカルストレージの特徴をまとめる
- Goでサーバーサイド側のBearer認証を実装してみた
- 終わり
- 参考記事
Bearer認証(トークン認証)とは
Bearer認証とは、Bearerトークンを用いたHTTPベースの認証のことです。Bearer認証はトークン認証とも呼ばれます。JWT認証もや外部APIへのリクエスト方法はBearer認証に似ているので、JWT認証や外部APIへのリクエスト方法は、Bearer認証の考え方をベースにされているのかなと思います(おそらく)。
Bearerトークンとは
Bearerトークンとは、リソースへのアクセス制御を担うトークン(アクセストークン)の一つの種類です。Bearerトークンは、署名なしトークンとも呼ばれます。BearerトークンはBearer(それを持ってきた存在)にアクセス権限を与える特性を持ちます。Bearerトークンは以下のフォーマットに従います。
b64token = 1*( ALPHA / DIGIT / "-" / "." / "_" / "‾" / "+" / "/" ) *"=" credentials = "Bearer" 1*SP b64token
BearerトークンとPoPトークンの違い
Bearerトークンは電車の切符に例えられます。切符は電車へのアクセスを制限するトークンであるとも言えます。切符の利用権利は「切符を持ってきた人(Bearer)」に付与されます。ゆえにBearerトークンは電車のきっぷに例えられます。拾った切符であっても、持ってきた人(Bearer)に乗車権利が付与されます。
Bearerトークンと対極の存在であるPoPトークンでは、そのトークンを所有している人であることを証明しなければなりません。そのためPoPトークンは国際線飛行機のチケットに例えられます。
Bearer認証のフロー
以下に一般的なBearer認証のフローをまとめます
- ユーザーがログイン情報を入力してサーバーにリクエストを出す。
- ログイン情報が正常だった場合、サーバーはBearerトークンを発行する。その後、レスポンスボディにBearerトークンを格納してレスポンスする。
- クライアントは受け取ったBearerトークンをクッキーまたはローカルストレージに格納する。
- ページへのアクセス時にはAuthorizationリクエストヘッダのvalueにBearerトークンをセットして、サーバーにリクエストを出す。
- サーバーはトークンが有効であるかどうかを確認して、有効ならリクエストされたリソースをレスポンスする。
Authorizationリクエストヘッダとは
HTTP の Authorization リクエストヘッダーは、ユーザーエージェントがサーバーから認証を受けるための証明書を保持するヘッダーです。ふつうは、必ずではないが、サーバーが 401 Unauthorized ステータスと WWW-Authenticate ヘッダーを返した後に使われます。
↓ 構文
Authorization: <type> <credentials>
Bearer認証のメリットデメリット
メリット
- クライアントのローカルストレージ or クッキーにBearerトークンを入れるので、Redisのようなセッションストレージを使わないです。その結果、サービスがスケールしてもサーバーリソースを増やしたりする必要がないです。
- これはRailsでセッションストレージをデフォルト でクッキーにしているのと同じメリットです。
デメリット
- この認証方法の場合、ログアウトしてもローカルストレージに存在するトークンを消すだけです。つまり、ログアウトの前にローカルストレージに存在するトークンが盗まれてしまった場合、ログアウト後に、そのトークンを使って、セッションを再開することができます。そのため、この認証方式ではサーバーサイドでセッションを削除することができないことが分かります。
- Railsのクッキーをデフォルトのセッションストレージにするやり方でも同じデメリットを持ちます。
- 先ほど説明したBearer認証の場合、トークンとユーザー情報をどのように紐付けるかが課題です。JWT認証でJWTトークンにユーザー情報を含めるようにBearerトークンにuser_idなどのユーザー情報を含めるか、または、トークンをステートフルなトークンとして扱い、ユーザー情報とトークン情報を紐付けてデータストレージに保存する方法が考えられます。後者のやり方の場合、サーバー側でセッションを切れるのでセッションの問題も解決しています(ただ、それだともはやJWTじゃなくて従来のセッションクッキーでよくね?という気もしますが)
クッキーとローカルストレージの特徴をまとめる
Bearer認証の際に利用するBearerトークンをクッキーに保存するかのか、ローカルストレージに保存するのかでよく議論が起こります。それぞれ状況に応じて良し悪しあると思うので、まずはその2つの特徴をまとめます。
クッキー
- クッキーとは、(クッキーの名前, クッキーの値)が成すペアと、それに結び付けられた0個以上のメタデータです。
- Set-Cookieヘッダを使うと、サーバーからクライアント(ブラウザ)に対してクッキーを送信できます。クッキーの数だけSet-Cookieヘッダを用意する必要があります。
- クライアントはサーバーからクッキーが送られてきたら、そのクッキーをおそらくクライアント独自の保存領域に保存します。
- クッキーに対して特に制限がかけられていなければ、クライアントはクッキー生成元サーバーにリクエストをするたびに、そのサーバーが生成したクッキーをCookieヘッダを使って送信します。(Cookieヘッダにはクッキー名とクッキーのvalueしか指定せず、クッキーのメタデータは指定しないので注意)
- リクエスト時にCookieを送るか送らないかは、Same Origin Policyを基準にしているわけではなくて、リクエスト先のドメインを元に判断しています(ここごっちゃになりやすいので注意)。クッキー生成時に特にドメインを指定しない場合、クッキー生成元サーバーのドメインにリクエストをするたびに、そのサーバーが生成したクッキーをCookieヘッダを使って送信します。
- Cookieヘッダー自体はHTTPリクエストメッセージに1つだけ含めることができます。そのため、複数のクッキーを送る場合は;で区切ります。
クッキーの重要な属性について
クッキーの重要な属性についてまとめます。
Domain属性
Secure属性
- サーバーは、この属性を使うことでセキュアなチャンネルで通信が行われる場合にのみCookieを送信するように求めることができます。
- HttpOnly属性
- HttpOnly 属性は、クッキーの視野を HTTP 要請に制限します。要するに、HttpOnly属性は、クッキーの利用範囲をHTTPリクエストの時だけに制限するということです。ややこしいのですが、httpOnly属性が指定されたCookieをfetch APIで送信することはできますが、JavaScriptからそのCookieの値にアクセスすることはできないということです(確かそうだった)。
- Same-Site属性
- Same-Site属性は、リクエスト元とリクエスト先が異なるサイトの場合、クッキーを送信するかしないかを制限できる属性です。Sam-Site属性があることで、この属性を指定することで、CSRF攻撃に対して防御をすることができます。
- Domain属性は送信先のドメインに対して制限をかけます。Same-Site属性は送り元のサイトに対して制限をかけます。
- Domain属性はあくまでどのサーバーにリクエストをする際にクッキーを送信するかを指定するだけです。そのため、「ある罠サイトからDomain属性に指定されたドメインにHTTPリクエストをする」とクッキーは送信できてしまいます。Same-Site属性を使うことでリクエスト元のサイトがリクエスト先のサイトと異なる場合、クッキーを送信しないようにできます。
- None: 過去にサイトBから付与されたCookieは、サイトAから無条件にサイトBへ送信されます
- Lax: 過去にサイトBから付与されたCookieは、サイトAから(大雑把にいえば)GETリクエストのみ送信しますが、POSTやPUTなどのリクエストではCookieは送信しません。
- Strict: 過去にサイトBから付与されたCookieは、サイトAからサイトBへは送信されません
- サイトはドメイン名の登録可能なドメイン部分によって決定されます。 つまり、api.example.comとexample.comでは、登録可能なドメイン部分(example.com)が一緒なので、同一サイトとして扱われます。ゆえに、Same-Site属性をStrictにしてもフロントエンドからクッキーを送信することができ、かつCSRFの脅威から防御することができます(同一サイトではないとSame-Site属性をNoneにしないと、クッキーを送信できない。それではCSRF対策したことにならない)
セキュリティを高めるためにも、Cookieのこれらの属性はできるだけ付けた方が良いでしょう。
ローカルストレージ
- localStorage は window プロパティの読み取り専用プロパティで、この Document の origin における Storage オブジェクトにアクセスできます。
- 格納されたデータは、ブラウザーのセッションを跨いで保存されます。
- JavaScriptでブラウザに保存したり、参照したり、削除したりできる
- シンプルなキーバリューストレージで、サーバーに勝手に送信されない。
- アクセスの範囲は同一オリジンに限定されています。 つまり、別オリジンのサイトを見ていたときに、そのクライアントに保存されている別のオリジンのローカルストレージに対して、別オリジンのサイトがJSでアクセスしたりはできません。
- localStorage は sessionStorage によく似ていまが、 localStorage のデータには期限がないのに対し、 sessionStorage のデータはページセッションが終了したとき、すなわちページが閉じられたときにクリアされます。
ローカルストレージはjsでトークンが取られるからだめだとよく聞きますが、罠サイト(https://hoge.com)からapiサーバー(https://api.amazon.com)のローカルストレージへのアクセスはできないです。ローカルストレージへのアクセスは、同一オリジンに限定されるためです。そのため、そんな騒ぐほど危険ではないです。しかし、投稿できる系のサービスで投稿内容をサイニタイズをしていない場合、同一オリジンのjsとして実行されてしまうので、jsからローカルストレージへアクセスできてしまう気もします。そのため、Cookieを使うならCSRF対策、ローカルストレージを使うなら、XSS対策を入念にした方が良いでしょう。
Goでサーバーサイド側のBearer認証を実装してみた
ステートフルなトークンを使って、Bearer認証を実装してみようと思います。この実装のデメリットは、ストレージを用意する必要があるということです(今回はDBを利用した)。メリットとしては、従来のBearer認証よりセキュリティを高めることができるということです。Laravelなどにステートフルなトークン認証があるそうです。
こちらにリポジトリのリンクを貼っておきます。
終わり
若干まだまだわかっていないところもあるので、再度深ぼってみます。
参考記事
様々な認証方式について(Bearer,Basic,Digest,Form,OAuth)
どうしてリスクアセスメントせずに JWT をセッションに使っちゃうわけ? - co3k.org
君はできるかな?Cookie & Same Origin Policyセキュリティークイズ4問 #JavaScript - Qiita
Window: localStorage プロパティ - Web API | MDN
The OAuth 2.0 Authorization Framework
The OAuth 2.0 Authorization Framework: Bearer Token Usage(日本語)
Cookieを扱う|伸び悩んでいる3年目Webエンジニアのための、Python Webアプリケーション自作入門
JWTを認証用トークンに使う時に調べたこと - Carpe Diem
PHP Conference Japan 2021: SPAセキュリティ入門 / 徳丸 浩 - YouTube
トークンを利用した認証・認可 API を実装するとき Authorization: Bearer ヘッダを使っていいのか調べた #API - Qiita