C#
OAuth
OpenID
openid_connect
Authlete

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

はじめに

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 社の創業者です。