Skip to content

Commit 4d48539

Browse files
committed
Implement auth-guard and interceptors
Removed those _guards and implemented as a service instead. Added a guard to '/questions' so only logged in users with tokens that have not expired can access it. If they are not logged in, users will be directed to '/login' to log in.
1 parent b01dceb commit 4d48539

File tree

10 files changed

+73
-42
lines changed

10 files changed

+73
-42
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export const environment = {
22
production: true,
3-
UserServiceApiUrl: 'http://localhost:3001'
3+
UserServiceApiUrl: 'http://localhost:8082'
44
};

frontend/src/_guards/user-only.guard.spec.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

frontend/src/_guards/user-only.guard.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

frontend/src/_helpers/interceptors/error.interceptor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class ErrorInterceptor implements HttpInterceptor {
1717
}
1818

1919
const error = err.error.message || err.statusText;
20-
return throwError(error);
20+
return throwError(() => new Error(error));
2121
}))
2222
}
2323
}
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
import { Injectable } from '@angular/core';
22
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
33
import { Observable } from 'rxjs';
4-
54
import { environment } from '../../_environments/environment';
65
import { AuthenticationService } from '../../_services/authentication.service';
6+
import { User } from '../../_models/user.model';
77

88
@Injectable()
99
export class JwtInterceptor implements HttpInterceptor {
10-
constructor(private authenticationService: AuthenticationService) { }
10+
private currentUser: User | null | undefined;
11+
constructor(private authenticationService: AuthenticationService) {
12+
this.authenticationService.user$.subscribe(user => {
13+
this.currentUser = user;
14+
})
15+
}
1116

1217
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
1318
// add auth header with jwt if user is logged in and request is to the api url
14-
const user = this.authenticationService.userValue;
15-
const isLoggedIn = user?.accessToken;
19+
const isLoggedIn = this.currentUser?.accessToken;
1620
const isApiUrl = request.url.startsWith(environment.UserServiceApiUrl);
1721
if (isLoggedIn && isApiUrl) {
1822
request = request.clone({
19-
setHeaders: {
20-
Authorization: `Bearer ${user.accessToken}`
21-
}
23+
headers: request.headers.set('Authorization', `Bearer ${this.currentUser!.accessToken}`),
2224
});
2325
}
24-
2526
return next.handle(request);
2627
}
2728
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Injectable } from "@angular/core";
2+
import { CanActivate, Router } from "@angular/router";
3+
import { AuthenticationService } from "./authentication.service";
4+
import { filter, map, Observable, of, switchMap } from 'rxjs';
5+
import { HttpClient } from "@angular/common/http";
6+
import { environment } from "../_environments/environment";
7+
8+
@Injectable()
9+
export class AuthGuardService implements CanActivate{
10+
constructor(
11+
private authenticationService: AuthenticationService,
12+
private http: HttpClient,
13+
private router: Router
14+
) {}
15+
16+
canActivate() : Observable<boolean> {
17+
return this.authenticationService.user$.pipe(
18+
filter(user => user !== undefined),
19+
switchMap((user) => { // switchMap to flatten the observable from http.get
20+
if (user === null) {
21+
// not logged in so redirect to login page with the return url
22+
this.router.navigate(['/account/login']);
23+
return of(false); // of() to return an observable to be flattened
24+
}
25+
// call to user service endpoint '/users/{user_id}' to check user is still valid
26+
return this.http.get<any>(`${environment.UserServiceApiUrl}/users/${user.id}`, { observe: 'response' })
27+
.pipe(map(response => {
28+
if (response.status === 200) {
29+
return true;
30+
}
31+
return false;
32+
}))
33+
})
34+
)
35+
}
36+
}

frontend/src/_services/authentication.service.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import { User } from '../_models/user.model';
99

1010
@Injectable({ providedIn: 'root' })
1111
export class AuthenticationService {
12-
private userSubject: BehaviorSubject<User | null>;
13-
public user: Observable<User | null>;
12+
private userSubject: BehaviorSubject<User | null | undefined>;
13+
public user$: Observable<User | null | undefined>;
1414

1515
constructor(
1616
private router: Router,
1717
private http: HttpClient
1818
) {
1919
this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user')!));
20-
this.user = this.userSubject.asObservable();
20+
this.user$ = this.userSubject.asObservable();
2121
}
2222

2323
public get userValue() {
@@ -29,7 +29,15 @@ export class AuthenticationService {
2929
{ "username": username, "password": password })
3030
.pipe(map(response => {
3131
// store user details and jwt token in local storage to keep user logged in between page refreshes
32-
const user = response.data;
32+
const data = response.data;
33+
const user: User = {
34+
id: data.id,
35+
username: data.username,
36+
email: data.email,
37+
accessToken: data.accessToken,
38+
isAdmin: data.isAdmin,
39+
createdAt: data.createdAt
40+
}
3341
localStorage.setItem('user', JSON.stringify(user));
3442
this.userSubject.next(user);
3543
return user;

frontend/src/app/account/login.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ export class LoginComponent {
3232
private router: Router,
3333
private route: ActivatedRoute
3434
) {
35-
// redirect to home if already logged in
36-
// if (this.authenticationService.userValue) {
37-
// this.router.navigate(['/']);
38-
// }
35+
//redirect to home if already logged in
36+
if (this.authenticationService.userValue) {
37+
this.router.navigate(['/']);
38+
}
3939
}
4040

4141
userForm = {

frontend/src/app/app.config.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
22
import { provideRouter } from '@angular/router';
3-
import { HTTP_INTERCEPTORS, provideHttpClient } from '@angular/common/http';
3+
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
44
import { routes } from './app.routes';
55
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
66
import { JwtInterceptor } from '../_helpers/interceptors/jwt.interceptor';
77
import { ErrorInterceptor } from '../_helpers/interceptors/error.interceptor';
8+
import { AuthGuardService } from '../_services/auth.guard.service';
89

910
export const appConfig: ApplicationConfig = {
1011
providers: [
1112
provideZoneChangeDetection({ eventCoalescing: true }),
1213
provideRouter(routes),
1314
provideAnimationsAsync(),
15+
provideHttpClient(
16+
// DI-based interceptors must be explicitly enabled.
17+
withInterceptorsFromDi(),
18+
),
1419
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
1520
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
16-
provideHttpClient()
21+
provideHttpClient(),
22+
AuthGuardService
1723
],
1824
};

frontend/src/app/app.routes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Routes } from '@angular/router';
22
import { QuestionsComponent } from './questions/questions.component';
3+
import { AuthGuardService } from '../_services/auth.guard.service';
34

45
const accountModule = () => import('./account/account.module').then(x => x.AccountModule);
56

@@ -11,5 +12,6 @@ export const routes: Routes = [
1112
{
1213
path: 'questions',
1314
component: QuestionsComponent,
15+
canActivate: [AuthGuardService],
1416
},
1517
];

0 commit comments

Comments
 (0)