Authentication and Authorization
JWT Authentication
1// auth.service.ts
2@Injectable({ providedIn: 'root' })
3export class AuthService {
4 private http = inject(HttpClient);
5 private router = inject(Router);
6
7 private currentUser = signal<User | null>(null);
8 readonly isLoggedIn = computed(() => !!this.currentUser());
9
10 login(credentials: LoginCredentials): Observable<AuthResponse> {
11 return this.http.post<AuthResponse>('/api/auth/login', credentials).pipe(
12 tap(response => {
13 localStorage.setItem('access_token', response.accessToken);
14 localStorage.setItem('refresh_token', response.refreshToken);
15 this.currentUser.set(response.user);
16 })
17 );
18 }
19
20 logout(): void {
21 localStorage.removeItem('access_token');
22 localStorage.removeItem('refresh_token');
23 this.currentUser.set(null);
24 this.router.navigate(['/login']);
25 }
26
27 getToken(): string | null {
28 return localStorage.getItem('access_token');
29 }
30
31 refreshToken(): Observable<AuthResponse> {
32 const refreshToken = localStorage.getItem('refresh_token');
33 return this.http.post<AuthResponse>('/api/auth/refresh', { refreshToken }).pipe(
34 tap(response => {
35 localStorage.setItem('access_token', response.accessToken);
36 })
37 );
38 }
39}
Auth Interceptor with Refresh Token
1// auth.interceptor.ts
2export const authInterceptor: HttpInterceptorFn = (req, next) => {
3 const authService = inject(AuthService);
4 const token = authService.getToken();
5
6 // Skip for public endpoints
7 if (req.context.get(SKIP_AUTH)) {
8 return next(req);
9 }
10
11 // Add token
12 if (token) {
13 req = req.clone({
14 headers: req.headers.set('Authorization', \`Bearer \${token}\`)
15 });
16 }
17
18 return next(req).pipe(
19 catchError((error: HttpErrorResponse) => {
20 if (error.status === 401) {
21 // Token expired - try to refresh
22 return authService.refreshToken().pipe(
23 switchMap(response => {
24 const newReq = req.clone({
25 headers: req.headers.set('Authorization', \`Bearer \${response.accessToken}\`)
26 });
27 return next(newReq);
28 }),
29 catchError(() => {
30 authService.logout();
31 return throwError(() => error);
32 })
33 );
34 }
35 return throwError(() => error);
36 })
37 );
38};
Route Guards
1// auth.guard.ts
2export const authGuard: CanActivateFn = (route, state) => {
3 const authService = inject(AuthService);
4 const router = inject(Router);
5
6 if (authService.isLoggedIn()) {
7 return true;
8 }
9
10 // Save URL for redirect after login
11 router.navigate(['/login'], {
12 queryParams: { returnUrl: state.url }
13 });
14 return false;
15};
16
17// role.guard.ts
18export const roleGuard: CanActivateFn = (route, state) => {
19 const authService = inject(AuthService);
20 const requiredRoles = route.data['roles'] as string[];
21
22 const user = authService.currentUser();
23 if (!user) return false;
24
25 return requiredRoles.some(role => user.roles.includes(role));
26};
27
28// Usage
29const routes: Routes = [
30 {
31 path: 'admin',
32 canActivate: [authGuard, roleGuard],
33 data: { roles: ['admin', 'moderator'] },
34 loadComponent: () => import('./admin.component')
35 }
36];