最近、OAuth 2.0 のサービスプロバイダ、及びクライアントをフルスクラッチで実装する機会があったので、調べたことをまとめてみました。
OAuth とは Web-APIなどを利用するための認可の手順(プロトコル)を標準化したもので、OAuth 1.0 と OAuth 2.0 の2つのバージョンがあります。
OAuth 2.0 誕生の背景
OAuth 1.0 は2007年に誕生し、Twitter, Google, Yahoo など多くの企業で採用されました。しかし OAuth 1.0 はリクエストパラメータ送信時に署名が必須となるなど仕組みが複雑で、またWebアプリケーション以外のスマートフォンアプリやJavaScriptクライアントでの実装が困難なことから、新しいプロトコルの策定が求められ、OAuth 2.0 が誕生しました。
OAuth 2.0 では様々な利用シーンを想定し、HTTPSを必須にすることによって署名をなくし、トークン取得の手順も簡略化されました。ただし、OAuth 1.0 との後方互換性はありません。今回は現在主流となっている OAuth 2.0 をまとめてみました。
認可のフローは大きく、以下の2つに分かれます。
- Authorization Code Grant Flow (認可コードグラント。Webアプリなど秘密鍵を管理できる場合に利用。)
- Implicit Grant Flow (インプリシットグラント。JavaScript アプリなど秘密鍵を管理できない場合に使用。認可コードを発行せずに直接アクセストークンが取得できます。Authorization Code Grant Flow と比べるとセキュリティが低下します。)
今回は Authorization Code Grant Flow を例に説明致します。
OAuth 2.0 認証の流れ (Authorization Code Grant Flow の場合)
クライアントアプリとは、ユーザ情報などのリソースの提供を受ける側を指しています。OAuthサービスプロバイダとはリソースを持っており、そのリソースをクライアントに提供する側を指しています。例えば Facebook のアカウント使って独自のアプリにログインしたい場合、独自アプリがクライアント、Facebook がOAuth サービスプロバイダとなります。クライアントにリソースの提供を行う場合、OAuth サービスプロバイダ側で必ず事前にユーザの許可を受ける必要があります。
事前準備
OAuth プロバイダーにクライアントを登録します。クライアント情報は以下の項目から成り立っています。
- client_id (クライアントを識別するID)
- client_secret (クライアントシークレット、アクセストークンリクエスト時に使用)
- client_name (クライアント名)
- redirect_uri (認可後にリダイレクトするクライアントアプリ側のURI)
- scope (スコープ。どのリソースにアクセスできるかを定義)
(1) 認可リクエスト
まずOAuthの認証を行うためには、ユーザが「認証」ボタンのクリックなどのアクションを起こす必要があります。アクション発生後、OAuthプロバイダーに以下の情報が送信され、ログイン画面が表示されます。
- client_id (クライアントを識別するために必須)
- redirect_uri (事前に登録している場合には不要)
- reponse_type (リクエストのレスポンスの種類。ひとつしかサポートしない場合には不要。認可コード : code/アクセストークン : token。)
- scope (スコープが固定の場合には不要)
- state (任意)
送信方式 : GET
(2) 自動リダイレクト
ログインが成功した場合、OAuthプロバイダーで用意した認可リクエストの管理画面に自動遷移します。
この画面でクライアントアプリから、OAuthプロバイダーのリソースへのアクセスを許可するかを、ユーザに確認します。
(3) 認可コード
OAuth プロバイダーで認可が無事完了すると、認可コードが発行され、予め指定されたURLに認可コードが付加された形で、リダイレクトされます。
認可時にエラーを検出した場合、内容に応じて以下のエラーコードを出力します。
エラーコード | 説明 |
invalid_request | リクエストに必要なパラメーターがない場合、もしくは不正な場合 |
invalid_client | コンシューマーキーが不正の場合 |
unauthorized_client | クライアントがリクエストの際に指定したresponse_typeが許可されていない場合 |
access_denied | エンドユーザーまたは認可サーバーがリクエストを拒否した場合 |
unsupported_response_type | 認可サーバーがリクエストの際に指定したresponse_typeによる認可コード取得をサポートしていない場合 |
invalid_scope | リクエストしたスコープが不正の場合 |
(4) アクセストークンのリクエスト
次に認可コードを元にアクセストークンのリクエストを行います。OAuth プロバイダーのアクセストークン発行APIに対して以下のパラメータを送信し、アクセストークンのリクエストを行います。
- client_id
- client_secret
- grant_type (リクエストにしようするパラメータの種類。認可コード : authorization_code/リフレッシュトークン : refresh_token)
- code
- redirect_uri
- refresh_token (認可タイプが refresh_token、アクセストークンの再発行時に使用)
送信方式 : POST
ここで注意したいのは client_secret です。これは秘密鍵であるため、JavaScript などクライアントサイド側では保持できません。client_secret はサーバサイドにて管理し、OAuth サービスプロバイダとの通信もサーバサイドにて行う必要があります。client_secret を管理できない場合には Implicit Grant Flow を使用します。
(5)アクセストークンの発行
アクセストークンが発行されると、以下のレスポンスが返ってきます。このアクセストークンをクライアントアプリ側に保管します。アクセストークンはユーザの特定と、認証を兼ねた重要な情報となります。管理には十分な注意が必要です。またアクセストークンには通常、有効期限があります。有効期限が切れた場合にはリフレッシュトークンにて再度、アクセストークンのリクエストを行います。
- access_token (アクセストークン)
- expires_in (アクセストークンの有効期限)
- refresh_token (リフレッシュトークン)
レスポンス形式 : JSON
アクセストークン発行時に、エラーを検出した場合、内容に応じて以下のエラーコードを出力します。
エラーコード | 説明 |
invalid_request | リクエストに必要なパラメーターがない場合、もしくは不正な場合 |
invalid_client | クライアントIDもしくはクライアントシークレットが正しくなく認証できない場合 |
invalid_grant | リクエストに含まれていた認可コード(またはリフレッシュトークン)が期限切れ等、正しくない場合 |
unauthorized_client | クライアントが当該のグラントタイプを利用する様に認可されていない場合 |
unsupported_grant_type | grant_typeにauthorization_codeやrefresh_token以外が指定されていた場合 |
server_error | その他、何らかのエラーが発生した場合 |
OAuth 対応APIの利用
OAuth に対応したAPIを利用するには通常のパラメータに加えて、必ずアクセストークンを付加する必要があります。OAuth プロバイダー側では、このアクセストークンの有効期限などの妥当性を確認し、問題がなければ、アクセストークンをユーザIDに変換し、各種処理を行い、実行結果を返します。
送信方式 : POST もしくは HTTPヘッダの Authorization にアクセストークンを設定
アクセストークンの設定例
Authorization : Bearer xxxxxxxxxxx
レスポンス形式 : JSON
テーブル定義
OAuth 2.0 に必要なテーブルには、クライアント管理テーブル、認可コード管理テーブル、アクセストークン管理テーブル、リフレッシュトークン管理テーブルなどがあります。それぞれシステムの仕様に応じて定義する必要がありますが、一般的には以下のようになります。
現時点は OAuth 2.0 が主流となっておりますが、最近では OAuth 2.0 をベースにした OpenID Connect の採用の動きも出てきているようです。
参考文献
The OAuth 2.0 Authorization Framework
https://openid-foundation-japan.github.io/rfc6749.ja.html
OAuth 2.0 のサービスプロバイダ、及びクライアントをスクラッチで実装しようとしているのですが、参考までにどれくらいの工数がかかったのかを教えてくださいますでしょうか?
また、実装にはどのプログラム言語を使ったのかも、合わせて教えて頂けると助かります。
サービスプロバイダ、クライアントともに実装した言語は C# (ASP.NET) で、調査や設計に1人月、実装/テスト/仕様の微調整に1人月ぐらいかかりました。(OAuth 対応 WEB-API の開発工数は含んでいません。)
OAuth の知識がほとんどないところから始めたため、いろいろと試行錯誤が必要だったのですが、先に OAuth 2.0 の仕様が理解できていれば、もう少し工数も少なくできたかもしれません。
基本的にはこの仕様に基づいて実装いたしました。
http://openid-foundation-japan.github.io/rfc6749.ja.html
さっそくご回答頂き、ありがとうございます。
参考にさせて頂きます。
恐れながら、もう一点ご質問がございます。
今回、2つの社内システム間をAPIでやりとりするバッチ処理に
ついて、OAuth2.0の対応を入れようと思っております。
Webアプリ等の場合は、ユーザに対し、リソースのアクセスを承認する
手続きがあるかと思いますが、バッチ処理のためそういった手続きは
省きたいです。
実装のやり方次第で、上記のようなことは可能なのでしょうか?
可能かと思います。今回の記事では触れておりませんでしたが、実は私もサーバ間のバッチ処理用に後から似たような機能を追加いたしました。
OAuth 2.0 にはサービスプロバイダから認証された1つのクライアントアカウントで、複数のユーザアカウントの管理が行える 「Resource Owner Password Credentials」 と呼ばれる Grand Type が存在します。この Grand Type を実装することで実現可能かと思います。(既存の Basic 認証を置き換える目的として用意されたようです。)
ただし非常に強力な権限がクライアントに付与されますので、信頼性の高いクライアントに対してのみ許可する必要があるかと思います。
OAuth 2.0 「4.3. リソースオーナーパスワードクレデンシャルグラント」
https://openid-foundation-japan.github.io/rfc6749.ja.html#grant-password
情報連携頂き、ありがとうございます。
私の方でも「Resource Owner Password Credentials」にて実装しようかと思います。
ありがとうございました。
度々すみません。
再度伺いたいことがあり、ご存知でしたらアドバイスお願い致します。
「Resource Owner Password Credentials」の場合、クライアントが認可サーバにクレデンシャル情報を送り、
アクセストークンが返りますが、クライアントだけでなく、リソースサーバの方でもトークンが何かを認識させる必要があると思っています。
リソースサーバへのトークン情報の連携というのは、どのようにして実装すればいいのでしょうか?
ご返事遅くなりました。もしかしたら社内サーバ間の連携には、「Client Credentials Grant」の方が向いているのかもしれません。「Resource Owner Password Credentials」はユーザのクレデンシャル情報をそのまま認可サーバに送り、アクセストークンを発行する仕組みですが、サーバ間の連携の場合は、個別のユーザの認可ではなく、クライアントに対する認可の意味合いが大きい為、「Client Credentials Grant」のほうが向いているように思います。
クライアントに対してアクセストークンが発行されると、そのアクセストークンを使用して全てのユーザのリソースにアクセスできるようになります。バッチ処理などにはこちらの方が向いているかと思います。