Reputation: 183
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!
Upvotes: 1
Views: 1558
Reputation: 6250
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 the isLoggedIn
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 the isLoggedIn
function.
to resolve this:
isLoggedIn() {
return this.httpClient.get('/users/check').pipe(
map(() => true),
catchError(() => of(false))
)
}
canActivate() {
return this.authService.isLoggedIn().pipe(take(1), tap(loggedIn => {
if (!loggedIn) this.router.navigate(['login']);
}));
}
Upvotes: 3
Reputation: 507
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;
});
}
Upvotes: 0