github octcat icon
twitter bird icon
rss icon

認証・認可とOAuth2.0と

2022-05-01

key

Photo Credit: Maria Ziegler

GCPのリソースにアクセスしたり、Google APIにアクセスしたりといった実装をすることが直近多かったのですが、認証認可にはgoogle-auth-library-nodejsを活用して、Google推奨の方式であるADC(Application Default Credentials)を用いて雰囲気で実装していました。

いまいち仕組みがわかっていなくても動くし、最低限セキュアな実装にはできていたのですが脱雰囲気実装をしたく、認証・認可及びOAuth 2.0について改めて調べ直したのでそのメモです。

仕組みを理解し、裏側で何が起こっているのかをイメージした上で実装できるようにすることが今回の目的だったため、個々の技術は一部簡略化して記載しています。

TL;DR

  • 認証・認可では誰がアクセスしてきていて、どのリソースに対してアクセスする権限があるのか確認している。
  • OAuth2.0では、最終的にアクセストークンを鍵として扱っているだけ。

そもそも、認証・認可の違いは?

認証

誰が」アクセスしてきているのかを確認したい。

認証だけが完了した段階では、アプリケーションにアクセスすることはできるが、ユーザーに紐づいて管理されているリソースにはアクセスできない状態。

例えば仮に、Twitterで認証は行えたが認可は行われていない状態ならば、パブリックなツイートを見ることはできるが、鍵アカのツイートや自分のツイート、DMなどは見ることができない状態。

認可

認可ではアクセスしてきた人がどのリソースに対しての権限を持っているのかを確認したい。

通常、認証で本人確認が完了したのちに認可が行われ、ユーザーがリソースにアクセスする権限を持っているかどうかを確認する。

先ほどのTwitterの例だと、認証後に認可を行いリソースに対するアクセス権を確認することで、自分のDMやフォローしている鍵アカのツイートは見ることができるようになるが、フォローできていない鍵アカのツイートや他人のDMは見ることができない。

おまけ:HTTPステータスでの使い分け

HTTPステータスで400系エラーコードは不正なリクエストに対するエラーコードですが、その中でも認証認可は使い分けがされています。

401 Unauthorized | 認証失敗

HTTP 401 Unauthorized  は、有効な認証資格が不足していることによりリクエストが適用されないことを示すクライアントエラーのレスポンスコードです。 401 Unauthorized - HTTP | MDN

403 Forbidden | 認可失敗

HTTP の 403 Forbidden  クライアントエラーレスポンスコードは、サーバーがリクエストを理解したものの、認証が拒否されたことを示します。

このステータスは [401](https://developer.mozilla.org/ja/docs/Web/HTTP/Status/401)に似ていますが、この場合は再認証しても結果は変わりません。アクセスは恒久的に禁止されており、リソースにアクセスする権限が不足しているなど、アプリケーションのロジックに結びついたものです。 403 Forbidden - HTTP | MDN

*MDNでは「認証が拒否されたことを示します。」と書かれているので、認可エラーではなく認証エラーじゃないか。と思ったのですが、続きの文言を解釈しググったところ、認可エラーで403を使うことが一般的であるようなので、403 Forbiddenを認可エラー時に使用すると捉えて問題なさそうです。

Webアプリケーションにおける認証方式

認証認可は別物である。ということがここまででわかりました。

まずは世の中にはどんな認証方式があるのか見て行きたいと思います。

Basic認証

ユーザーIDとパスワードの組みをBase64でエンコードしてAuthorizationヘッダーに詰めてサーバーに送信することで認証を行う方式です。

//Base64エンコード後(実際にリクエストヘッダーに入っている値)
Authorization: Basic c2hnbmtuOm15cGFzc3dvcmQ=

//エンコードする前
Authorization: Basic shgnkn:mypassword

実装が楽なので活用されることが多いようですが、ユーザーIDとパスワードの組みをBase64でエンコードしているだけなので通信が傍受されてしまった場合ユーザーIDとパスワードがダダ漏れです。そのため、活用する際には基本的にSSL /TLS(HTTPS)で通信を暗号化して活用します。

Digest認証

Basic認証と基本的には同じで、AuthorizationヘッダーにユーザーIDとパスワードの組みを詰めてサーバーに送信することで認証を行います。

Basic認証と異なる点としては、Authorizationヘッダーに情報を詰める前に、最初のレスポンスでサーバーから返ってきたランダムな文字列とクライアントで生成したランダムな文字列、ユーザーID、パスワードをハッシュ関数で暗号化してから詰めるので、セキュリティが向上しています。

平文がそのまま送られてしまうBasic認証よりはセキュアになっています。最新のブラウザはSHA-256に対応していますが、古いブラウザだとMD5にしか対応していない場合もあるのでその場合には注意が必要です。(MD5は既に脆弱性が見つかっているため非推奨です。)

Bearer認証

OAuth 2.0 で保護されたリソースにアクセスする際にBearerトークンを用いる方式です。

今回はOAuth 2.0の学習をメインで行ったので、詳細は後ほど記載します。

その他認証方式

今までBasic認証、Digest認証、Bearer認証は触れたことがあったのですが、今回認証方式にはどんなものがあるのだろうと調べた際に思ったよりも色々な種類があって驚きました。

詳細は触れませんが、IANAに記載されていた認証方式を列挙しておきます。

  • HOBA
  • Mutual
  • Negotiate
  • OAuth
  • SCRAM-SHA-1
  • SCRAM-SHA-256
  • vapid

OpenID Connect

認証方式には上で挙げたように様々な方式がありますが、認証の機能を自前で実装するのは大変です。なぜなら、ユーザーIDとパスワードの組みを自前のDBに保存する必要があり、そのDBをハックされたらお終いなので、セキュリティで気を使うべきポイントが一気に増えてしまいます。

そこで、Googleアカウント、Twitterアカウント、Facebookアカウントなど外部サービスのアカウントを用いて認証を外部に委託するといったことがよく行われます。

この、認証を外部に委託する仕組みがOAuth2.0を拡張したOpenID Connectと言われるもので、auth0などのベンダーによって機能が提供されています。

今回OpenID Connectの仕様を深く調べることはできていないので、詳細は省きます。

OAuth 2.0

認証方式にどのようなものがあるのかは1つ前のパートで見てきました。

Bearer認証でOAuth2.0の話が登場しましたが、OAuth 2.0は認証方式を定めたプロトコルではなく、「アクセストークンの要求とその応答」を標準化したプロトコルです。

アクセストークンを発行する認可サーバーを自前で実装するのが大変なため、AuthleteやAuth0といったサービスを利用することが多いかと思います。

OAuth2.0を用いた認可の流れ

超大雑把に書くと、Resource OwnerがClientに対してリクエストしてからアクセストークンを取得して、Resource Serverにアクセスするまでの流れは以下のようになります。6のリソースへのアクセスを行う際には、AuthorizationヘッダーにBearer access-tokenを詰めてリクエストすることでリソースへのアクセスが可能になります。

図から省きましたが、6でResource Serverがアクセストークンを受け取った際に、Authorization Serverに対してアクセストークンが正しいものか確認したり、そもそも認可を行う前にResource OwnerはID/Passwordで、ClientはClient ID/Client Secretを用いて認証を行ったりといったフローがあったりするのですが、それらはOAuth2.0とは別のRFCで定義されたものなので今回の図からは省きました。

oauth

Google ColaboratoryでGoogle Driveをマウントする際の認可

OAuth2.0を用いてアクセストークンを取得し、Resource Serverにアクセスするフローはわかりました。 まだここまでだとOAuth2.0そのものがわかったつもりになっても、実際のプロダクトではどのようなフローで実装されているのかイメージがつきにくいため、Google ColaboratoryでGoogle Driveをマウントしようとした際の挙動を用いて流れを追ってみます。

実行環境

IncognitoモードでColaboratoryを開きます。

その状態で、何らかのコードを実行しようとするとGoogleへのログインを求められるので、ログインします。

この段階で「誰が」リソース(ここではGoogle Colaboratoryの実行環境)にアクセスするのかを確認する、認証を行っています。

login

ログイン後は、Notebookの編集が一度全てリセットされるので、そこから以下コードを実行して行きます。

マウントするためのコード

以下コードでドライブをマウントして、自分のGoogle Drive内にあるリソースに対してColaboratoryからアクセスすることができるようにしてみます。

from google.colab import drive
drive.mount('/content/drive')

Google Drive内にあるリソースを許可するということは、自分のアカウントでリソースに対してアクセスする権限があるのかを確認する「認可」を行ういくことになります。ここでOAuth2.0の仕組みが活躍していきます。

認可フロー

まずはアクセスを許可するか確認のモーダルが出てきます。

ask

次にどのアカウントのリソースに対してのアクセスを許可するか選択します。

今回はアカウントが一つしか写っていませんが、複数アカウントでログインしている状況だと複数アカウントが選択肢として表示されるはずです。

choose-account

最後に、今回Colaboratoryにアクセスを許可するスコープの確認画面が出ます。

ここで、どのリソースに対するアクセス権をColaboratoryに付与するか確認をとっています。

scope

ここまでの操作が完了して初めて、ColaboratoryからGoogle Drive上のリソースにアクセス可能となります。

ColaboratoryからGoogle Driveにアクセスするリクエストが送られる際には、ここで取得したアクセストークンがAuthorizationヘッダーに詰められてリクエストされることになります。