Help us understand the problem. What is going on with this article?

C# による OAuth 2.0 と OpenID Connect の実装 (Authlete)

More than 3 years have passed since last update.

はじめに

OAuth 2.0OpenID Connect をサポートする認可サーバー兼 OpenID プロバイダーの C# 実装である『csharp-oauth-server』、及び、OpenID Connect Core 1.0 の「5.3. UserInfo Endpoint」で定義されているユーザー情報エンドポイントの実装を含むリソースサーバーの C# 実装である『csharp-resource-server』を紹介します。

English version: OAuth 2.0 and OpenID Connect implementation in C# (Authlete)

1. アーキテクチャー

csharp-oauth-server と csharp-resource-server は『authlete-csharp』ライブラリ(NuGet パッケージ 『Authlete.Authlete』)を用いて書かれています。authlete-csharp ライブラリは Authlete(オースリート)というクラウドサービスが提供する Web API と通信するためのライブラリです。

Authlete は OAuth 2.0 と OpenID Connect を実装するのに必要な機能を Web API として提供するクラウドサービスです。特長的なのは、Authlete 自身は認可サーバーでも OpenID プロバイダーでもなく、あくまで認可サーバーや OpenID プロバイダーの後ろで動くバックエンドサービスだということです。

relationship-between-frontend-server-and-authlete.png

このため、OAuth 2.0 と OpenID Connect のほとんどの部分は Authlete 内で実装されてはいるものの、それでもフロントエンドとして csharp-oauth-server のようなものが必要となります。 java-oauth-serverspring-oauth-server もそのようなフロントエンド認可サーバーの実装例です。

Authlete のアーキテクチャーの利点については『OAuth 2.0 / OIDC 実装の新アーキテクチャー』をご参照ください。

2. 前準備

2.1. API キーと API シークレット

csharp-oauth-server と csharp-resource-server を動かすには、Authlete サーバーと通信する際に要求される API キーと API シークレットが必要になります。Authlete のサインアップページでアカウント登録をすると、自動的に API キーと API シークレットの組みが一つ生成されるので、API キーと
API シークレットの取得に必要な作業はアカウント登録作業のみです。詳細な手順は『Spring + OAuth 2.0 + OpenID Connect』の「3.1. Authlete API にアクセスするための API キー」をご参照ください。

2.2. クライアント ID

認可サーバー csharp-oauth-server に対して認可リクエストを投げる際、OAuth 2.0 の仕様(RFC 6749)により、クライアント ID が必要になります。Authlete にアカウント登録をすると、自動的にクライアントアプリケーションが一つ生成されるので、クライアント ID 取得についても、必要な作業はアカウント登録作業のみです。詳細な手順は『Spring + OAuth 2.0 + OpenID Connect』の「3.2. クライアント ID」をご参照ください。

3. 実行

3.1. 認可サーバー起動

$ git clone https://github.com/authlete/csharp-oauth-server
$ cd csharp-oauth-server/AuthorizationServer
$ vi authlete.properties
$ dotnet run

上記の手順により、localhost:5000 で認可サーバーが起動します。

authlete.propertiescsharp-oauth-server が参照する設定ファイルです。このファイルに「2.1. API キーと API シークレット」で取得した API キーと API シークレットを設定してください。

3.2. リソースサーバー起動

$ git clone https://github.com/authlete/csharp-resource-server
$ cd csharp-resource-server/ResourceServer
$ vi authlete.properties
$ dotnet run

上記の手順により、localhost:5001 でリソースサーバーが起動します。

authlete.propertiescsharp-resource-server が参照する設定ファイルです。このファイルに「2.1. API キーと API シークレット」で取得した API キーと API シークレットを設定してください。

3.3. 認可リクエスト

csharp-oauth-server は /api/authorization で「認可エンドポイント」を提供します。インプリシットフローresponse_type=token)で認可リクエストをこのエンドポイントに投げてみましょう。下記の URL を Web ブラウザのアドレスバーに入力してください。{クライアントID} の箇所は「2.2. クライアント ID」で取得したクライアント ID で置き換えてください。

http://localhost:5000/api/authorization?response_type=token&client_id={クライアントID}

次のような認可画面が表示されます。

認可画面
charp-oauth-server_authorization-page.png

Login ID フィールドと Password フィールドに、それぞれ john, john と入力し、Authorize ボタンを押してください。

※: Login ID フィールドと Password フィールドが表示されない場合は認可リクエストの URL の末尾に &prompt=login を追加してください。

すると、Web ブラウザは(クライアントアプリケーションの設定をあなたが変更していなければ) https://api.authlete.com/api/mock/redirection/{サービスAPIキー} へとリダイレクトされ、認可リクエストの結果が表示されます。

認可リクエストの結果
authorization-request-result.png

表示されている表の access_token の隣にある値が、今回の認可リクエストの結果として発行されたアクセストークンです。

3.4. API コール

認可リクエストの結果、アクセストークンが取得できたので、リソースサーバーに対して API コールをしてみましょう。

csharp-resource-server/api/time で簡単な API を提供しています。この API に、まず、アクセストークンをつけないでアクセスしてみます。リソースサーバーが返す HTTP ヘッダーの値を見るため、-v オプションをつけて curl コマンドを実行します。

$ curl -v http://localhost:5001/api/time

すると、HTTP ステータス 400 Bad Request が返ってきます。

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 5001 (#0)
> GET /api/time HTTP/1.1
> Host: localhost:5001
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 400 Bad Request
< Date: Mon, 08 Jan 2018 13:08:24 GMT
< Server: Kestrel
< Content-Length: 0
< Cache-Control: no-store
< Pragma: no-cache
< WWW-Authenticate: Bearer error="invalid_token",error_description="[A057301] The request does not contain a valid access token.",error_uri="https://www.authlete.com/documents/apis/result_codes#A057301"
< 
* Connection #0 to host localhost left intact

RFC 6750(The OAuth 2.0 Authorization Framework: Bearer Token Usage)の仕様により、エラーメッセージはレスポンスボディーではなく、WWW-Authenticate ヘッダーに書かれていることに注意してください。

次に、「3.3. 認可リクエスト」で取得したアクセストークンをつけて /api/time API を呼んでみます。なお、/api/time API は、RFC 6750 で定義されている 3 つの方法を全てサポートしているので、次のいずれの方法でもアクセストークンを受け付けることができます。

RFC 6750, 2.1. Authorization Request Header Field

$ curl -v http://localhost:5001/api/time \
       -H 'Authorization: Bearer pZ-NP9xuMOTf5Q_jafvXaWfq3czJp_-EHgDtF-ru90g'

RFC 6750, 2.2. Form-Encoded Body Parameter

$ curl -v http://localhost:5001/api/time \
       -d access_token=pZ-NP9xuMOTf5Q_jafvXaWfq3czJp_-EHgDtF-ru90g

RFC 6750, 2.3. URI Query Parameter

curl -v http://localhost:5001/api/time\?access_token=pZ-NP9xuMOTf5Q_jafvXaWfq3czJp_-EHgDtF-ru90g

さて、一番目の方法で API コールを実行すると、結果は次のようになります。

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 5001 (#0)
> GET /api/time HTTP/1.1
> Host: localhost:5001
> User-Agent: curl/7.54.0
> Accept: */*
> Authorization: Bearer pZ-NP9xuMOTf5Q_jafvXaWfq3czJp_-EHgDtF-ru90g
> 
< HTTP/1.1 200 OK
< Date: Mon, 08 Jan 2018 13:27:56 GMT
< Content-Type: application/json; charset=utf-8
< Server: Kestrel
< Content-Length: 151
< Cache-Control: no-store
< Pragma: no-cache
< 
{
  "year":        2018,
  "month":       1,
  "day":         8,
  "hour":        13,
  "minute":      27,
  "second":      56,
  "millisecond": 341
}
* Connection #0 to host localhost left intact

HTTP ステータス 200 OK で JSON が返ってきます。

おわりに

認可サーバーや OpenID プロバイダーの実装に利用可能な C# ライブラリはそれほど多くありませんが、authlete-csharp ライブラリ(Authlete.Authlete NuGet パッケージ)の登場により、選択肢が一つ増えました。

authlete-csharp ライブラリは他のライブラリと異なり、バックエンドサービスとして Authlete を利用します。(1)OAuth 2.0 と OpenID Connect
のロジックは Authlete 側で実装され、(2)アクセストークン等の関連データも Authlete 側のデータベースに保存され、(3)サーバーやクライアントの設定も GUI(Service Owner Console & Developer Console)でおこなえるため(他のライブラリのように設定自体をいちいちコーディングしなくてもよい)、フロントエンドの認可サーバーの実装はかなり簡単になります。

例えば、OpenID Connect Discovery 1.0 で定義されている設定エンドポイントの実装(ConfigurationController.cs)は実質的に下記のコードだけで終わってしまいます。

using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Authlete.Api;
using Authlete.Handler;

namespace AuthorizationServer.Controllers
{
    [Route(".well-known/openid-configuration")]
    public class ConfigurationController : BaseController
    {
        public ConfigurationController(IAuthleteApi api) : base(api)
        {
        }

        [HttpGet]
        public async Task<HttpResponseMessage> Get()
        {
            // Call Authlete's /api/service/configuration API.
            return await new ConfigurationRequestHandler(API).Handle();
        }
    }
}

上記の利点は C# に限った話ではありません。フロントサーバーを Java で実装しても(例:java-oauth-server)、同様の利点を得られます。ですので、どのプログラミング言語を使うかに関わらず、認可サーバー・OpenID プロバイダーの実装をおこなう際は Authlete の利用を是非ご検討ください!

注:この記事の筆者は Authlete 社の創業者です。

TakahikoKawasaki
株式会社 Authlete の共同創業者。プログラマー兼代表取締役社長。
https://www.authlete.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away