JWT in cookies session how to refresh token triggering process from client side when navigating

Discussão em 'Angular' iniciado por Enrique GF, Outubro 4, 2024.

  1. Enrique GF

    Enrique GF Guest

    Good day developers. I'm really new to working with JWT stored in cookies for session management, and I'm trying to figure out how to implement the refresh token process on the client side.

    Basically, this application uses Node.js and Express for the backend, and Angular for the frontend. On my server side, I have a controller called "Token Controller," where tokens from the request headers are expected to be present and refreshed using specific methods. Here's an example of the refreshTokenValidation function:

    public async refreshTokenValidation(
    request: Request,
    response: Response
    ): Promise<Response> {
    const refreshTokenInCookies = request.cookies["JWTREF"];

    if (!refreshTokenInCookies) {
    return response
    .status(400)
    .json(
    RESPONSE_HANDLER.responseHandler(
    false,
    "There is not refresh token available",
    null,
    null,
    null,
    response
    )
    );
    }

    const { userId, userInfo } = JWT_HELPER.getRefreshTokenInfo(
    refreshTokenInCookies
    );

    if (!userId) {
    return response
    .status(400)
    .json(
    ERROR_EXCEPTION_HANDLER.errorHandler(
    false,
    "Token Credentials don´t contain user ID"
    )
    );
    }

    if (!userInfo) {
    return response
    .status(400)
    .json(
    ERROR_EXCEPTION_HANDLER.errorHandler(
    false,
    "Token Credentials don´t contain user INFO"
    )
    );
    }

    await JWT_HELPER.generateJWTandJWTRef({ userId, userInfo });

    return response
    .status(200)
    .json(
    RESPONSE_HANDLER.responseHandler(
    true,
    "Token updated",
    null,
    null,
    null,
    response
    )
    );
    } catch (error) {
    return response
    .status(500)
    .json(ERROR_EXCEPTION_HANDLER.errorHandler(false, error as string));
    }
    }


    My cors in the root app config are established like this:

    app.use(
    cors({
    origin: process.env.GOSSIPI_FRONT_ALLOWED_ORIGIN,
    methods: JSON.parse(process.env.ALLOWED_HTTPS_METHODS as string),
    credentials: true,
    allowedHeaders: [
    "Origin",
    "X-Requested-With",
    "Content-Type",
    "Accept",
    "Authorization",
    ],
    })
    );


    This token is set during a specific API action, like logging in a user. If the login is successful, the response includes the tokens in cookies (Set-Cookie header), so the client side will receive this:

    METHOD LOGIN USER(it has a response handler class where in cookies are settled)

    public async loginUser(
    request: Request,
    response: Response
    ): Promise<Response> {
    const data = request.body;

    try {
    const { searchString, password } = data;
    const userWithValidCredentials =
    await FIELDS_VALIDATOR.loginCredentialsAreValid(searchString, password);

    if ((userWithValidCredentials as UserInterface) === undefined) {
    return response
    .status(400)
    .json(
    ERROR_EXCEPTION_HANDLER.errorHandler(
    false,
    "This user name,email, or nick is incorrect"
    )
    );
    }

    const { token, refreshToken } =
    (await FIELDS_VALIDATOR.jwtGeneratedOnUserValidcredentials(
    userWithValidCredentials
    )) as Record<string, string | null | undefined>;

    if (!token || !refreshToken) {
    return response
    .status(400)
    .json(
    ERROR_EXCEPTION_HANDLER.errorHandler(
    false,
    "The token generated for validation is not valid"
    )
    );
    }

    return response
    .status(200)
    .json(
    RESPONSE_HANDLER.responseHandler(
    true,
    "User has been logged",
    userWithValidCredentials,
    token,
    refreshToken,
    response
    )
    );
    } catch (error) {
    return response
    .status(500)
    .json(
    ERROR_EXCEPTION_HANDLER.errorHandler(
    false,
    "An error occurred while login the user",
    { error }
    )
    );
    }
    }


    RESPONSE HANDLER CLASS(In here is where cookies arre settled as part of the response of any successful call) :

    import { Response } from "express";
    import { ResponseInterface } from "../root-interfaces/response-interface/response-interface.interface";

    export class ResponseHandlerClass {
    private static responseHandlerInstance: ResponseHandlerClass;

    public static getResponseHandlerInstance(): ResponseHandlerClass {
    if (!this.responseHandlerInstance) {
    this.responseHandlerInstance = new ResponseHandlerClass();
    }

    return this.responseHandlerInstance;
    }

    public responseHandler(
    status: boolean,
    message: string,
    data?: undefined | null | Record<string, any>,
    token?: undefined | null | string,
    refreshToken?: undefined | null | string,
    response?: Response | null
    ): ResponseInterface {
    this.responseCookiesHandler(response ?? null, token, refreshToken);

    return { status, message, data };
    }

    public responseCookiesHandler(
    response: Response | null,
    token: undefined | null | string,
    refreshToken: undefined | null | string
    ): void {
    if (token && refreshToken && response) {
    response?.cookie("JWT", token, {
    httpOnly: true,
    secure: true,
    sameSite: "strict",
    maxAge: 32400000, // 9 hours in milliseconds in order to mach token expirein time,
    domain: "",
    });

    response.cookie("JWTREF", refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: "strict",
    maxAge: 129600000, // 36 hours in milliseconds in order to mach refresh token expirein time
    domain: "",
    });
    }
    }
    }



    When I log in a user successfully on the client side, I can see the cookies in the headers, as shown below:

    [​IMG]

    Client-Side (Angular) Token Refresh Now, let’s say I have several tabs in my Angular app, and anytime I enter a view, a guard is triggered to check the validity of the token. Here’s the guard:

    import { HttpClient } from '@angular/common/http';
    import { inject } from '@angular/core';
    import {
    ActivatedRouteSnapshot,
    CanActivateFn,
    Router,
    RouterStateSnapshot,
    } from '@angular/router';
    import { Observable, catchError, map, of } from 'rxjs';
    import { environment } from 'src/environments/environment';

    export const ServicesGuard: CanActivateFn = (
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
    ) => {
    const httpClient = inject(HttpClient);
    const router = inject(Router);

    const refreshToken = (): Observable<boolean> => {
    return httpClient
    .post<boolean>(
    `${environment.SERVER_URL_HOST_CONNECTION}/token/token_validation/refresh`,
    {},
    { withCredentials: true }
    )
    .pipe(
    map(() => true),
    catchError(() => {
    router.navigate(['/goss_login']);

    return of(false);
    })
    );
    };

    return refreshToken();
    };



    and in my routes are set like this:

    import { NgModule } from '@angular/core';
    import { RouterModule, Routes } from '@angular/router';
    import { TabConfigPage } from './xxxxe';
    import { ServicesGuard } from 'xxxxx';

    const routes: Routes = [
    {
    path: '',
    component: TabConfigPage,
    canActivate: [ServicesGuard],
    },
    ];

    @NgModule({
    imports: [RouterModule.forChild(routes)],
    exports: [RouterModule],
    })
    export class TabConfigPageRoutingModule {}



    However, this is currently not working because I'm not passing any token to verify its validity, and I’m unsure how to proceed. I don’t think it's a good idea to store the token on the client side.

    Any ideas or support on understanding this issue?

    How to access those token to call the back end and refresh them or verify them

    Continue reading...

Compartilhe esta Página