OpenIDDictによる認証サーバの設定-パートIII -クライアント資格情報の流れ


This article is part of a series called Setting up an Authorization Server with OpenIddict. The articles in this series will guide you through the process of setting up an OAuth2 + OpenID Connect authorization server on the the ASPNET Core platform using OpenIddict.



  • 第3部:クライアント資格情報の流れ



  • ロビンソン / 認証サーバ


    OpenIDDictで実装された認証サーバ。


  • この部分ではOpenIDDictをプロジェクトに追加し、最初の認証フローを実行しますClient Credentials Flow .

    openiddictパッケージを追加する


    まず、OpenIDDict Nugetパッケージをインストールする必要があります.
    dotnet add package OpenIddict
    dotnet add package OpenIddict.AspNetCore
    dotnet add package OpenIddict.EntityFrameworkCore
    dotnet add package Microsoft.EntityFrameworkCore.InMemory
    
    メインライブラリのほかにもOpenIddict.AspNetCore パッケージは、このパッケージは、ASPNETコアホストのOpenIDDictの統合を可能にします.OpenIddict.EntityFrameworkCore パッケージは、Entity Frameworkコアサポートを有効にします.今のところ、我々は、パッケージを使用してメモリ内の実装で動作しますMicrosoft.EntityFrameworkCore.InMemory .

    セットアップOpeniddict


    OpenIDDictを実行して実行するのに必要最小限のものから始めます.少なくとも1つのOAuth 2.0/OpenID接続フローを有効にする必要があります.を有効にするClient Credentials Flow , これはマシンからマシンへのアプリケーションに適しています.このシリーズの次の部分ではAuthorization Code Flow with PKCE これは、単一ページアプリケーション(SPA)とネイティブ/モバイルアプリケーションの推奨される流れです.
    次の変更を開始しますStartup.cs :
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        services.AddDbContext<DbContext>(options =>
        {
            // Configure the context to use an in-memory store.
            options.UseInMemoryDatabase(nameof(DbContext));
    
            // Register the entity sets needed by OpenIddict.
            options.UseOpenIddict();
        });
    
        services.AddOpenIddict()
    
            // Register the OpenIddict core components.
            .AddCore(options =>
            {
                // Configure OpenIddict to use the EF Core stores/models.
                options.UseEntityFrameworkCore()
                    .UseDbContext<DbContext>();
            })
    
            // Register the OpenIddict server components.
            .AddServer(options =>
            {
                options
                    .AllowClientCredentialsFlow();
    
                options
                    .SetTokenEndpointUris("/connect/token");
    
                // Encryption and signing of tokens
                options
                    .AddEphemeralEncryptionKey()
                    .AddEphemeralSigningKey();
    
                // Register scopes (permissions)
                options.RegisterScopes("api");
    
                // Register the ASP.NET Core host and configure the ASP.NET Core-specific options.
                options
                    .UseAspNetCore()
                    .EnableTokenEndpointPassthrough();            
            });
    }
    
    最初に、DBContextはConfigureServices メソッド.OpenIDDictは、エンティティフレームワークコア、エンティティフレームワーク6およびMongoDBの箱の中からネイティブにサポートし、また、あなた自身の店を提供することができます.
    この例では、Entity Frameworkコアを使用し、メモリ内データベースを使用します.The options.UseOpenIdDict call openOpenDictが必要とするエンティティセットを登録します.
    次に、OpenIDDict自身が登録されます.The AddOpenIddict() call openOpenDictサービスを登録し、OpenIddictBuilder OpenIDDictを構成できるクラス.
    コアコンポーネントは最初に登録されます.OpenIDDictはEntity Frameworkコアを使用するように指示され、前述のDBContextを使用します.
    次に、サーバーコンポーネントが登録され、クライアント資格情報の流れが有効になります.この流れのために、我々はトークン終点を登録する必要があります.このエンドポイントを実装する必要があります.後でこれをします.
    OpenIDDictはトークンを暗号化してサインすることができるので、暗号化のためのキーと署名のために2つのキーを登録する必要があります.この例では、文字キーを使用します.アプリケーションがシャットダウンし、ペイロードが署名されたか、これらのキーを使用して暗号化されたペイロードが自動的に無効になったときに、一時的なキーが自動的に破棄されます.このメソッドは、開発中のみ使用する必要があります.X . 509証明書を使用して、生産に推薦されます.RegisterScopes どのスコープ(権限)がサポートされているかを定義します.この場合、1つのスコープがありますapi , しかし、認証サーバは複数のスコープをサポートできます.
    The UseAspNetCore() コールは、OpenDictのホストとしてaspnetcoreを設定するために使用されます.また、コールEnableTokenEndpointPassthrough それ以外の場合は、将来のトークンエンドポイントへの要求はブロックされます.
    OpenIDDictが正しく設定されているかどうかを確認するには、アプリケーションを起動し、次のように移動します.https://localhost:5001/.well-known/openid-configuration , レスポンスは次のようになります.
    {
      "issuer": "https://localhost:5001/",
      "token_endpoint": "https://localhost:5001/connect/token",
      "jwks_uri": "https://localhost:5001/.well-known/jwks",
      "grant_types_supported": [
        "client_credentials"
      ],
      "scopes_supported": [
        "openid",
        "api"
      ],
      "claims_supported": [
        "aud",
        "exp",
        "iat",
        "iss",
        "sub"
      ],
      "id_token_signing_alg_values_supported": [
        "RS256"
      ],
      "subject_types_supported": [
        "public"
      ],
      "token_endpoint_auth_methods_supported": [
        "client_secret_basic",
        "client_secret_post"
      ],
      "claims_parameter_supported": false,
      "request_parameter_supported": false,
      "request_uri_parameter_supported": false
    }
    
    このガイドでは、我々は使用されますPostman 権限サーバーをテストするには、簡単に別のツールを使用することができます.
    以下に、Postmanを使用した例の認証要求を見つけます.Grant Typeはクライアント資格情報の流れです.クライアントを認証するために、アクセストークンURL、クライアントIDと秘密を指定します.また、アクセスを要求しますapi スコープ.

    トークンを要求すると、操作は失敗します.これは私たちの認証サーバをまだ登録していないからです.
    データベースに追加することでクライアントを作成できます.そのためにクラスと呼ばれるクラスを作りますTestData . テストデータはIHostedService におけるテストデータの生成を実行できるインターフェースStartup.cs ときは、アプリケーションを起動します.
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using OpenIddict.Abstractions;
    
    namespace AuthorizationServer
    {
        public class TestData : IHostedService
        {
            private readonly IServiceProvider _serviceProvider;
    
            public TestData(IServiceProvider serviceProvider)
            {
                _serviceProvider = serviceProvider;
            }
    
            public async Task StartAsync(CancellationToken cancellationToken)
            {
                using var scope = _serviceProvider.CreateScope();
    
                var context = scope.ServiceProvider.GetRequiredService<DbContext>();
                await context.Database.EnsureCreatedAsync(cancellationToken);
    
                var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>();
    
                if (await manager.FindByClientIdAsync("postman", cancellationToken) is null)
                {
                    await manager.CreateAsync(new OpenIddictApplicationDescriptor
                    {
                        ClientId = "postman",
                        ClientSecret = "postman-secret",
                        DisplayName = "Postman",
                        Permissions =
                        {
                            OpenIddictConstants.Permissions.Endpoints.Token,
                            OpenIddictConstants.Permissions.GrantTypes.ClientCredentials,
    
                            OpenIddictConstants.Permissions.Prefixes.Scope + "api"
                        }
                    }, cancellationToken);
                }
            }
    
            public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
        }
    }
    
    テストデータにはクライアントが登録されている.クライアントIDと秘密を使用して、認証サーバーでクライアントを認証します.パーミッションは、このクライアントのオプションが何であるかを決定します.
    この場合、クライアントのクライアント資格情報の流れを使用し、トークンエンドポイントにアクセスし、クライアントに要求を許可するapi スコープ.
    テストデータサービスを登録するStartup.cs , アプリケーションの起動時に実行されます.
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        services.AddHostedService<TestData>();
    }
    
    Postmanで再度アクセストークンを取得しようとすると、リクエストは失敗します.これはまだトークンエンドポイントを作成していないからです.今すぐやります.
    新しいコントローラを作成するAuthorizationController , ここでエンドポイントをホストします.
    using System;
    using System.Security.Claims;
    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Mvc;
    using OpenIddict.Abstractions;
    using OpenIddict.Server.AspNetCore;
    
    namespace AuthorizationServer.Controllers
    {
        public class AuthorizationController : Controller
        {
            [HttpPost("~/connect/token")]
            public IActionResult Exchange()
            {
                var request = HttpContext.GetOpenIddictServerRequest() ??
                              throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
    
                ClaimsPrincipal claimsPrincipal;
    
                if (request.IsClientCredentialsGrantType())
                {
                    // Note: the client credentials are automatically validated by OpenIddict:
                    // if client_id or client_secret are invalid, this action won't be invoked.
    
                    var identity = new ClaimsIdentity(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
    
                    // Subject (sub) is a required field, we use the client id as the subject identifier here.
                    identity.AddClaim(OpenIddictConstants.Claims.Subject, request.ClientId ?? throw new InvalidOperationException());
    
                    // Add some claim, don't forget to add destination otherwise it won't be added to the access token.
                    identity.AddClaim("some-claim", "some-value", OpenIddictConstants.Destinations.AccessToken);
    
                    claimsPrincipal = new ClaimsPrincipal(identity);
    
                    claimsPrincipal.SetScopes(request.GetScopes());
                }
    
                else
                {
                    throw new InvalidOperationException("The specified grant type is not supported.");
                }
    
                // Returning a SignInResult will ask OpenIddict to issue the appropriate access/identity tokens.
                return SignIn(claimsPrincipal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
            }
        }
    }
    
    つのアクションが実装されます.Exchange . このアクションは、クライアントの資格情報の流れだけでなく、すべてのフローによってアクセストークンを取得するために使用されます.
    クライアント資格情報フローの場合、クライアントの資格情報に基づいてトークンが発行されます.認証コードフローの場合、同じエンドポイントを使用しますが、トークンの認証コードを交換します.我々は、それを見ます.
    今のところ、クライアントの資格情報の流れに集中する必要があります.リクエストがExchange アクション、クライアント資格情報(clientidとclientSecret)は既にOpenIDDictによって検証されます.それで、我々は要求を認証する必要はありません、我々はクレームプリンシパルを作成しなければならなくて、サインするためにそれを使用しなければならないだけです.
    クレームプリンシパルは、アカウントコントローラで使用されているものと同じではありません.これは、クッキー認証ハンドラに基づいていて、認証サーバー自身のコンテキスト内でのみ使用され、ユーザーが認証されたかどうかを判断します.
    我々が作成しなければならないクレームプリンシパルはOpenIddictServerAspNetCoreDefaults.AuthenticationScheme . このように、我々が呼ぶときSignIn このメソッドの最後に、OpenIDDictミドルウェアは、Sign INを処理し、クライアントへの応答としてアクセストークンを返します.
    クレームプリンシパルで定義されたクレームは、目的地を指定するときのみアクセストークンに含まれる.The some-value この例ではアクセストークンにクレームを追加します.
    The subject クレームが必要であり、目的地を指定する必要はありません.
    また、呼び出しによってすべての要求されたスコープを付与するclaimsPrincipal.SetScopes(request.GetScopes()); . OpenIDDictは、要求されたスコープが許可されているかどうかを確認しました.スコープをここで手動で追加しなければならない理由は、ここで指定したスコープをフィルタリングすることができます.

    それらをすべて支配するトークン


    再び郵便配達人とのアクセストークンを手に入れようとしましょう.
    OpenIDDictのv 3の時点で、アクセストークンはデフォルトでJason Webトークン(JWT)形式です.これによりトークンを調べることができますjwt.io . 感謝Auth0 , そのサービスをホスティングするために!)
    つの問題は、トークンが署名されていないだけでなく、暗号化されます.OpenIDDict既定でアクセストークンを暗号化します.OpenDictを設定するとき、この暗号化を無効にすることができますStartup.cs :
    // Encryption and signing of tokens
    options
        .AddEphemeralEncryptionKey()
        .AddEphemeralSigningKey()
        .DisableAccessTokenEncryption();
    
    さて、認証サーバーを再起動し、新しいトークンを要求するとき.トークンをペーストするjwt.io トークンの内容を表示します.

    クライアントIDを見ることができますpostman が設定されるsub . またsome-claim クレームがアクセストークンに追加されます.


    おめでとう、あなたはClient Credentials Flow OpenIDdictで!
    お気づきのように、ログインページはクライアント資格情報の流れによって使用されません.このフローはクライアントの資格情報をすぐにトークンに交換します.
    を実装しますAuthorization Code Flow with PKCE , これは、単一ページアプリケーション(SPA)とモバイルアプリケーションの推奨される流れです.この流れはユーザーを含んでいるので、我々のログインページは遊びに来ます.