fix jwt handler to return proper error codes

This commit is contained in:
Krrish Ghimire
2026-07-01 10:25:37 +05:45
parent 77b32b4d32
commit 18fed113f9
2 changed files with 83 additions and 5 deletions

View File

@@ -64,6 +64,12 @@ public class SecurityConfig {
.requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll() .requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/sites/**").authenticated() .requestMatchers(HttpMethod.GET, "/api/sites/**").authenticated()
.anyRequest().authenticated() .anyRequest().authenticated()
)
.exceptionHandling(ex -> ex
.authenticationEntryPoint((request, response, authException) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"))
.accessDeniedHandler((request, response, accessDeniedException) ->
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden"))
); );
if (isOAuth2Configured()) { if (isOAuth2Configured()) {
@@ -128,6 +134,9 @@ public class SecurityConfig {
} catch (Exception e) { } catch (Exception e) {
org.springframework.security.core.context.SecurityContextHolder org.springframework.security.core.context.SecurityContextHolder
.clearContext(); .clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Invalid or expired token");
return;
} }
} }

View File

@@ -1,11 +1,21 @@
import { Injectable } from '@angular/core'; import { Injectable, inject } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; import {
import { Observable } from 'rxjs'; HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
HttpErrorResponse,
} from '@angular/common/http';
import { BehaviorSubject, Observable, catchError, filter, switchMap, take, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
import { Router } from '@angular/router';
@Injectable() @Injectable()
export class JwtInterceptor implements HttpInterceptor { export class JwtInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {} private authService = inject(AuthService);
private router = inject(Router);
private isRefreshing = false;
private refreshTokenSubject = new BehaviorSubject<string | null>(null);
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const token = this.authService.getAccessToken(); const token = this.authService.getAccessToken();
@@ -14,6 +24,65 @@ export class JwtInterceptor implements HttpInterceptor {
setHeaders: { Authorization: `Bearer ${token}` }, setHeaders: { Authorization: `Bearer ${token}` },
}); });
} }
return next.handle(req);
return next.handle(req).pipe(
catchError((error) => {
if (
error instanceof HttpErrorResponse &&
error.status === 401 &&
!req.url.includes('/api/auth/refresh')
) {
return this.handle401Error(req, next);
}
return throwError(() => error);
}),
);
}
private handle401Error(
req: HttpRequest<unknown>,
next: HttpHandler,
): Observable<HttpEvent<unknown>> {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
const refreshToken = this.authService.getRefreshToken();
if (!refreshToken) {
this.authService.clearSessionForLogout();
this.router.navigate(['/login']);
return throwError(() => new Error('No refresh token available'));
}
return this.authService.refreshToken(refreshToken).pipe(
switchMap((res) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(res.accessToken);
return next.handle(
req.clone({
setHeaders: { Authorization: `Bearer ${res.accessToken}` },
}),
);
}),
catchError((err) => {
this.isRefreshing = false;
this.authService.clearSessionForLogout();
this.router.navigate(['/login']);
return throwError(() => err);
}),
);
} else {
return this.refreshTokenSubject.pipe(
filter((token): token is string => token !== null),
take(1),
switchMap((token) =>
next.handle(
req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
}),
),
),
);
}
} }
} }