クライアント証明書を使用したAPIの保護

このクイックスタートは、IdentityServerを使用してAPIを保護するための最も基本的なシナリオを示しています。

このシナリオでは、アクセスするAPIとクライアントを定義します。クライアントはIdentityServerでアクセストークンを要求し、アクセストークンを使用してAPIにアクセスします。

APIの定義

スコープは、保護するシステムのリソース(APIなど)を定義します。

このチュートリアルではメモリ内の設定を使用しているので、APIを追加するには、型のオブジェクトを作成し ApiResource て適切なプロパティを設定するだけです。

Config.cs プロジェクトにファイルなどを追加し、次のコードを追加します。:

public static IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("api1", "My API")
    };
}

クライアントの定義

次のステップは、このAPIにアクセスできるクライアントを定義することです。

このシナリオでは、クライアントには対話ユーザーがなく、IdentityServerでいわゆるクライアントシークレットを使用して認証されます。Config.cs ファイルに次のコードを追加します。:

public static IEnumerable<Client> GetClients()
{
    return new List<Client>
    {
        new Client
        {
            ClientId = "client",

            // no interactive user, use the clientid/secret for authentication
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            // secret for authentication
            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },

            // scopes that client has access to
            AllowedScopes = { "api1" }
        }
    };
}

IdentityServerの設定

スコープとクライアント定義を使用するようにIdentityServerを設定するには、ConfigureServices メソッドにコードを追加する必要があります。カバーの下で、関連する店舗とデータをDIシステムに追加することができます。:

public void ConfigureServices(IServiceCollection services)
{
    // configure identity server with in-memory stores, keys, clients and resources
    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients());
}

つまり、サーバーを実行してブラウザをナビゲートすると``http://localhost:5000/.well-known/openid-configuration`` 、いわゆる発見文書が表示されます。これは、クライアントとAPIが必要な構成データをダウンロードするために使用されます。

../_images/1_discovery.png

APIの追加

次に、ソリューションにAPIを追加します。

ASP.NET Core Web APIテンプレートを使用できます。ここでも、Kestrelと起動プロファイルを以前と同じように設定したのと同じ手法を使用してポートを制御することをお勧めします。このチュートリアルでは、APIを実行するように設定していることを前提としています http://localhost:5001

The controller

APIプロジェクトに新しいコントローラを追加する:

[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}

このコントローラーは後で認可要件をテストするために使用されるだけでなく、APIの目でクレームIDを視覚化するために使用されます。

Configuration

最後のステップは、DIと認証ミドルウェアに認証サービスをパイプラインに追加することです。これらは:

  • 受信したトークンを検証して、信頼できる発行元からのものであることを確認する
  • トークンがこのapi(別名スコープ)で使用するのに有効であることを検証する

IdentityServer4.AccessTokenValidation NuGetパッケージをプロジェクトに追加します。

../_images/1_nuget_accesstokenvalidation.png

スタートアップを次のように更新します。:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvcCore()
            .AddAuthorization()
            .AddJsonFormatters();

        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;

                options.ApiName = "api1";
            });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseAuthentication();

        app.UseMvc();
    }
}

AddAuthentication DIに認証サービスを追加``"Bearer"`` し、デフォルトスキームとして設定します。 AddIdentityServerAuthentication 認証サービスで使用するためにIdentityServerアクセス​​トークン検証ハンドラをDIに追加します。 UseAuthentication 認証ミドルウェアをパイプラインに追加するため、ホストへのすべてのコールで認証が自動的に実行されます。

ブラウザーを使用してコントローラー (http://localhost:5001/identity)に移動する場合は、代わりに401ステータスコードを取得する必要があります。つまり、APIには資格情報が必要です。

それで、APIはIdentityServerによって保護されています。

クライアントの作成

最後のステップでは、アクセストークンを要求するクライアントを作成し、このトークンを使用してAPIにアクセスします。そのためには、ソリューションにコンソールプロジェクトを追加してください(see full code here)。

IdentityServerのトークンエンドポイントはOAuth 2.0プロトコルを実装しており、生のHTTPを使用してアクセスできます。しかし、使いやすいAPIでプロトコルのやりとりをカプセル化するIdentityModelというクライアントライブラリがあります。

IdentityModel NuGetパッケージをアプリケーションに追加します。

../_images/1_nuget_identitymodel.png

IdentityModelには、ディスカバリー・エンドポイントで使用するクライアント・ライブラリーが含まれています。この方法で、IdentityServerのベースアドレスを知る必要があります。実際のエンドポイントアドレスは、メタデータから読み取ることができます。:

// discover endpoints from metadata
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
if (disco.IsError)
{
    Console.WriteLine(disco.Error);
    return;
}

次に、 TokenClient クラスを使用してトークンを要求できます。インスタンスを作成するには、トークンエンドポイントのアドレス、クライアントID、および秘密を渡す必要があります。

次に、この RequestClientCredentialsAsync メソッドを使用してAPIのトークンをリクエストできます。:

// request token
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");

if (tokenResponse.IsError)
{
    Console.WriteLine(tokenResponse.Error);
    return;
}

Console.WriteLine(tokenResponse.Json);

注釈

コンソールからのアクセストークンをコピーして jwt.io に貼り付けて、生のトークンを調べます。

最後のステップはAPIを呼び出すことです。

APIにアクセストークンを送信するには、通常、HTTP Authorizationヘッダーを使用します。これは、SetBearerToken 拡張メソッドを使用して行われます。:

// call api
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);

var response = await client.GetAsync("http://localhost:5001/identity");
if (!response.IsSuccessStatusCode)
{
    Console.WriteLine(response.StatusCode);
}
else
{
    var content = await response.Content.ReadAsStringAsync();
    Console.WriteLine(JArray.Parse(content));
}

出力は次のようになります。:

../_images/1_client_screenshot.png

注釈

デフォルトでは、アクセストークンにスコープ、有効期間(nbfおよびexp)、クライアントID(client_id)および発行者名(iss)に関するクレームが含まれます。

さらなる実験

これまでの成功パスに焦点を当てたこのウォークスルー

  • クライアントはトークンを要求できました
  • クライアントはトークンを使用してAPIにアクセスできます

システムをどのように動作させるかを知るためにエラーを引き起こすようになりました。

  • 実行していないときにIdentityServerに接続しようとする(利用できない)
  • 無効なクライアントIDまたはシークレットを使用してトークンを要求しようとする
  • トークン要求中に無効なスコープを要求しようとする
  • APIが実行されていないときに呼び出す(利用できない)
  • トークンをAPIに送信しない
  • トークン内のスコープとは異なるスコープを要求するようにAPIを構成する