次を認証します.jsスパ.NET 6アイデンティティとDuendeアイデンティティサーバPart 2


導入


で、我々は詳細にどのように我々のswagger UIを認証する方法をカバーしている.Duendeアイデンティティサーバを使用したJSアプリケーション前のフローでは、認証コードGrant Typeを使用してアクセストークンを要求します.このフローを使用して、認証サーバーを使用して同意サーバーを使用します.しかし、いくつかのシナリオでは、我々のスパから直接ビューを使用して(認証サーバービューに行くことなく)認証します.幸いにも、アイデンティティサーバーには、単純なことを達成させる流れがあります.それはリソースの所有者パスワード資格情報(ROPC)の流れです.
この記事では、ROPCフローを実装し、APIを消費するためのアクセストークンを使用します.

ギタブレポ


このポストに沿って簡単にフォローするには、Githubレポを見ることができます.
SPA Identity Server Authenticate Sample

リソース所有者パスワード資格情報流れを紹介してください


"The Password grant type is a way to exchange a user's credentials for an access token. Because the client application has to collect the user's password and send it to the authorization server, it is not recommended that this grant be used at all anymore.”

”As of OAuth 2.1, the ROPC grant type is now deprecated, and its use is discouraged by the OAuth security best practices.”


ROPCフローは、ユーザーアイデンティティを認証して、顧客によって、系からユーザーのデータを要請するために用いる単純な助成金流動である.これは、“リソースの所有者”(リソースはプロファイルまたはAPIリソースをすることができます)は、ユーザーが含まれます.Identity Serverは、ユーザーの資格情報(ユーザ名とパスワード)をユーザーストア(基本的にデータベース)に対して検証し、成功した検証時に、要求されたクライアントが希望する型のトークンを取得します.ユーザがこの流れに関与しているので、クライアントはユーザが許可を有するAPI資源にアクセスするために使用されるアクセストークンを要求することができる.

DuendeアイデンティティサーバにおけるROPCの実装


前の記事で定義したクライアントの設定を使ってropcを実装します.
// Config.cs

new Client
{
    ClientId = "nextjs_web_app",
    ClientName = "NextJs Web App",
    ClientSecrets = { new Secret("secret".Sha256()) },
    RequireClientSecret = false,
    AllowedGrantTypes =  new[] { 
            GrantType.AuthorizationCode, 
            **GrantType.ResourceOwnerPassword // Add this to allow the client to use ROPC to authorize**
        },

    AllowOfflineAccess = true, // Add this to recieve the refresh token after login

    // where to redirect to after login
    RedirectUris = { "http://localhost:3000/api/auth/callback/sample-identity-server" },
    // where to redirect to after logout
    PostLogoutRedirectUris = { "http://localhost:3000" },
    AllowedCorsOrigins= { "http://localhost:3000" },

    AllowedScopes = new List<string>
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "SampleAPI"
    },
2行に注意する
// ....  
AllowedGrantTypes =  new[] { 
        GrantType.AuthorizationCode, 
        **GrantType.ResourceOwnerPassword // Add this to allow the client to use ROPC to authorize**
},

AllowOfflineAccess = true, // Add this to recieve the refresh token after login
独自のユーザーストアに対してユーザー資格情報を検証するには、IdenticeCownerPasswordValidatorインターフェイスの独自の実装を提供する必要があります.これは、パスワードサーバーのトークン要求を受け取ると、IDサーバーが呼び出されるインターフェイスです.
IdentityServersNetNedentityプロジェクトの内部で、単一のメソッドを宣言するInterface IResourceownerPasswordValidatorを実装するUserValidatorというクラスを作成しますValidateAsync(ResourceOwnerPasswordValidationContext context) . アイデンティティサーバーは、このメソッドを使用して資格情報を検証し、コンテキストを成功または失敗に設定します.
// UserValidator.cs

public class UserValidator : IResourceOwnerPasswordValidator
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;
    public UserValidator(
        UserManager<ApplicationUser> userManager,
        SignInManager<ApplicationUser> signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }

    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        var result = await _signInManager.PasswordSignInAsync(context.UserName, context.Password, isPersistent: true, lockoutOnFailure: true);

        if (result.Succeeded)
        {
            var user = await _userManager.FindByNameAsync(context.UserName);
            if (user != null)
            {
                var claims = await _userManager.GetClaimsAsync(user);
                // context set to success
                context.Result = new GrantValidationResult(
                    subject: user.Id.ToString(),
                    authenticationMethod: AuthenticationMethods.Password,
                    claims: claims
                );
                return;
            }
        }

        // context set to Failure        
        context.Result = new GrantValidationResult(
                TokenRequestErrors.UnauthorizedClient, "Invalid Crdentials");

    }
}
次に、IdentiValidatorクラスを使用するIDサーバーを指定する必要があります.にConfigurationServices クラスの拡張クラスのメソッド.
// HostingExtensions.cs

builder.Services
            .AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;

                // see https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/
                options.EmitStaticAudienceClaim = true;
            })
            .AddInMemoryIdentityResources(Config.IdentityResources)
            .AddInMemoryApiScopes(Config.ApiScopes)
            .AddInMemoryClients(Config.Clients)
            .AddAspNetIdentity<ApplicationUser>()
            **.AddResourceOwnerValidator<UserValidator>(); // Add this line**
このクライアントにROPC認証を受け入れるよう設定しました.我々は、それが我々の次をセットアップする前にうまくいくかどうかテストします.jsスパ.
私たちは、郵便配達人を呼び出します/token エンドポイント.IDサーバープロジェクトを起動し、以下のような要求を行います

(あるいはPostmanのimport関数を使って以下のテキストをインポートすることができます).
curl --location --request POST 'https://localhost:5001/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=nextjs_web_app' \
--data-urlencode 'scope=openid profile offline_access SampleAPI' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=bob' \
--data-urlencode 'password=Pass123$'
レスポンスはこのようなJSONオブジェクトを含んでいます
{
    "access_token": "xxx",
    "expires_in": 3600,
    "token_type": "Bearer",
    "refresh_token": "xxx",
    "scope": "offline_access openid profile SampleAPI"
}
ACCENCERYトークンを直接取得したことに注意してください.また、AccessCacheトークンが期限切れになったときには、RefreshRankトークンを使用して新しいアクセサリトークンを交換できます.

次の設定。ユーザ名とパスワードで認証を使用するJS


次のスパを設定します.JSアプリケーションは、ROPCの流れを使用して承認するには、別のプロバイダを追加する必要があります.ネクサスJSは既にユーザ名とパスワードのような任意の資格情報で署名を扱うことができます.我々は、上記の郵便配達人リクエストを資格証明プロバイダーに持ってきます.
我々の次へ.JSアプリケーション[...nextauth].ts 設定ファイル
import CredentialsProvider from "next-auth/providers/credentials";

// ....
export default NextAuth({
  providers: [
        // Other Providers
        // ...

        CredentialsProvider({
      name: "Credentials Demo",
      credentials: {
        username: { label: "Username", type: "text", placeholder: "alice" },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials, req) {
        const reqData = {
          ...credentials,
          client_id: "nextjs_web_app",
          scope: "openid profile offline_access SampleAPI",
          grant_type: "password",
        } as any

        let formBody = []
        for (let property in reqData) {
          let encodedKey = encodeURIComponent(property)
          let encodedValue = encodeURIComponent(reqData[property])
          formBody.push(encodedKey + "=" + encodedValue)
        }

        const formBodyStr = formBody.join("&")

        const res = await fetch("https://localhost:5001/connect/token", {
          method: "POST",
          body: formBodyStr,
          headers: {
            "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
          },
        })
        const response= await res.json()
        // If no error and we have user data, return it
        if (res.ok && response) {
          return response
        }
        // Return null if user data could not be retrieved
        return null
      },
    }),
    ]
})
応答データスキーマは次のようなものです
{
    "access_token": "xxx",
    "expires_in": 3600,
    "token_type": "Bearer",
    "refresh_token": "xxx",
    "scope": "offline_access openid profile SampleAPI"
}
アプリケーションを起動してhttp://localhost:3000/ . ヘッダーの中のサインインボタンを押してください、あなたはNextauthに操縦されます.JSサインインページ

RPOCフローを直接使用して前の記事またはサインの流れを使用して、あなたにサインする2つのオプションがあります.ユーザ名とパスワードを入力してRPOCフローをテストします.その後、正常にログインする必要がありますhttp://localhost:3000/

APIをアクセストークンで消費する


私たちはどのように我々のユーザーを承認し、首尾よくアイデンティティサーバーからAccessResトークンを得ることを学んだ.このトークンを使ってAPIを消費します.
クールなことは、nextauthです.JSはすでに私たちのために非常に簡単にしました.ヘルパーコールgetToken() これは、現在のログインJWTトークンを取得するためにどこにでも注入することができます.
デフォルトでは、nextAuth.JSはOAuthトークンをITプロバイダに保存しません.それで、始めるために、我々は我々のAPIを消費するためにそれらを使うことができるように、それらのトークンをアクセスしたいです.
Nextauthで.我々は定義することができますcallbacks これにより、認証イベントをタップし、返されるものをカスタマイズできます.開放する[...nextauth].ts 次のコードを追加します

// ....
export default NextAuth({
  // ...
    providers: [...]
    // ...
    callbacks: {
    async signIn({ user, account, profile, email, credentials }) {
      if (user?.access_token) {
        account.access_token = user?.access_token as string
      }
      if (user?.refresh_token) {
        account.refresh_token = user?.refresh_token as string
      }

      return true
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      if (account?.access_token) {
        token.access_token = account?.access_token
      }

      if (account?.refresh_token) {
        token.refresh_token = account?.refresh_token
      }

      return token
    },
})
少し説明をさらに
  • The signIn コールバックはjwt コールバック.我々は、返されるトークンのjwt コールバックには常にAccessRankトークンとRefreshRankトークンが含まれます.
  • ROPCフローを使用して署名するなら、AccessRockトークンとRefreshRankトークンはsignIn ’sユーザーオブジェクト.私たちはアカウントからユーザにトークンを渡し、アカウントからアカウントの中のトークンにjwt コールバック
  • 認証コードフロー(前の記事で行った流れ)を使用してログインすると、jwt ’sアカウントオブジェクト.トークンオブジェクトにアカウントからトークンを渡す必要があります.
  • その後、トークンはセッションで利用可能になり、我々はどこでもnextauth以内にそれを使用することができます.JSのプロバイダー
  • この例のコンテキストでは、WeatherProtoControllerのエンドポイントへの呼び出しを行います.(最初にAPIアプリケーションを起動してください.
    /フォルダ/APIフォルダ内で、サンプルと呼ばれるフォルダを作成します.そして、index.ts ファイルを入れる.これは、エンドポイントを呼び出すために使用されます.
    // /pages/api/sampe/index.ts
    import { getToken } from "next-auth/jwt"
    import type { NextApiRequest, NextApiResponse } from "next"
    
    const secret = process.env.SECRET
    
    export default async function sample(req: NextApiRequest, res: NextApiResponse
    ) {
        const token = await getToken({ req, secret });
        if(!token?.access_token){
            return res.status(401).json({
                status: 'Need Authorization!'
            })
        }
    
        try{
            const result = await fetch("https://localhost:7101/WeatherForecast", {
                method: "GET",
                headers: {
                  "Authorization": "Bearer " + token?.access_token,
                },
              });
    
            return res.status(200).json({
                status: 'Ok',
                data: await result.json()
            })
    
        }
        catch(e: any){
            return res.status(400).json({
                status: e.message
              });
        }
    }
    
    一旦我々がこれをするならば、我々は訪問によって我々のブラウザーで直接これにアクセスすることができなければなりませんhttp://localhost:3000/api/sample
    ページが戻るには
    {"status":"Need Authorization!"}
    
    帰らなければならないhttp://localhost:3000/ , ログインアクションを実行し、データをhttp://localhost:3000/api/sample は、我々が正常に我々のAPIを消費していることを示すべきです!
    {
        "status": "Ok",
        "data": [
            {
                "date": "2022-04-14T17:59:46.1262274+07:00",
                "temperatureC": -2,
                "temperatureF": 29,
                "summary": "Bracing"
            },
            {
                "date": "2022-04-15T17:59:46.1262316+07:00",
                "temperatureC": 37,
                "temperatureF": 98,
                "summary": "Chilly"
            },
            {
                "date": "2022-04-16T17:59:46.1262318+07:00",
                "temperatureC": 6,
                "temperatureF": 42,
                "summary": "Balmy"
            },
            {
                "date": "2022-04-17T17:59:46.1262319+07:00",
                "temperatureC": -8,
                "temperatureF": 18,
                "summary": "Hot"
            },
            {
                "date": "2022-04-18T17:59:46.126232+07:00",
                "temperatureC": -13,
                "temperatureF": 9,
                "summary": "Warm"
            }
        ]
    }
    

    他に何ができるか


    OAuth 2についての詳細を読む

  • https://aaronparecki.com/oauth-2-simplified/
  • https://www.scottbrady91.com/oauth/why-the-resource-owner-password-credentials-grant-type-is-not-authentication-nor-suitable-for-modern-applications
  • 役割ベース認可


    さらに、このチュートリアルでは、ロールベースの認証を実装し、APIを保護するために使用する方法の別のシリーズを作成します.希望は誰もがそれをお楽しみいただけます!

    概要


    我々は、次の認証する最も基本的な方法を行っている.DuendeアイデンティティサーバによるJS温泉.Nextauthで探検するより多くのものがあります.JSドキュメントとDuendeアイデンティティサーバドキュメント.それで、彼らに訪問をして、あなた自身のものを実行しようとするのを忘れないでください.
    読んでくれてありがとう!🤗🤗🤗

    参考文献

  • https://referbruv.com/blog/posts/implementing-resource-owner-password-credentials-(ropc)-using-identityserver4
  • https://next-auth.js.org/getting-started/introduction
  • https://www.scottbrady91.com/oauth/why-the-resource-owner-password-credentials-grant-type-is-not-authentication-nor-suitable-for-modern-applications