angularjwtauthorizationprimeng

Login User Data Persistent


I'm not understanding what I'm doing wrong in this implementation. So I'm logging in and passing the correct credentials for a user. After submitting the credentials it will call the server to validate the user creds. Once it is valid it will spit back out a JWT Token, a refresh token, and isLoggedIn variable true. In theory and from what I understand in the current code I have written, it should save the isLoggedIn variable to true in localStorage (Not the best but that is not the current issue). The button that displays Login should be switched to Logout, but it is not happening, and I do not understand why. I get the JWT token and refresh token with no problems. It is now to have the user be persistent that it is logged in.

Technologies that I'm using: Angular 17 and Prime NG 17

Code as follows:

Navbar.component.ts


export class NavbarComponent {
  menuItems: MenuItem[] = [];
  isLoggedIn: Observable<boolean>;
  ref: DynamicDialogRef | undefined;

  constructor(
    public dialogService: DialogService,
    private authService: AuthserviceService
  ) {
    this.isLoggedIn = authService.isLoggedIn();
  }

  ngOnInit() {
    this.menuItems = [//Menu Items would go here]
  }

  OpenLoginDialog(){
    this.ref = this.dialogService.open(LoginComponent, {
      header: 'Login',
      width: '50vw',
      modal:true,
      breakpoints: {
          '960px': '75vw',
          '640px': '90vw'
      },
    })

}

Navbar.component.html


<div>
  <p-menubar [model]="menuItems">
    <div>
      <input type="text" pInputText placeholder="Search">
      <button *ngIf="!isLoggedIn; else loggedOut" type="button" pButton label="Logout" icon="pi pi-power-off" style="margin-left:.25em"></button>
      <ng-template #loggedOut>
        <button type="button" pButton (click)="OpenLoginDialog()" label="Login" icon="pi pi-power-off" style="margin-left:.25em"></button>
      </ng-template>
    </div>
  </p-menubar>
</div>

Auth.Service.ts


export class AuthserviceService {
  isLoginSubject = new BehaviorSubject<boolean>(this.hasToken());

  constructor(private http: HttpClient) { }

  loginUser<LoginResponse>(loginCreds: any){
    return this.http.post(baseUrl + "login", loginCreds)
  }

  logoutUser(){
    // This is where I delete the local storage 
  }

  setStorage(data: any){
    localStorage.setItem("token", data.jwtToken)
    localStorage.setItem("refresh", data.refreshToken)
    // Seem redundant so commented bottom line
    // localStorage.setItem("isLoggedIn", data.isLoggedIn.toString())
  }

  isLoggedIn(): Observable<boolean>{
    return this.isLoginSubject.asObservable()
  }

  private hasToken(): boolean{
    return !!localStorage.getItem("token")
  }
}

Login.component.ts


export class LoginComponent {

  loginCredentials: FormGroup;

  constructor(public ref: DynamicDialogRef, private formBuilder: FormBuilder, private auth: AuthserviceService) {
    this.loginCredentials = this.formBuilder.group({
      alias: ['', Validators.required],
      password: ['', Validators.required]
    })
  }

  SubmitCredentials() {
    console.log("Submitting Creds")
    this.auth.loginUser<LoginResponse>(this.loginCredentials.value)
    .subscribe({
      next: (data: any) => {
      console.log('Data: ' + data.jwtToken)
      console.log('Data: ' + data.refreshToken)
      console.log('Data: ' + data.isLoggedIn)
      this.auth.setStorage(data)
    },
    error: (error: HttpErrorResponse) => {
      console.log('Error: ' + error.error)
    }});

    this.closeDialog(localStorage.getItem("isLoggedIn"))
  }

  closeDialog(data: any = null){
    this.ref.close(data)
  }

}


Solution

  • The issue lies in how the isLoggedIn state is being managed and updated.

    isLoggedIn in your NavbarComponent is initialized in the constructor but is never updated when the login status changes. Even though you store the token in localStorage, you don't notify the BehaviorSubject that the login state has changed.

    After setting the token in localStorage in the setStorage method, you should also update the isLoginSubject so that components subscribed to isLoggedIn will get the updated value.

    setStorage(data: any) {
      localStorage.setItem("token", data.jwtToken);
      localStorage.setItem("refresh", data.refreshToken);
      ////////////////////////////////////////////////////////////////////
      this.isLoginSubject.next(true); // Notify that the user is logged in
      ////////////////////////////////////////////////////////////////////
    }

    Also you should use the | async pipe when working with observables in the template. Without the | async pipe, the *ngIf directive will not properly evaluate the observable, and it will not update the UI as expected.

    <button *ngIf="!(isLoggedIn | async); else loggedOut" type="button" pButton label="Logout" icon="pi pi-power-off" style="margin-left:.25em"></button>
    <ng-template #loggedOut>
      <button type="button" pButton (click)="OpenLoginDialog()" label="Login" icon="pi pi-power-off" style="margin-left:.25em"></button>
    </ng-template>