Many students say that AgileConfig’s UI is really ugly. I think so. Originally, I used this project by myself. At the beginning, I didn’t even have a UI. The UI was later added using the old bootstrap3 base style. The foreground framework also uses AngularJS, which is also old hat. I finally decided to revamp AgileConfig’s front UI over the New Year. Finally, AntDesign Pro + React was selected as the front-end UI framework. I chose Ant-Design Pro because it’s pretty and popular. I chose React because I know a little bit about VUE and Angular. Let’s take the opportunity to learn what React is and why it’s so popular. The authentication scheme for login is JWT. In fact, I am not too keen on JWT (see here “Do we really need JWT?”). ), but everyone likes it, so I have to follow the crowd. In fact, I have almost finished the interface based on Ant-Design Pro, because it supports mock data, so I have not modified a line of background code, and I have almost finished the interface. From now on it’s time to really tune into the back-end code. So let’s start by logging in. Let’s take a look at how the back-end ASP.NET Core aspect will change.

Modify the ASP.NET Core backend code

"JwtSetting": {"SecurityKey": "XXXXXXXXXXXX ", // Issuer: "agileconfig. Admin ", // Issuer: "Audience": "Agileconfig. admin", // Receiver "ExpireSeconds": 20 // Expiration time s}Copy the code

Add the JWt-related configuration to the appSettings. json file.

public class JwtSetting { static JwtSetting() { Instance = new JwtSetting(); Instance.Audience = Global.Config["JwtSetting:Audience"]; Instance.SecurityKey = Global.Config["JwtSetting:SecurityKey"]; Instance.Issuer = Global.Config["JwtSetting:Issuer"]; Instance.ExpireSeconds = int.Parse(Global.Config["JwtSetting:ExpireSeconds"]); } public string SecurityKey { get; set; } public string Issuer { get; set; } public string Audience { get; set; } public int ExpireSeconds { get; set; } public static JwtSetting Instance { get; }}Copy the code

Define a JwtSetting class that reads the configuration.

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache();
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                      .AddJwtBearer(options =>
                      {
                          options.TokenValidationParameters = new TokenValidationParameters
                          {
                              ValidIssuer = JwtSetting.Instance.Issuer,
                              ValidAudience = JwtSetting.Instance.Audience,
                              IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JwtSetting.Instance.SecurityKey)),
                          };
                      });
            services.AddCors();
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0).AddRazorRuntimeCompilation();
            services.AddFreeSqlDbContext();
            services.AddBusinessServices();
            services.AddAntiforgery(o => o.SuppressXFrameOptionsHeader = true);
        }
Copy the code

Modify the Startup file ConfigureServices method, modified certification Scheme for JwtBearerDefaults. AuthenticationScheme, configuration JWT related configuration information within AddJwtBearer method. Since the front and back end are separated from the project, it is possible that the API and UI are deployed under different domain names, so Cors is enabled.

     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseMiddleware<ExceptionHandlerMiddleware>();
            }
            app.UseCors(op=> {
                op.AllowAnyOrigin();
                op.AllowAnyMethod();
                op.AllowAnyHeader();
            });
            app.UseWebSockets(new WebSocketOptions()
            {
                KeepAliveInterval = TimeSpan.FromSeconds(60),
                ReceiveBufferSize = 2 * 1024
            });
            app.UseMiddleware<WebsocketHandlerMiddleware>();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }
Copy the code

Example Modify the Configure method of Startup to set Cors to Any.

Public class JWT {public static string GetToken() { According to need to add more information var claims = new Claim [] {new Claim (JwtRegisteredClaimNames. Jit, Guid NewGuid (). The ToString ()), New Claim("id", "admin", claimValuetypes.string), // User ID new Claim("name", "admin"), // user name new Claim("admin", True.tostring (), claimValuetypes.boolean) // Whether it is an administrator}; var key = Encoding.UTF8.GetBytes(JwtSetting.Instance.SecurityKey); / / create a token var token = new JwtSecurityToken (issuer: JwtSetting. The Instance. The issuer, on: JwtSetting.Instance.Audience, signingCredentials: new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature), claims: claims, notBefore: DateTime.Now, expires: DateTime.Now.AddSeconds(JwtSetting.Instance.ExpireSeconds) ); string jwtToken = new JwtSecurityTokenHandler().WriteToken(token); return jwtToken; }}Copy the code

Add a JWT static class to generate JWT tokens. Since the agileconfig user is admin, the user name and ID are written out directly.

[HttpPost("admin/jwt/login")] public async Task<IActionResult> Login4AntdPro([FromBody] LoginVM model) { string password  = model.password; If (string.IsNullOrEmpty(password)) {return Json(new {status = "error", message = "password cannot be null"}); } var result = await _settingService.ValidateAdminPassword(password); if (result) { var jwt = JWT.GetToken(); return Json(new { status="ok", token=jwt, type= "Bearer", currentAuthority = "admin" }); } return Json(new {status = "error", message = "password error"}); }Copy the code

Add an Action method as the login entry point. After the password is verified here, the token is generated and returned to the front end. This is where the.net Core side of the back end code changes. It is mainly to add things related to JWT, which has been written a lot on the Internet. Let’s start modifying the front-end code.

Modify the code of AntDesign Pro

AntDesign Pro has already generated the login page and logic for us, but the original login is fake, and JWT token is not supported as the login certificate. Now we need to modify several files to improve the login.

export function setToken(token:string): void {
  localStorage.setItem('token', token);
}

export function getToken(): string {
  var tk = localStorage.getItem('token');
  if (tk) {
    return tk as string;
  }

  return '';
}

Copy the code

Add 2 new methods in utils/authority.ts file to store and obtain tokens. Our JWT tokens are stored in localStorage.

*/ const request = extend({prefix: 'http://localhost:5000', errorHandler, // Default error handling credentials: 'same-origin', // default request with cookie,}); const authHeaderInterceptor = (url: string, options: RequestOptionsInit) => { const authHeader = { Authorization: 'Bearer ' + getToken() }; return { url: `${url}`, options: { ... options, interceptors: true, headers: authHeader }, }; }; request.interceptors.request.use(authHeaderInterceptor);Copy the code

Modify the utils/request.ts file to define an interceptor with an Authorization header, and use the interceptor, which automatically carries the header every time a request is made and passes the JWT token to the background. Set prefix to http://localhost:5000. This is the service address of our back-end API, which will be replaced by the official address in real production. Set the credentials to same-origin.

export async function accountLogin(params: LoginParamsType) {
  return request('/admin/jwt/login', {
    method: 'POST',
    data: params,
  });
}

Copy the code

Add a method to initiate a login request in the services/login.ts file.

effects: { *login({ payload }, { call, put }) { const response = yield call(accountLogin, payload); yield put({ type: 'changeLoginStatus', payload: response, }); // Login successfully if (response.status === 'ok') { const urlParams = new URL(window.location.href); const params = getPageQuery(); Message. success('🎉 🎉 🎉 Login successful! '); let { redirect } = params as { redirect: string }; if (redirect) { console.log('redirect url ' , redirect); const redirectUrlParams = new URL(redirect); if (redirectUrlParams.origin === urlParams.origin) { redirect = redirect.substr(urlParams.origin.length); if (redirect.match(/^\/.*#/)) { redirect = redirect.substr(redirect.indexOf('#') + 1); } } else { window.location.href = '/'; return; } } history.replace(redirect || '/'); } }, reducers: { changeLoginStatus(state, { payload }) { setAuthority(payload.currentAuthority); setToken(payload.token) return { ... state, status: payload.status, type: payload.type, }; }},Copy the code

Modify the models/login.ts file, change the Effects login method, and internally replace fakeAccountLogin with accountLogin. Meanwhile, modify the changeLoginStatus method inside reducers and add the code of setToken, which will be stored after successful login after modification.

effects: { *fetch(_, { call, put }) { const response = yield call(queryUsers); yield put({ type: 'save', payload: response, }); }, *fetchCurrent(_, {call, put}) {const response = {name: 'admin', userID: 'admin'}; yield put({ type: 'saveCurrentUser', payload: response, }); }},Copy the code

Modify the models/user.ts file to change the Effects fetchCurrent method to return response directly. Originally fetchCurrent will pull the current user information in the background, because the agileconfig user is only admin, so I directly write dead.Let’s try login 🙂

The source code is here:Github.com/kklldog/Agi…🌟 🌟 🌟

Pay attention to my public number to play with technology