0

I'm trying to implement authentication with Observables in Angular 10. I'm using ReplaySubject in the AuthService for set the current user signed in. The problem is that the AuthGuard never redirect to home after user sign in. I describe the issue below on auth.guard.ts file.

app.routing.ts

const routes: Routes = [
      {
        path: 'not-found',
        component: NotFoundComponent,
      },
      {
        path: '',
        component: MainLayoutComponent,
        children: [
          {
            path: '',
            canActivateChild: [AuthGuard],
            loadChildren: () =>
              import('./modules/landing/landing.module').then(
                (m) => m.LandingModule
              ),
          },
        ],
      },
      { path: '**', redirectTo: 'not-found', pathMatch: 'full' },
    ];

auth.service.ts

@Injectable({ providedIn: 'root' })
export class AuthService {
  baseUrl = environment.apiUrl;
  private currentUserSource = new ReplaySubject<LoggedIn>(1);
  currentUser$ = this.currentUserSource.asObservable();

  constructor(private http: HttpClient) {}

  signIn(user: SignInDto) {
    return this.http.post(this.baseUrl + 'auth/signin', user).pipe(
      map((user: LoggedInDto) => {
        if (user) {
          localStorage.setItem('token', user.token);
          this.currentUserSource.next(user);

          return user;
        }
      })
    );
  }

  getCurrentUser(token: string) {
    if (token === null) {
      this.currentUserSource.next(null);
      return of(null);
    }
    const headers = new HttpHeaders().set('Authorization', 'Bearer ' + token);

    return this.http.get(this.baseUrl + 'auth/me', { headers }).pipe(
      map((user: LoggedIn) => {
        if (user) {
          localStorage.setItem('token', user.token);
          this.currentUserSource.next(user);
        }
      })
    );
  }
}

app.component.ts

export class AppComponent implements OnInit {

  constructor(private readonly authService: AuthService) {}

  ngOnInit(): void {
    this.getCurrentUser();
  }

  getCurrentUser() {
    const token = localStorage.getItem('token');

    this.authService.getCurrentUser(token).subscribe(() => {
      console.log('user loaded');
    }, console.log);
  }
}
export class SignInComponent implements OnInit {
  returnUrl: string;

  constructor(
    private readonly authService: AuthService,
    private readonly router: Router,
    private readonly activatedRoute: ActivatedRoute
  ) {}

  ngOnInit(): void {
    this.returnUrl = this.activatedRoute.snapshot.queryParams.returnUrl || '';
  }

  onSubmit() {
    this.authService.signIn({username: 'xxx', password: 'xxx'}).subscribe(
      (response) => {
        this.router.navigateByUrl(this.returnUrl);
      },
      console.log
    );
  }
}

auth.guard.ts

The problem is here... When user sign in the currentUser$ observable is always null, everything else works fine (the token is saved in the local storage). If I try to refresh the page after sign in, the guard works fine but, if I signed in without update the page nothing happend.

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivateChild {
  logged: boolean = false;
  constructor(private authService: AuthService, private router: Router) {}

  canActivateChild(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    return this.authService.currentUser$.pipe(
      map((auth) => {
        // when you sign in, auth object is always null
        // so the guard never redirect. But, if I refresh the page
        // after the token was saved the guard works fine, the auth object comes with data
        // What's the problem? :(
        if (auth) {
          return true;
        }

        this.router.navigate(['/signin'], {
          queryParams: { returnUrl: state.url },
        });
      })
    );
  }
}

So the question is, what Im doing wrong or ignoring?

  • 1
    Why are you using a ReplaySubject? This seems like you would want a BehaviorSubject. – Adrian Brand Aug 06 '20 at 05:07
  • @AdrianBrand According with this [answer](https://stackoverflow.com/questions/44697159/angular-4-canactivate-observable-not-invoked), use ReplayObject is the best practice. Isn't it? – Marluan Espiritusanto Guerrero Aug 06 '20 at 12:21
  • 1
    That answer is 3 years old. I didn't feel like reading that giant wall of text but my guess is that behavior subjects had not been added to RxJs back then. I might be completely wrong but for your requirement you should totally be using a behavior subject. – Adrian Brand Aug 06 '20 at 12:36

0 Answers0