angulartypescriptasp.net-core-mvchttpresponseasp.net-core-2.1

After invalid ASP.NET MVC server login, how can I capture the error text in the returned Json response and display in the Angular client?


Angular 9 Client login page

I want to display the specific returned error message in the <h4> element, instead of the generic message that displays currently. Note: I have updated the page with the solution I am going to use.

<div class="navbar bg-info mb-1">
   <a class="navbar-brand text-white">The ACTS Factory Authentication</a>
</div>
<!-- I want to put the specific error condition here -->
<h4 *ngIf="showError" class="p-2 bg-danger text-white">
<!-- Invalid username or password! --> {{theError}}      
</h4>
<form novalidate #authForm="ngForm" class="m-3" title="LoginForm">
   <div class="form-group">
     <label>Name:</label>
     <input #name="ngModel" name="name" class="form-control"
            [(ngModel)]="authService.name" required />

     <div *ngIf="name.invalid" class="text-danger">
       Please enter your user name
     </div>

   </div>
   <div class="form-group">
     <label>Password:</label>
     <input type="password" #password="ngModel" name="password"
            class="form-control" [(ngModel)]="authService.password" required />
     <div *ngIf="password.invalid" class="text-danger">
       Please enter your password
     </div>
   </div>
   <div class="text-center pt-2">
     <button type="button" class="btn btn-primary font-weight-bold" [disabled]="authForm.invalid"
             (click)="login()">
       Please Login
     </button>
   </div>
 </form>

Angular 9 Component

I am logging in from this Angular client component. When the response status code is 400 Bad Request, result is false and the generic error message is displayed in the <h4> element. The login() method here subscribes to the Login() method in the authentication service repository. The returned response object, or a property containing the error text, must make its way back here to be added to the html display. Note: I have updated the code with the solution I am going to use.

 import { Component } from "@angular/core";
 import { AuthenticationService } from "./authentication.service";
 import { HttpErrorResponse } from "@angular/common/http";

 @Component({
    templateUrl: "authentication.component.html"
 })
 export class AuthenticationComponent {

   showError: boolean = false;
   theError?: string = null;

   constructor(public authService: AuthenticationService) { }

   login() {
    // this.showError = false;
    // this.authService.login().subscribe(result => { this.showError = !result });

    // new Solution:
        this.authService.login().subscribe(result => { /* success action */ this.showError = !result },  // if true result, "showError" = false
         (err: HttpErrorResponse) => { /* error action */ console.error(err) },
         () => { /* complete (after success OR error) action */
         if (this.authService.theError != null) {
         this.theError = this.authService.theError;
         this.showError = true;
       }})

   }
 }
 

Angular 9 Login method in client authentication service repository

This login method calls the datasource's observable login() method, determines if a valid (true or false) response has been received and maps returned properties contained in the response to local variables. Note: I have updated the code with the solution I am going to use.

   // new variable for solution
   theError: string = null;

   login(): Observable<boolean> {
     this.authenticated = false;

     return this.dataSource.login(this.userName, this.userPwd).pipe(
       map(response => {
         if (response) {
           this.callbackUrl = null;
           this.authenticated = true;
           this.password = null
           this.router.navigateByUrl(this.callbackUrl || "appmenu");
         }
         // new code for solution - to capture error from datasource response object
         else {
                this.theError = this.dataSource.loginError;
         }

         this.profileEmail = null;
         this.profilePhone = null;

         if (this.dataSource.teamUser) {
           this.isTeamUser = this.dataSource.teamUser;
         }
         else {
         this.isTeamUser = false;
       }

       return this.authenticated = true;
     }),
     catchError(e => {
       this.authenticated = false;

    [I have tried to capture the error text here]   

       return of(false);
      }));
  }

Angular 9 Client Authentication datasource login method

This is the datasource for the client; it sends the http request to the server and receives the http response. It also maps properties in the response to local variables, determines the target url, and keeps track of necessary headers. Note: I have updated the code with the solution I am going to use.

   // new property in solution to contain login errors
   _loginError?: string = "Server not ready - try again.";
   public get loginError(): string {
   return this._loginError;
   }
   public set loginError(newValue: string) {
     this._loginError = newValue;
   }
   // end new property

   auth_Header: HttpHeaders = new HttpHeaders();
   auth_token: string = null;

   login(theName: string, thePassword: string): Observable<boolean> {
     return this.http.post<any>("/api/account/login",
     { name: theName, password: thePassword })
      .pipe(map(response => {

       // new solution code for capturing error and exiting if
       // the login status code is 203, codes in 400 series do
       // not logically track here.
       this.loginError = response.LoginError;
       if (!response.success) {
         return false;
       }
       // end new code

       this.auth_token = response.success ? response.token : null;    // JWT token generated in server-side AccountController
       this.auth_Header = this.auth_Header.set("Authorization", "Bearer<" + this.auth_token + ">");
       this.authCookie.JWTauthcookie = this.auth_token;  // save for the other datasources

       this.loginTime = response.loginTime;

   // to return user's registered email and phonenumber to client
       this.profileEmail = "";
       this.profileEmail = response.profileEmail;
       this.profilePhone = "";
       this.profilePhone = response.profilePhone;

       }

       this.isAuthenticated = true;
       return response.success;
     }));

ASP.NET Core MVC Account Controller

This is the ASP.NET Core MVC controller that handles the login request, searches in ASP.NET Core Identity for the username and password (not shown in code) and returns the 200 Status Code plus a JSON object when login is successful. I am now returning two specific login error conditions (user not found or password not valid) which I want to display on the client after the invalid login attempt. I have tried returning the errors with an OK instead of the BadRequest() status code 400. But, that is not helpful on the client. Note: I have updated the code with the solution I am going to use.

Any and all ideas would be appreciated. Thanks.

[HttpPost("/api/account/login")]  
public async Task<IActionResult> Login([FromBody] LoginViewModel creds)
{
    LoginError = "";
    bool loginResult = false;

    object myReturnObject = new object();

    if (ModelState.IsValid) 
    {
        // go look for existing Identity user and try to sign in
        loginResult = await DoLogin(creds);  

        if (loginResult)
        {
            myReturnObject = "{ " + '\n' + "   " + '"' + "success" + '"' + ":" + '"' + signInResult.Succeeded.ToString() + '"' + "," + '\n' +
                    "   " + '"' + "token" + '"' + ':' + '"' + bearerJWTokenString + '"' + "," + '\n' +
                    "   " + '"' + "loginTime" + '"' + ':' + '"' + DateTime.Now.ToShortTimeString() + '"' + "," + '\n' +
                    "   " + '"' + "profileEmail" + '"' + ':' + '"' + profileEmail + '"' + "," + '\n' +
                    "   " + '"' + "profilePhone" + '"' + ':' + '"' + profilePhone + '"' + "," + '\n' +
                    "   " + '"' + "team" + '"' + ':' + '"' + loggedInUserType.ToString() + '"' + "," + '\n' +
                    "}";

            // good login returns this object as a Json file in 
            // the HTTP Response  body
            return Ok(myReturnObject);   
        }
        else
        {   
            // Solution: fixed Json object, and now if bad username or pwd.
            // This returns http status code 203 plus the LoginError json object.
            // It was returning "BadRequest(Json-formatted string)" 
            myReturnObject = "{" + '\n' + "   " + '"' + "InvalidLogin" + '"' + ':' + '"' + "True" + '"' + "," +
                               '\n' + "   " + '"' + "LoginError" + '"' + ':' + '"' + LoginError + '"' +
                               '\n' + "}";
            return StatusCode(203, myReturnObject);
        }

    }

    // bad model state returns - http error 400 Bad Request
    return BadRequest(ModelState.ValidationState.ToString());
}

Screenshot of the original error condition displayed in client:

Login error text message


Now the solution provides a specific error condition when status code 203 is returned with a Json object:

new login error message

enter image description here


Solution

  • .subscribe(
      data => {/* success action*/},
      (error: HttpErrorResponse) => {/* error action */ console.error(error)},
      () => {/* complite (after success OR error) action */}
    )