Issue with Refresh Token After Long Inactivity in Angular

Discussão em 'Angular' iniciado por Mohamed Aymen Hssen, Outubro 29, 2024.

  1. I'm working on an Angular application that uses authentication with accessToken and refreshToken. My goal is to keep the user logged in even after an extended period of inactivity. Here’s the expected vs. actual behavior:

    Expected behavior: When the user returns after a long period of inactivity, the refreshToken should be used to obtain a new accessToken without requiring a manual re-login. Actual behavior: The refreshToken works only the first time. After a long period of inactivity (e.g., several hours or days), it stops refreshing automatically, forcing the user to log out manually and re-login. I suspect there’s an issue with the automatic refresh logic, which stops functioning correctly after a certain duration. Here’s the relevant code:

    interface CustomJwtPayload {
    exp: number; // Timestamp de l'expiration
    userId?: number; // ID utilisateur, optionnel
    }

    export class AuthService {

    private baseUrl = 'http://localhost:8080/api';
    private readonly ACCESS_TOKEN_EXPIRATION = 1000 * 60 * 15; // 15 minutes
    private readonly REFRESH_TOKEN_EXPIRATION = 1000 * 60 * 60 * 24 * 7; // 7 jours

    constructor(
    private http: HttpClient,
    @Inject(PLATFORM_ID) private platformId: Object,
    private ngZone: NgZone
    ) {
    this.setupAutoRefresh();
    }

    private getToken(): string | null {
    if (isPlatformBrowser(this.platformId)) {
    return localStorage.getItem('accessToken'); // Utilisez localStorage
    }
    return null;
    }

    private getRefreshToken(): string | null {
    return localStorage.getItem('refreshToken'); // Utilisez localStorage
    }

    private storeNewAccessToken(token: string) {
    if (isPlatformBrowser(this.platformId)) {
    localStorage.setItem('accessToken', token); // Utilisez localStorage
    }
    }

    public refreshToken(refreshToken: string): Observable<any> {
    return this.http.post(`${this.baseUrl}/refresh-token`, { refreshToken }, {
    headers: this.getHeaders()
    }).pipe(
    catchError(error => {
    console.error('Token refresh failed', error);
    return EMPTY; // Retourne un Observable vide pour éviter tout blocage
    })
    );
    }

    private setupAutoRefresh() {
    if (isPlatformBrowser(this.platformId)) {
    this.ngZone.runOutsideAngular(() => {
    setInterval(() => {
    if (this.isAccessTokenCloseToExpiration()) {
    const refreshToken = this.getRefreshToken(); // Récupère le token de rafraîchissement
    if (refreshToken) {
    this.refreshToken(refreshToken).subscribe(newAccessToken => {
    this.storeNewAccessToken(newAccessToken.token); // Stocke le nouveau token d'accès
    });
    }
    }
    }, 13 * 60 * 1000); // Appelle toutes les 13 minutes
    });
    }
    }

    private isAccessTokenCloseToExpiration(): boolean {
    const decodedToken = this.decodeToken();
    if (!decodedToken) return false;
    const expirationTime = decodedToken.exp * 1000;
    const currentTime = new Date().getTime();
    return expirationTime - currentTime < 2 * 60 * 1000; // Moins de 2 minutes restantes
    }

    private decodeToken(): CustomJwtPayload | null {
    const token = this.getToken();
    if (!token) return null;
    try {
    return jwtDecode<CustomJwtPayload>(token);
    } catch (error) {
    console.error('Token decoding failed', error);
    return null;
    }
    }


    SPRing boot

    private final long ACCESS_TOKEN_EXPIRATION = 1000 * 60 * 15;
    private final long REFRESH_TOKEN_EXPIRATION = 1000 * 60 * 60 * 24 * 7;

    public String extractUsername(String token) {
    return extractClaim(token, Claims:: getSubject);
    }

    public Date extractExpiration(String token) {
    return extractClaim(token, Claims:: getExpiration);
    }

    public < T > T extractClaim(String token, Function < Claims, T > claimsResolver) {
    final Claims claims = extractAllClaims(token);
    return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
    return Jwts.parserBuilder()
    .setSigningKey(SECRET_KEY)
    .build()
    .parseClaimsJws(token)
    .getBody();
    }

    private Boolean isTokenExpired(String token) {
    return extractExpiration(token).before(new Date());
    }

    // Génération d'un Token d'Accès
    public String generateAccessToken(String username, Long userId) {
    Map < String, Object > claims = new HashMap<>();
    claims.put("userId", userId); // Ajoutez l'ID utilisateur aux revendications
    return createToken(claims, username, ACCESS_TOKEN_EXPIRATION, SECRET_KEY);
    }

    // Génération d'un Refresh Token
    public String generateRefreshToken(String username, Long userId) {
    Map < String, Object > claims = new HashMap<>();
    claims.put("userId", userId); // Ajoutez l'ID utilisateur aux revendications
    return createToken(claims, username, REFRESH_TOKEN_EXPIRATION, REFRESH_SECRET_KEY);
    }

    // Extraction de l'ID utilisateur à partir du token
    public Long extractUserId(String token) {
    final Claims claims = extractAllClaims(token);
    return (Long) claims.get("userId");
    }

    // Création d'un JWT avec expiration personnalisée et clé donnée
    private String createToken(Map < String, Object > claims, String subject, long expirationTime, Key key) {
    return Jwts.builder()
    .setClaims(claims)
    .setSubject(subject)
    .setIssuedAt(new Date(System.currentTimeMillis()))
    .setExpiration(new Date(System.currentTimeMillis() + expirationTime))
    .signWith(key) // Utilisez la clé appropriée
    .compact();
    }

    // Validation du Token d'Accès
    public Boolean validateAccessToken(String token, String username) {
    final String extractedUsername = extractUsername(token);
    return (extractedUsername.equals(username) && !isTokenExpired(token));
    }

    // Validation du Refresh Token
    public Boolean validateRefreshToken(String token) {
    try {
    Jwts.parserBuilder()
    .setSigningKey(REFRESH_SECRET_KEY)
    .build()
    .parseClaimsJws(token);
    return true;
    } catch (Exception e) {
    return false;
    }
    }

    // Vérification si le Token d'Accès est expiré
    public Boolean isAccessTokenExpired(String token) {
    return isTokenExpired(token);
    }


    @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

    final String authorizationHeader = request.getHeader("Authorization");
    final String refreshTokenHeader = request.getHeader("Refresh-Token");

    String jwt = null;
    String username = null;

    if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
    jwt = authorizationHeader.substring(7);
    try {
    username = jwtUtil.extractUsername(jwt);
    } catch (ExpiredJwtException e) {
    // Si le token d'accès est expiré, essayons de générer un nouveau token à partir du refresh token
    if (refreshTokenHeader != null && jwtUtil.validateRefreshToken(refreshTokenHeader)) {
    // Extraire le nom d'utilisateur à partir du refresh token
    String refreshTokenUsername = jwtUtil.extractUsername(refreshTokenHeader);
    String newAccessToken = jwtTokenRefreshService.refreshAccessToken(refreshTokenHeader, refreshTokenUsername);
    response.setHeader("New-Access-Token", newAccessToken); // Retourne le nouveau token d'accès dans les en-têtes de réponse
    } else {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.getWriter().write("Refresh token is invalid or not provided.");
    return;
    }
    } catch (Exception e) {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.getWriter().write("JWT token parsing failed.");
    return;
    }

    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
    try {
    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
    if (jwtUtil.validateAccessToken(jwt, username)) {
    UsernamePasswordAuthenticationToken authenticationToken =
    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    } else {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.getWriter().write("Invalid JWT token.");
    return;
    }
    } catch (Exception e) {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.getWriter().write("Exception in setting authentication: " + e.getMessage());
    return;
    }
    }
    }

    chain.doFilter(request, response);


    }

    Continue reading...

Compartilhe esta Página