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

# **はじめに**

[OAuth 2.0](https://tools.ietf.org/html/rfc6749) と [OpenID Connect](https://openid.net/connect/) をサポートする認可サーバー兼 OpenID プロバイダーの C# 実装である『[csharp-oauth-server](https://github.com/authlete/csharp-oauth-server)』、及び、[OpenID Connect Core 1.0](http://openid.net/specs/openid-connect-core-1_0.html) の「[5.3. UserInfo Endpoint](http://openid.net/specs/openid-connect-core-1_0.html#UserInfo)」で定義されているユーザー情報エンドポイントの実装を含むリソースサーバーの C# 実装である『[csharp-resource-server](https://github.com/authlete/csharp-resource-server)』を紹介します。

<sup>English version: _“[OAuth 2.0 and OpenID Connect implementation in C# (Authlete)](https://medium.com/@darutk/oauth-2-0-and-openid-connect-implementation-in-c-authlete-8a8f9efc9361)”_</sup>


# **1. アーキテクチャー**

csharp-oauth-server と csharp-resource-server は『[authlete-csharp](https://github.com/authlete/authlete-csharp)』ライブラリ（NuGet パッケージ 『[Authlete.Authlete](https://www.nuget.org/packages/Authlete.Authlete)』）を用いて書かれています。authlete-csharp ライブラリは [Authlete](https://www.authlete.com)（オースリート）というクラウドサービスが提供する [Web API](https://docs.authlete.com/) と通信するためのライブラリです。

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

<img width="895" alt="relationship-between-frontend-server-and-authlete.png" src="https://qiita-image-store.s3.amazonaws.com/0/106044/aefedf8c-9ec8-851a-7bfe-41cb0c567b3f.png">

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

Authlete のアーキテクチャーの利点については『[OAuth 2.0 / OIDC 実装の新アーキテクチャー](https://qiita.com/TakahikoKawasaki/items/b2a4fc39e0c1a1949aab)』をご参照ください。


# **2. 前準備**

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

csharp-oauth-server と csharp-resource-server を動かすには、Authlete サーバーと通信する際に要求される API キーと API シークレットが必要になります。Authlete の[サインアップ](https://so.authlete.com/accounts/signup)ページでアカウント登録をすると、自動的に API キーと API シークレットの組みが一つ生成されるので、API キーと
 API シークレットの取得に必要な作業はアカウント登録作業のみです。詳細な手順は『[Spring + OAuth 2.0 + OpenID Connect](https://qiita.com/TakahikoKawasaki/items/18d5b05dabe246fb239e)』の「[3.1. Authlete API にアクセスするための API キー](https://qiita.com/TakahikoKawasaki/items/18d5b05dabe246fb239e#31-authlete-api-%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE-api-%E3%82%AD%E3%83%BC)」をご参照ください。


### **2.2. クライアント ID**

認可サーバー csharp-oauth-server に対して認可リクエストを投げる際、OAuth 2.0 の仕様（[RFC 6749](https://tools.ietf.org/html/rfc6749)）により、クライアント ID が必要になります。Authlete にアカウント登録をすると、自動的にクライアントアプリケーションが一つ生成されるので、クライアント ID 取得についても、必要な作業はアカウント登録作業のみです。詳細な手順は『[Spring + OAuth 2.0 + OpenID Connect](https://qiita.com/TakahikoKawasaki/items/18d5b05dabe246fb239e)』の「[3.2. クライアント ID](https://qiita.com/TakahikoKawasaki/items/18d5b05dabe246fb239e#32-%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88-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](http://localhost:5000/) で認可サーバーが起動します。

`authlete.properties` は `csharp-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](http://localhost:5001/) でリソースサーバーが起動します。

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


### **3.3. 認可リクエスト**

csharp-oauth-server は `/api/authorization` で「[認可エンドポイント](https://tools.ietf.org/html/rfc6749#section-3.1)」を提供します。[インプリシットフロー](https://tools.ietf.org/html/rfc6749#section-4.2)（`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](https://qiita-image-store.s3.amazonaws.com/0/106044/9243e6dd-3d3b-e6cb-b489-9e25bbb39f96.png)|

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

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

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

|認可リクエストの結果|
|:-:|
|![authorization-request-result.png](https://qiita-image-store.s3.amazonaws.com/0/106044/d3fcc2a7-da86-6a6b-e73e-ef572e87e7af.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](https://tools.ietf.org/html/rfc6750)（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](https://tools.ietf.org/html/rfc6750#section-2.1)

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

RFC 6750, [2.2. Form-Encoded Body Parameter](https://tools.ietf.org/html/rfc6750#section-2.2)

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

RFC 6750, [2.3. URI Query Parameter](https://tools.ietf.org/html/rfc6750#section-2.3)

```
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](https://github.com/authlete/authlete-csharp) ライブラリ（[Authlete.Authlete](https://www.nuget.org/packages/Authlete.Authlete) NuGet パッケージ）の登場により、選択肢が一つ増えました。

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

例えば、[OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) で定義されている設定エンドポイントの実装（[ConfigurationController.cs](https://github.com/authlete/csharp-oauth-server/blob/master/AuthorizationServer/Controllers/ConfigurationController.cs)）は実質的に下記のコードだけで終わってしまいます。

```csharp
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](https://github.com/authlete/java-oauth-server)）、同様の利点を得られます。ですので、どのプログラミング言語を使うかに関わらず、認可サーバー・OpenID プロバイダーの実装をおこなう際は [Authlete](https://www.authlete.com/) の利用を是非ご検討ください！

<sub>注：この記事の筆者は Authlete 社の創業者です。</sub>