series

  1. Develop blog projects based on ABP vNext and.NET Core – build projects using ABP CLI
  2. Develop blog projects based on ABP vNext and.NET Core – slim the project down and make it run
  3. Development blog project based on ABP vNext and.NET Core – Refinement and beautification, Swagger enter
  4. Develop blog project based on ABP vNext and.NET Core – data access and code priority
  5. Development blog project based on ABP vNext and.NET Core – add, delete, change and check custom warehouse
  6. Develop blog project based on ABP vNext and.NET Core – Uniform specification API, wrapper back model
  7. Develop blog projects based on ABP vNext and.NET Core – say Swagger, grouping, description, little green lock
  8. Develop blog projects based on ABP vNext and.NET Core – access GitHub and protect your API with JWT
  9. Develop blog project based on ABP vNext and.NET Core – exception handling and logging
  10. Develop blog projects based on ABP vNext and.NET Core – using Redis to cache data
  11. Develop blog project based on ABP vNext and.NET Core – integrate Hangfire for timed task processing
  12. Develop blog projects based on ABP vNext and.NET Core – Use AutoMapper to map objects
  13. Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (Part 1)
  14. Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (Part 2)
  15. Developing blog projects based on ABP vNext and.NET Core – Best Practices for Timed Tasks (PART 3)
  16. Blog Development project based on ABP vNext and.NET Core
  17. Abp vNext and.NET Core
  18. Blog Development project based on ABP vNext and.NET Core
  19. Blog Development project based on ABP vNext and.NET Core
  20. Blog Development project based on ABP vNext and.NET Core
  21. Abp vNext and.NET Core Development Blog Project – Blazor
  22. Abp vNext and.NET Core Development Blog Project – Blazor – Part 2
  23. Abp vNext and.NET Core Development Blog Project – Blazor
  24. Abp vNext and.NET Core Development Blog Project – Blazor
  25. Abp vNext and.NET Core Development Blog Project – Blazor
  26. Abp vNext and.NET Core Development Blog Project – Blazor – Part 6
  27. Abp vNext and.NET Core Development Blog Project – Blazor
  28. Abp vNext and.NET Core Development Blog Project – Blazor Series (8)
  29. Abp vNext and.NET Core Development Blog Project – Blazor Series (9)
  30. Abp vNext and.NET Core development blog project – Final release project

Previous article (juejin.cn/post/684490…) The usage of Swagger is explained again, and the grouping and description of Swagger are completed, and the small green lock is opened for identity authentication and authorization. Then, identity authentication and authorization are discussed in this paper.

Before we start, we need to be clear about a few concepts. Please note that authentication and authorization mean different things. Authentication is to prove your identity. Authorization, that is, permission, is assigned to a user with a certain permission identifier, and the user can use a certain function of the system.

There are many ways for identity authentication, such as creating a user table and using account passwords, or accessing third-party platforms. Here, I accessed GitHub for identity authentication. Of course, you can choose other ways (such as QQ, wechat, weibo, etc.), you can expand.

Open GitHub and go to the developer Settings screen (github.com/settings/de…). , let’s create a new oAuth App.

As shown in the figure, we will use sensitive data in appSettings. json

{
  ...
  "Github": {
    "UserId": 13010050,
    "ClientID": "5956811a5d04337ec2ca",
    "ClientSecret": "8fc1062c39728a8c2a47ba445dd45165063edd92",
    "RedirectUri": "https://localhost:44388/account/auth",
    "ApplicationName": "阿星Plus"
  }
}
Copy the code

ClientID and ClientSecret are generated by GitHub for us, please take care of your ClientID and ClientSecret. I’m giving it in plain text here, and I’ll delete the oAuth App 😝 at the end of this article. Please create your own!

RedirectUri is the callback address we added ourselves. ApplicationName is the name of our app, all of which corresponds to GitHub.

Read from appSettings. cs accordingly

./// <summary>
        /// GitHub
        /// </summary>
        public static class GitHub
        {
            public static int UserId => Convert.ToInt32(_config["Github:UserId"]);

            public static string Client_ID => _config["Github:ClientID"];

            public static string Client_Secret => _config["Github:ClientSecret"];

            public static string Redirect_Uri => _config["Github:RedirectUri"];

            public static string ApplicationName => _config["Github:ApplicationName"]; }...Copy the code

Next, all of us to go to the official document request lot, developer.github.com/apps/buildi…

Analysis, we access GitHub identity authentication authorization process down into the following steps

  1. The GitHub redirect address is generated based on the parameters, and the GitHub login page is displayed for login
  2. After a successful login, it will jump to our callback address, which will carrycodeparameter
  3. You get the code parameter, you get the access_token
  4. With access_token, you can invoke GitHub’s interface for obtaining user information to obtain the information about the current successful user

Before we get started, let’s take a quick look at GitHub’s API.

Create a githubconfig. cs configuration class under the Configurations folder in the.Domain layer and read out the required API and the contents of AppSettings. json.

//GitHubConfig.cs
namespace Meowv.Blog.Domain.Configurations
{
    public class GitHubConfig
    {
        /// <summary>
        ///GET Redirects to the GitHub login page, obtains user authorization, and obtains the code
        /// </summary>
        public static string API_Authorize = "https://github.com/login/oauth/authorize";

        /// <summary>
        ///POST request, get access_token according to code
        /// </summary>
        public static string API_AccessToken = "https://github.com/login/oauth/access_token";

        /// <summary>
        ///GET request to obtain user information based on access_token
        /// </summary>
        public static string API_User = "https://api.github.com/user";

        /// <summary>
        /// Github UserId
        /// </summary>
        public static int UserId = AppSettings.GitHub.UserId;

        /// <summary>
        /// Client ID
        /// </summary>
        public static string Client_ID = AppSettings.GitHub.Client_ID;

        /// <summary>
        /// Client Secret
        /// </summary>
        public static string Client_Secret = AppSettings.GitHub.Client_Secret;

        /// <summary>
        /// Authorization callback URL
        /// </summary>
        public static string Redirect_Uri = AppSettings.GitHub.Redirect_Uri;

        /// <summary>
        /// Application name
        /// </summary>
        public static stringApplicationName = AppSettings.GitHub.ApplicationName; }}Copy the code

If you are careful, you may have noticed that we added a UserId to the configuration. The GitHub UserId is unique. I will configure my own UserId. When we get the UserId through the API and it is the same as the UserId we have configured, we will authorize it. You can go backstage and have fun.

Before we start writing the interface, there is still some work to be done to enable the use of our authentication and authorization in.NET Core, because. The HttpApi.Hosting layer references the project. Application, the Application layer itself also need to add Microsoft. AspNetCore. Authentication. JwtBearer, and so on. Application add package: Microsoft. AspNetCore. Authentication JwtBearer, Open the Package manager console with a command to Install – Package. Microsoft AspNetCore. Authentication. JwtBearer installation, so you don’t need to repeat the add reference.

In HttpApi. Hosting module class MeowvBlogHttpApiHostingModule, ConfigureServices added

public override void ConfigureServices(ServiceConfigurationContext context)
{
    // Authentication
    context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
           .AddJwtBearer(options =>
           {
               options.TokenValidationParameters = new TokenValidationParameters
               {
                   ValidateIssuer = true,
                   ValidateAudience = true,
                   ValidateLifetime = true,
                   ClockSkew = TimeSpan.FromSeconds(30),
                   ValidateIssuerSigningKey = true,
                   ValidAudience = AppSettings.JWT.Domain,
                   ValidIssuer = AppSettings.JWT.Domain,
                   IssuerSigningKey = new SymmetricSecurityKey(AppSettings.JWT.SecurityKey.GetBytes())
               };
           });

    // Authentication and authorization
    context.Services.AddAuthorization();

    / / Http requests
    context.Services.AddHttpClient();
}
Copy the code

Because the forwarder will we need to call dead simple API in your code, so there will be ahead of System.Net.Http.IHttpClientFactory and related services are added to the IServiceCollection.

Explain what TokenValidationParameters parameter is:

ValidateIssuer: indicates whether the issuer is verified. ValidateAudience: Specifies whether to validate the access group. ValidateLifetime: Indicates whether to validate the lifetime. ClockSkew: Verifies the time offset of the Token. ValidateIssuerSigningKey: Indicates whether to verify the security key. ValidAudience: access group. ValidIssuer: indicates the issuer. IssuerSigningKey: indicates the security key. GetBytes() is an extension of ABP and can be used directly.

Set the values of ValidAudience, ValidIssuer, and IssuerSigningKey to True, and set the time offset to 30 seconds. Set the values of ValidAudience, ValidIssuer, and IssuerSigningKey to AppSettings. json.

//appsettings.json{..."JWT": {
    "Domain": "https://localhost:44388"."SecurityKey": "H4sIAAAAAAAAA3N0cnZxdXP38PTy9vH18w8I9AkOCQ0Lj4iMAgDB4fXPGgAAAA=="."Expires": 30}}//AppSettings.cs.public static class JWT
        {
            public static string Domain => _config["JWT:Domain"];

            public static string SecurityKey => _config["JWT:SecurityKey"];

            public static int Expires => Convert.ToInt32(_config["JWT:Expires"]); }...Copy the code

Expires is our token expiration time, so we give 30 here as well. Whether it’s 30 minutes or 30 seconds is up to you.

The SecurityKey was generated using a random coding tool.

At the same time in OnApplicationInitialization (ApplicationInitializationContext context) used in it.

.public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {...// Authentication
            app.UseAuthentication();

            // Authentication and authorizationapp.UseAuthorization(); . }...Copy the code

Now that the configuration is complete, write the interface to generate the Token and apply it to Swagger.

Packages have been added before the.Application layer: Microsoft. AspNetCore. Authentication JwtBearer, direct the Authorize new folder, add an interface IAuthorizeService and implementation class AuthorizeService.

//IAuthorizeService.cs
using Meowv.Blog.ToolKits.Base;
using System.Threading.Tasks;

namespace Meowv.Blog.Application.Authorize
{
    public interface IAuthorizeService
    {
        /// <summary>
        ///Obtain the login address (GitHub)
        /// </summary>
        /// <returns></returns>
        Task<ServiceResult<string>> GetLoginAddressAsync();

        /// <summary>
        ///Get AccessToken
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        Task<ServiceResult<string>> GetAccessTokenAsync(string code);

        /// <summary>
        ///The login succeeds, and the Token is generated
        /// </summary>
        /// <param name="access_token"></param>
        /// <returns></returns>
        Task<ServiceResult<string>> GenerateTokenAsync(stringaccess_token); }}Copy the code

Add three interface member methods, all asynchronous, and note that we receive them using the return model we wrote earlier, and then implement them one by one.

First implement GetLoginAddressAsync(), let’s build an AuthorizeRequest object, used to populate the generated GitHub login address, in. ToolKits layer new GitHub folder, reference. Domain project, add the authorizerequest.cs class.

//AuthorizeRequest.cs
using Meowv.Blog.Domain.Configurations;
using System;

namespace Meowv.Blog.ToolKits.GitHub
{
    public class AuthorizeRequest
    {
        /// <summary>
        /// Client ID
        /// </summary>
        public string Client_ID = GitHubConfig.Client_ID;

        /// <summary>
        /// Authorization callback URL
        /// </summary>
        public string Redirect_Uri = GitHubConfig.Redirect_Uri;

        /// <summary>
        /// State
        /// </summary>
        public string State { get; set; } = Guid.NewGuid().ToString("N");

        /// <summary>
        ///This parameter is optional. Multiple parameters to invoke Github are separated by commas (,), for example, scope=user,public_repo.
        ///If not, your app will only read the publicly gists in Github, such as publicly gists information, publicly repository information and gists information
        /// </summary>
        public string Scope { get; set; } = "user,public_repo"; }}Copy the code

The implementation method is as follows: Concatenate parameters and output the GitHub redirect address.

./// <summary>
        ///Obtain the login address (GitHub)
        /// </summary>
        /// <returns></returns>
        public async Task<ServiceResult<string>> GetLoginAddressAsync()
        {
            var result = new ServiceResult<string> ();var request = new AuthorizeRequest();
            var address = string.Concat(new string[]
            {
                    GitHubConfig.API_Authorize,
                    "? client_id=", request.Client_ID,
                    "&scope=", request.Scope,
                    "&state=", request.State,
                    "&redirect_uri=", request.Redirect_Uri
            });

            result.IsSuccess(address);
            return awaitTask.FromResult(result); }...Copy the code

Similarly, implement GetAccessTokenAsync(String code) and build the AccessTokenRequest object in. ToolKitsGitHub folder adds class: accesstokenRequest.cs.

//AccessTokenRequest.cs
using Meowv.Blog.Domain.Configurations;

namespace Meowv.Blog.ToolKits.GitHub
{
    public class AccessTokenRequest
    {
        /// <summary>
        /// Client ID
        /// </summary>
        public string Client_ID = GitHubConfig.Client_ID;

        /// <summary>
        /// Client Secret
        /// </summary>
        public string Client_Secret = GitHubConfig.Client_Secret;

        /// <summary>
        ///Call the API_Authorize Code value obtained
        /// </summary>
        public string Code { get; set; }

        /// <summary>
        /// Authorization callback URL
        /// </summary>
        public string Redirect_Uri = GitHubConfig.Redirect_Uri;

        /// <summary>
        /// State
        /// </summary>
        public string State { get; set; }}}Copy the code

AccessToken is obtained based on the code that was successfully logged in to, since HTTP requests are involved, before that we need to dependency inject the IHttpClientFactory in the constructor and use the IHttpClientFactory to create the HttpClient.

.private readonly IHttpClientFactory _httpClient;

public AuthorizeService(IHttpClientFactory httpClient)
{ _httpClient = httpClient; }...Copy the code
./// <summary>
        ///Get AccessToken
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        public async Task<ServiceResult<string>> GetAccessTokenAsync(string code)
        {
            var result = new ServiceResult<string> ();if (string.IsNullOrEmpty(code))
            {
                result.IsFailed("Code is empty");
                return result;
            }

            var request = new AccessTokenRequest();

            var content = new StringContent($"code={code}&client_id={request.Client_ID}&redirect_uri={request.Redirect_Uri}&client_secret={request.Client_Secret}");
            content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");

            using var client = _httpClient.CreateClient();
            var httpResponse = await client.PostAsync(GitHubConfig.API_AccessToken, content);

            var response = await httpResponse.Content.ReadAsStringAsync();

            if (response.StartsWith("access_token"))
                result.IsSuccess(response.Split("=") [1].Split("&").First());
            else
                result.IsFailed("Code is not correct");

            returnresult; }...Copy the code

Creating an HttpClient using IHttpClientFactory automatically frees the object, sends a POST request to the HttpClient, and if GitHub returns a string with an Access_token, it succeeds. Let’s deal with the output access_token. If not, the parameter code is wrong.

Create a new AuthController in the.Httpapi layer, inject our IAuthorizeServiceService, and try out our interface.

//AuthController.cs
using Meowv.Blog.Application.Authorize;
using Meowv.Blog.ToolKits.Base;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc;
using static Meowv.Blog.Domain.Shared.MeowvBlogConsts;

namespace Meowv.Blog.HttpApi.Controllers
{
    [ApiController]
    [AllowAnonymous]
    [Route("[controller]")]
    [ApiExplorerSettings(GroupName = Grouping.GroupName_v4)]
    public class AuthController : AbpController
    {
        private readonly IAuthorizeService _authorizeService;

        public AuthController(IAuthorizeService authorizeService)
        {
            _authorizeService = authorizeService;
        }

        /// <summary>
        ///Obtain the login address (GitHub)
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("url")]
        public async Task<ServiceResult<string>> GetLoginAddressAsync()
        {
            return await _authorizeService.GetLoginAddressAsync();
        }

        /// <summary>
        ///Get AccessToken
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("access_token")]
        public async Task<ServiceResult<string>> GetAccessTokenAsync(string code)
        {
            return await_authorizeService.GetAccessTokenAsync(code); }}}Copy the code

Notice that we have added two attributes: [AllowAnonymous] and [ApiExplorerSettings(GroupName = Grouping.GroupName_v4)]. Add description to Swagger layer for AuthController

.new OpenApiTag {
    Name = "Auth",
    Description = "JWT Mode authentication and Authorization",
    ExternalDocs = new OpenApiExternalDocs { Description = "JSON Web Token"}}...Copy the code

Open the Swagger document and call our two interfaces to see the effect.

Then open the redirection address we generated, it will jump to the login page, as follows:

Click Authorize button, the login will jump to our configured callback page,… /account/auth? code=10b7a58c7ba2e4414a14&state=a1ef05212c3b4a2cb2bbd87846dd4a8e

And then to get the code (10 b7a58c7ba2e4414a14), to call the AccessToken interface, success back our access_token eeafd5ca01b3719f74fc928440c89d59f2eeag (97).

Once you get the Access_token, you can call the get user information API. But before we do that, let’s write a couple of extension methods that we’ll use later and in the future. Create a folder called Extensions in the ToolKits layer and add a few more commonly used Extensions (…). .

I won’t post the code for the extension class. Go to GitHub(github.com/Meowv/Blog/…) Download it yourself, with specific annotations for each extension method.

GenerateTokenAsync(string access_token) is then implemented to generate the Token.

With access_token, you can directly call the interface that obtains user information: api.github.com/user?access… , and wrap this JSON in a model class, userResponse.cs.

If you need to convert JSON or XML to a model class, you can use one of the shortcuts in Visual Studio. Click on the upper-left menu: Edit => Paste optionally => Paste JSON as a class/paste XML as a class.

//UserResponse.cs
namespace Meowv.Blog.ToolKits.GitHub
{
    public class UserResponse
    {
        public string Login { get; set; }

        public int Id { get; set; }

        public string Avatar_url { get; set; }

        public string Html_url { get; set; }

        public string Repos_url { get; set; }

        public string Name { get; set; }

        public string Company { get; set; }

        public string Blog { get; set; }

        public string Location { get; set; }

        public string Email { get; set; }

        public string Bio { get; set; }

        public int Public_repos { get; set; }}}Copy the code

Then take a look at the specific way to generate tokens.

./// <summary>
        ///The login succeeds, and the Token is generated
        /// </summary>
        /// <param name="access_token"></param>
        /// <returns></returns>
        public async Task<ServiceResult<string>> GenerateTokenAsync(string access_token)
        {
            var result = new ServiceResult<string> ();if (string.IsNullOrEmpty(access_token))
            {
                result.IsFailed("Access_token is empty");
                return result;
            }

            var url = $"{GitHubConfig.API_User}? access_token={access_token}";
            using var client = _httpClient.CreateClient();
            client.DefaultRequestHeaders.Add("User-Agent"."Mozilla / 5.0 (Windows NT 10.0; Win64; X64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.14 Safari/ 537.36EDG /83.0.478.13");
            var httpResponse = await client.GetAsync(url);
            if(httpResponse.StatusCode ! = HttpStatusCode.OK) { result.IsFailed("Access_token is incorrect");
                return result;
            }

            var content = await httpResponse.Content.ReadAsStringAsync();

            var user = content.FromJson<UserResponse>();
            if (user.IsNull())
            {
                result.IsFailed("User data not obtained");
                return result;
            }

            if(user.Id ! = GitHubConfig.UserId) { result.IsFailed("Current account is not authorized");
                return result;
            }

            var claims = new[] {
                    new Claim(ClaimTypes.Name, user.Name),
                    new Claim(ClaimTypes.Email, user.Email),
                    new Claim(JwtRegisteredClaimNames.Exp, $"{new DateTimeOffset(DateTime.Now.AddMinutes(AppSettings.JWT.Expires)).ToUnixTimeSeconds()}"),
                    new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}")};var key = new SymmetricSecurityKey(AppSettings.JWT.SecurityKey.SerializeUtf8());
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

            var securityToken = new JwtSecurityToken(
                issuer: AppSettings.JWT.Domain,
                audience: AppSettings.JWT.Domain,
                claims: claims,
                expires: DateTime.Now.AddMinutes(AppSettings.JWT.Expires),
                signingCredentials: creds);

            var token = new JwtSecurityTokenHandler().WriteToken(securityToken);

            result.IsSuccess(token);
            return awaitTask.FromResult(result); }...Copy the code

The GitHub API has a security mechanism. It is important to note that when we use code to simulate a request, we need to add user-agent to it, otherwise it will not return the result successfully.

FromJson

is an extension method we added earlier that converts JSON strings into solid objects.

SymmetricSecurityKey(byte[] key) takes a byte[] argument, and here we also use an extension method SerializeUtf8() string serialized into a sequence of bytes.

We judge whether the returned Id is the user Id we configured. If so, authentication succeeds, authorization is carried out, and Token is generated.

The Token generation code is also simple, specifying Name, Email, and an expiration time of 30 minutes. Specific the meaning can go here to see: tools.ietf.org/html/rfc751…

WriteToken(SecurityToken token) : new JwtSecurityTokenHandler().writeToken (SecurityToken token)

./// <summary>
        ///The login succeeds, and the Token is generated
        /// </summary>
        /// <param name="access_token"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("token")]
        public async Task<ServiceResult<string>> GenerateTokenAsync(string access_token)
        {
            return await_authorizeService.GenerateTokenAsync(access_token); }...Copy the code

Pass in the previously received access_token, and the calling interface can see that the token has been successfully generated.

[AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] [AllowAnonymous] This can be specified for the entire Controller as well as for specific interfaces.

When you want to protect an interface, you simply add Attribute: [Authorize]. Now to protect our BlogController under the query interface, adding [the Authorize], increased when Microsoft. Pay attention to the reference namespace AspNetCore. Authorization.

. ./// <summary>
        ///Add a blog
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPost]
        [Authorize]
        public async Task<ServiceResult<string>> InsertPostAsync([FromBody] PostDto dto)
        ...

        /// <summary>
        ///Delete the blog
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete]
        [Authorize]
        public async Task<ServiceResult> DeletePostAsync([Required] int id).../// <summary>
        /// Update the blog
        /// </summary>
        /// <param name="id"></param>
        /// <param name="dto"></param>
        /// <returns></returns>
        [HttpPut]
        [Authorize]
        public async Task<ServiceResult<string>> UpdatePostAsync([Required] int id, [FromBody] PostDto dto).../// <summary>
        /// query blogs
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        public async Task<ServiceResult<PostDto>> GetPostAsync([Required] int id)... .Copy the code

Now compile and run, call the add, delete and change above and see if it works?

The interface will directly give us return a status code of 401 errors, in order to avoid this kind of unfriendly errors, we can add a middleware to handle our pipeline requests or in AddJwtBearer () in the treatment of our authentication event mechanism, when encounters an error status code, we still before returning to us the model created, Define friendly return errors, which will be described in a later chapter.

You can see that the small green lock is different between the public API and the API requiring authorization. The public API is displayed in black, and the API requiring authorization is displayed in gray.

What if we need to call our private API in Swagger? Click on our little green lock to fill in the tokens generated as Bearer {token}.

Be careful not to click Logout, otherwise you will exit.

As you can see, when we requested, there was an authorization: Bearer {token} in the request header and we were done. When we call from the Web, we just follow this rule.

Special note

When I did the authorization, the token was successfully generated and Bearer {token} was correctly filled in the Swagger. When the interface is called, 401 is always returned, and it is found that the cause of this problem is an incorrect name when configuring Swagger little green lock.

A unique name for the scheme, as per the Swagger spec.

As shown, change its name to “oauth2” and you can successfully authorize it. This article is connected to GitHub, to achieve authentication and authorization, using JWT way to protect our written API, have you learned? 😁 😁 😁

Open source: github.com/Meowv/Blog/…