web-dev-qa-db-ja.com

ASP.NET Coreのトークンベース認証

私はASP.NET Coreアプリケーションを使用しています。 Token Based Authenticationを実装しようとしていますが、私の場合はnew Security System の使い方を理解できません。私は試しました しかし、彼らは私をあまり助けにしませんでした、彼らはクッキー認証か外部認証(GitHub、マイクロソフト、ツイッター)のどちらかを使っています。

私のシナリオは何ですか:angularjsアプリケーションは、ユーザー名とパスワードを渡して/token urlを要求するべきです。 WebApiはユーザーを認証し、access_tokenを返す必要があります。これは、次の要求でangularjsアプリによって使用されます。

現在のバージョンのASP.NETに必要なものを正確に実装するための素晴らしい記事が見つかりました。 ASP.NET Web API 2、Owin、およびIdentityを使用したトークンベースの認証 。しかし、ASP.NET Coreで同じことを実行する方法は私にとって明らかではありません。

私の質問は、ASP.NET Core WebApiアプリケーションをトークンベースの認証で動作するように設定する方法です。

148
Grant

.Net Core 2用に更新されました。

この回答の以前のバージョンではRSAを使用していました。トークンを生成しているのと同じコードがトークンも検証している場合は、実際には必要ありません。しかし、責任を分散しているのであれば、おそらくMicrosoft.IdentityModel.Tokens.RsaSecurityKeyのインスタンスを使用してこれを実行したいと思うでしょう。

  1. 後で使用する定数をいくつか作成します。これが私がしたことです:

    const string TokenAudience = "Myself";
    const string TokenIssuer = "MyProject";
    
  2. これをStartup.csのConfigureServicesに追加します。これらの設定にアクセスするには、後で依存性注入を使用します。私はあなたのauthenticationConfigurationConfigurationSectionまたはConfigurationオブジェクトであると仮定しているので、デバッグ用と本番用に異なる設定を持つことができます。鍵を安全に保管してください。任意の文字列にすることができます。

    var keySecret = authenticationConfiguration["JwtSigningKey"];
    var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(keySecret));
    
    services.AddTransient(_ => new JwtSignInHandler(symmetricKey));
    
    services.AddAuthentication(options =>
    {
        // This causes the default authentication scheme to be JWT.
        // Without this, the Authorization header is not checked and
        // you'll get no results. However, this also means that if
        // you're already using cookies in your app, they won't be 
        // checked by default.
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    })
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters.ValidateIssuerSigningKey = true;
            options.TokenValidationParameters.IssuerSigningKey = symmetricKey;
            options.TokenValidationParameters.ValidAudience = JwtSignInHandler.TokenAudience;
            options.TokenValidationParameters.ValidIssuer = JwtSignInHandler.TokenIssuer;
        });
    

    私は他の答えが他の設定を変更するのを見ました、例えばClockSkew;デフォルトは、クロックが正確に同期していない分散環境でも機能するように設定されています。これらはあなたが変更する必要がある唯一の設定です。

  3. 認証を設定します。 app.UseMvc()のようにUserの情報を必要とするミドルウェアの前にこの行を置くべきです。

    app.UseAuthentication();
    

    これはあなたのトークンがSignInManagerや他のものと一緒に発行される原因にはなりません。あなたはあなたのJWTを出力するためのあなた自身のメカニズムを提供する必要があるでしょう - 下記を見てください。

  4. AuthorizationPolicyを指定することをお勧めします。これにより、[Authorize("Bearer")]を使用した認証としてベアラトークンのみを許可するコントローラとアクションを指定できます。

    services.AddAuthorization(auth =>
    {
        auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
            .AddAuthenticationTypes(JwtBearerDefaults.AuthenticationType)
            .RequireAuthenticatedUser().Build());
    });
    
  5. ここで注意が必要なのは、トークンの作成です。

    class JwtSignInHandler
    {
        public const string TokenAudience = "Myself";
        public const string TokenIssuer = "MyProject";
        private readonly SymmetricSecurityKey key;
    
        public JwtSignInHandler(SymmetricSecurityKey symmetricKey)
        {
            this.key = symmetricKey;
        }
    
        public string BuildJwt(ClaimsPrincipal principal)
        {
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
            var token = new JwtSecurityToken(
                issuer: TokenIssuer,
                audience: TokenAudience,
                claims: principal.Claims,
                expires: DateTime.Now.AddMinutes(20),
                signingCredentials: creds
            );
    
            return new JwtSecurityTokenHandler().WriteToken(token);
        }
    }
    

    それから、あなたのコントローラであなたがあなたのトークンが欲しいところで、以下のようなもの:

    [HttpPost]
    public string AnonymousSignIn([FromServices] JwtSignInHandler tokenFactory)
    {
        var principal = new System.Security.Claims.ClaimsPrincipal(new[]
        {
            new System.Security.Claims.ClaimsIdentity(new[]
            {
                new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "Demo User")
            })
        });
        return tokenFactory.BuildJwt(principal);
    }
    

    ここでは、私はあなたがすでに校長を持っていると仮定します。 Identityを使用している場合は、 IUserClaimsPrincipalFactory<> を使用してUserClaimsPrincipalに変換できます。

  6. テストするには:トークンを入手し、 jwt.io のところに置いてください。私が上で提供した指示はまたあなたが署名を検証するためにあなたの設定から秘密を使うことを可能にします!

  7. .NET 4.5の無記名認証と組み合わせてHTMLページの部分ビューでこれをレンダリングしていた場合は、ViewComponentを使用して同じことを実行できます。これは、上記のコントローラアクションコードとほとんど同じです。

124
Matt DeKrey

Matt Dekreyのすばらしい答え から作業して、私はASP.NET Core(1.0.1)に対して動作する、完全に実用的なトークンベース認証の例を作成しました。完全なコード このリポジトリのGitHub上1.0.0-rc1beta8beta7 )の代替ブランチ、しかし簡単に言えば、重要なステップは次のとおりです。

あなたのアプリケーション用のキーを生成します

私の例では、アプリが起動するたびにランダムキーを生成します。それを生成してどこかに保存し、それをアプリケーションに提供する必要があります。 私がランダムキーを生成する方法と.jsonファイルからインポートする方法については、このファイルを参照してください @kspearrinによるコメントで示唆されているように、 Data Protection API はキーを「正しく」管理するための理想的な候補のように思えますが、それが可能であれば私はうまくいきませんでした。うまくいったらプルリクエストを送ってください!

Startup.cs - ConfigureServices

ここで、私たちのトークンが署名されるための秘密鍵をロードする必要があります。秘密鍵は提示されたときにトークンを検証するためにも使用します。このキーをクラスレベルの変数keyに格納しています。これを以下のConfigureメソッドで再利用します。 TokenAuthOptions は、キーを作成するためにTokenControllerに必要な署名用のID、オーディエンス、および発行者を保持する単純なクラスです。

// Replace this with some sort of loading from config / file.
RSAParameters keyParams = RSAKeyUtils.GetRandomKey();

// Create the key, and a set of token options to record signing credentials 
// using that key, along with the other parameters we will need in the 
// token controlller.
key = new RsaSecurityKey(keyParams);
tokenOptions = new TokenAuthOptions()
{
    Audience = TokenAudience,
    Issuer = TokenIssuer,
    SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.Sha256Digest)
};

// Save the token options into an instance so they're accessible to the 
// controller.
services.AddSingleton<TokenAuthOptions>(tokenOptions);

// Enable the use of an [Authorize("Bearer")] attribute on methods and
// classes to protect.
services.AddAuthorization(auth =>
{
    auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
        .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme‌​)
        .RequireAuthenticatedUser().Build());
});

また、保護したいエンドポイントおよびクラスで[Authorize("Bearer")]を使用できるようにするための承認ポリシーも設定しました。

Startup.cs - 設定

ここでは、JwtBearerAuthenticationを設定する必要があります。

app.UseJwtBearerAuthentication(new JwtBearerOptions {
    TokenValidationParameters = new TokenValidationParameters {
        IssuerSigningKey = key,
        ValidAudience = tokenOptions.Audience,
        ValidIssuer = tokenOptions.Issuer,

        // When receiving a token, check that it is still valid.
        ValidateLifetime = true,

        // This defines the maximum allowable clock skew - i.e.
        // provides a tolerance on the token expiry time 
        // when validating the lifetime. As we're creating the tokens 
        // locally and validating them on the same machines which 
        // should have synchronised time, this can be set to zero. 
        // Where external tokens are used, some leeway here could be 
        // useful.
        ClockSkew = TimeSpan.FromMinutes(0)
    }
});

TokenController

トークンコントローラでは、Startup.csにロードされたキーを使用して署名付きキーを生成するためのメソッドが必要です。 TokenAuthOptionsインスタンスをStartupに登録したので、それをTokenControllerのコンストラクタにインジェクトする必要があります。

[Route("api/[controller]")]
public class TokenController : Controller
{
    private readonly TokenAuthOptions tokenOptions;

    public TokenController(TokenAuthOptions tokenOptions)
    {
        this.tokenOptions = tokenOptions;
    }
...

次に、ログインエンドポイント用のハンドラーでトークンを生成する必要があります。この例では、ユーザー名とパスワードを取得し、ifステートメントを使用してそれらを検証します。ただし、必要なことはクレームを作成またはロードすることです。ベースのIDとそのためのトークンを生成します。

public class AuthRequest
{
    public string username { get; set; }
    public string password { get; set; }
}

/// <summary>
/// Request a new token for a given username/password pair.
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
public dynamic Post([FromBody] AuthRequest req)
{
    // Obviously, at this point you need to validate the username and password against whatever system you wish.
    if ((req.username == "TEST" && req.password == "TEST") || (req.username == "TEST2" && req.password == "TEST"))
    {
        DateTime? expires = DateTime.UtcNow.AddMinutes(2);
        var token = GetToken(req.username, expires);
        return new { authenticated = true, entityId = 1, token = token, tokenExpires = expires };
    }
    return new { authenticated = false };
}

private string GetToken(string user, DateTime? expires)
{
    var handler = new JwtSecurityTokenHandler();

    // Here, you should create or look up an identity for the user which is being authenticated.
    // For now, just creating a simple generic identity.
    ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user, "TokenAuth"), new[] { new Claim("EntityID", "1", ClaimValueTypes.Integer) });

    var securityToken = handler.CreateToken(new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor() {
        Issuer = tokenOptions.Issuer,
        Audience = tokenOptions.Audience,
        SigningCredentials = tokenOptions.SigningCredentials,
        Subject = identity,
        Expires = expires
    });
    return handler.WriteToken(securityToken);
}

そしてそれはそれであるはずです。保護したいメソッドやクラスに[Authorize("Bearer")]を追加するだけで、トークンなしでアクセスしようとするとエラーになります。 500エラーではなく401エラーを返したい場合は、カスタム例外ハンドラ この例ではこの例のように を登録する必要があります。

80
Mark Hughes

JWTトークンを含むさまざまな認証メカニズムを処理する方法を示すOpenId接続サンプルを見てください。

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Samples

Cordova Backendプロジェクトを見ると、APIの構成は次のようになっています。

           // Create a new branch where the registered middleware will be executed only for non API calls.
        app.UseWhen(context => !context.Request.Path.StartsWithSegments(new PathString("/api")), branch => {
            // Insert a new cookies middleware in the pipeline to store
            // the user identity returned by the external identity provider.
            branch.UseCookieAuthentication(new CookieAuthenticationOptions {
                AutomaticAuthenticate = true,
                AutomaticChallenge = true,
                AuthenticationScheme = "ServerCookie",
                CookieName = CookieAuthenticationDefaults.CookiePrefix + "ServerCookie",
                ExpireTimeSpan = TimeSpan.FromMinutes(5),
                LoginPath = new PathString("/signin"),
                LogoutPath = new PathString("/signout")
            });

            branch.UseGoogleAuthentication(new GoogleOptions {
                ClientId = "560027070069-37ldt4kfuohhu3m495hk2j4pjp92d382.apps.googleusercontent.com",
                ClientSecret = "n2Q-GEw9RQjzcRbU3qhfTj8f"
            });

            branch.UseTwitterAuthentication(new TwitterOptions {
                ConsumerKey = "6XaCTaLbMqfj6ww3zvZ5g",
                ConsumerSecret = "Il2eFzGIrYhz6BWjYhVXBPQSfZuS4xoHpSSyD9PI"
            });
        });

/Providers/AuthorizationProvider.cs内のロジックとそのプロジェクトのRessourceControllerも見ておく価値があります。

あるいは、以下のコードを使用してトークンを検証することもできます(signalRで機能させるためのスニペットもあります)。

        // Add a new middleware validating access tokens.
        app.UseOAuthValidation(options =>
        {
            // Automatic authentication must be enabled
            // for SignalR to receive the access token.
            options.AutomaticAuthenticate = true;

            options.Events = new OAuthValidationEvents
            {
                // Note: for SignalR connections, the default Authorization header does not work,
                // because the WebSockets JS API doesn't allow setting custom parameters.
                // To work around this limitation, the access token is retrieved from the query string.
                OnRetrieveToken = context =>
                {
                    // Note: when the token is missing from the query string,
                    // context.Token is null and the JWT bearer middleware will
                    // automatically try to retrieve it from the Authorization header.
                    context.Token = context.Request.Query["access_token"];

                    return Task.FromResult(0);
                }
            };
        });

トークンを発行するためには、openId Connectサーバーパッケージを使うことができます。

        // Add a new middleware issuing access tokens.
        app.UseOpenIdConnectServer(options =>
        {
            options.Provider = new AuthenticationProvider();
            // Enable the authorization, logout, token and userinfo endpoints.
            //options.AuthorizationEndpointPath = "/connect/authorize";
            //options.LogoutEndpointPath = "/connect/logout";
            options.TokenEndpointPath = "/connect/token";
            //options.UserinfoEndpointPath = "/connect/userinfo";

            // Note: if you don't explicitly register a signing key, one is automatically generated and
            // persisted on the disk. If the key cannot be persisted, an exception is thrown.
            // 
            // On production, using a X.509 certificate stored in the machine store is recommended.
            // You can generate a self-signed certificate using Pluralsight's self-cert utility:
            // https://s3.amazonaws.com/pluralsight-free/keith-brown/samples/SelfCert.Zip
            // 
            // options.SigningCredentials.AddCertificate("7D2A741FE34CC2C7369237A5F2078988E17A6A75");
            // 
            // Alternatively, you can also store the certificate as an embedded .pfx resource
            // directly in this Assembly or in a file published alongside this project:
            // 
            // options.SigningCredentials.AddCertificate(
            //     Assembly: typeof(Startup).GetTypeInfo().Assembly,
            //     resource: "Nancy.Server.Certificate.pfx",
            //     password: "Owin.Security.OpenIdConnect.Server");

            // Note: see AuthorizationController.cs for more
            // information concerning ApplicationCanDisplayErrors.
            options.ApplicationCanDisplayErrors = true // in dev only ...;
            options.AllowInsecureHttp = true // in dev only...;
        });

編集:私はAureliaフロントエンドフレームワークとASP.NETコアを使用してトークンベースの認証実装で単一ページのアプリケーションを実装しました。シグナルR永続的接続もあります。しかし私はDBの実装をしていません。ここでコードを見ることができます: https://github.com/alexandre-spieser/AureliaAspNetCoreAuth

お役に立てれば、

ベスト、

アレックス

3
Darxtar

OpenIddictを見てください。これは、ASP.NET 5でJWTトークンの作成と更新トークンの構成を容易にする新しいプロジェクトです(執筆時点で)。トークンの検証は他のソフトウェアによって処理されます。

IdentityEntity Frameworkと一緒に使用すると仮定すると、最後の行はConfigureServicesメソッドに追加するものです。

services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders()
    .AddOpenIddictCore<Application>(config => config.UseEntityFramework());

Configureでは、JWTトークンを提供するようにOpenIddictを設定します。

app.UseOpenIddictCore(builder =>
{
    // tell openiddict you're wanting to use jwt tokens
    builder.Options.UseJwtTokens();
    // NOTE: for dev consumption only! for live, this is not encouraged!
    builder.Options.AllowInsecureHttp = true;
    builder.Options.ApplicationCanDisplayErrors = true;
});

トークンの検証もConfigureで設定します。

// use jwt bearer authentication
app.UseJwtBearerAuthentication(options =>
{
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
    options.RequireHttpsMetadata = false;
    options.Audience = "http://localhost:58292/";
    options.Authority = "http://localhost:58292/";
});

DbContextがOpenIddictContextから派生する必要があるなど、他に1つか2つの小さなことがあります。

あなたはこのブログ記事で完全な長さの説明を見ることができます: http://capesean.co.za/blog/asp-net-5-jwt-tokens/

機能しているデモは以下から入手できます。 https://github.com/capesean/openiddict-test

1
Sean