I'm implementing an auth guard in Angular but instead of accessing localstorage or cookies to check if the user is authenticated, I have an API endpoints that returns 200 OK
if there is a httponly cookie containing the JWT attached to the request and 401 Unauthorized
otherwise.
Here is what I have tried
// auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(
private httpClient: HttpClient,
) {}
login(username: string, password: string) {
return this.httpClient.post('/users/login', { username, password });
}
isLoggedIn(): boolean {
this.httpClient.get('/users/check').subscribe({
next: (data) => {
return true;
},
error: () => {
return false;
},
});
return false;
}
}
// auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): boolean {
if (!this.authService.isLoggedIn()) {
this.router.navigate(['login']);
return false;
}
return true;
}
}
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login/login.component';
import { LayoutComponent } from './layout/layout.component';
import { ProjectsComponent } from './projects/projects.component';
import { AuthGuard } from './core/auth/auth.guard';
const routes: Routes = [
{
path: '',
component: LayoutComponent,
canActivate: [AuthGuard],
children: [{ path: 'projects', component: ProjectsComponent }],
},
{ path: 'login', component: LoginComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
The problem happens after the user has logged on and the authentication cookie has been set, the isLoggedIn()
function still returns false and thus redirects the user back to the login page. My speculation is that it has something to do with me handling the observable from httpclient
wrongly and causing it to return false;
before the request is completed?
Any help is appreciated and thanks in advance!
CodePudding user response:
This is a classic issue of async handling, and some function scoping Understanding...
when you do this, two main issues can be seen:
isLoggedIn(): boolean {
this.httpClient.get('/users/check').subscribe({
next: (data) => {
return true; // issue 02
},
error: () => {
return false; // issue 02
},
});
return false; // issue 01
}
Issue 01: Your function is only/always returning
false
, as this is the only return statement in the scope of theisLoggedIn
function. (classic async issue)Issue 02: Your callback functions withing the subscription are called, but their returned value is not being used. (issue with the understanding of function scoping). The issue is that the
return
's I marked as "Issue 02" belong to the scope of the subscription callbacks, and not to the scope of theisLoggedIn
function.
to resolve this:
- you should return the observable and not subscribe:
isLoggedIn() {
return this.httpClient.get('/users/check')
}
- From your guard (where you have a similar problem as in your service), you also not subscribe, and trust that the subscription will be done by the caller of the Guard (angular internal):
canActivate() {
return this.authService.isLoggedIn().pipe(take(1), tap(loggedIn => {
if (!loggedIn) this.router.navigate(['login']);
}));
}
CodePudding user response:
Try this, It works for me.
Inside isLoggedIn function:
isLoggedIn(): boolean {
return this.httpClient.get('/users/check').subscribe({
next: (data) => {
return true;
},
error: () => {
return false;
},
});
}
Inside canActivate Guard :
canActivate(){
return this.authService.isLoggedIn().subscribe(res => {
if (!res) this.router.navigate(['login']);
else return true;
});
}