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:
Now the solution provides a specific error condition when status code 203 is returned with a Json object:
.subscribe(
data => {/* success action*/},
(error: HttpErrorResponse) => {/* error action */ console.error(error)},
() => {/* complite (after success OR error) action */}
)