Angular客户端Cookie存储JWT的令牌刷新流程实现咨询
我刚接触将JWT存储在Cookie中的会话管理方案,现在要在Angular前端实现令牌刷新流程。技术栈为后端Node.js+Express,前端Angular。
后端已完成Token Controller的refreshTokenValidation方法用于刷新令牌,CORS配置开启了credentials: true,登录成功后会通过Set-Cookie响应头把JWT(键名JWT)和刷新令牌(键名JWTREF)存入客户端Cookie。
目前Angular端设置了路由守卫ServicesGuard,进入视图时会调用后端刷新令牌接口但未生效。我不想在客户端存储令牌,不清楚如何访问这些Cookie里的令牌来完成后端的刷新或验证,求解决方案。
后端相关代码
refreshTokenValidation方法
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)); } }
CORS配置
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", ], }) );
登录接口loginUser方法
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 } ) ); } }
ResponseHandlerClass类
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: "", }); } } }
Angular前端相关代码
ServicesGuard路由守卫
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(); };
路由配置
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 {}
解决方案
1. 核心逻辑确认
你不需要手动访问Cookie里的令牌——Angular请求中设置{ withCredentials: true }后,浏览器会自动把当前域名下的HttpOnly Cookie(包括JWT和JWTREF)携带到后端请求中,完全满足你“不在客户端存储令牌”的需求。
2. 修复后端刷新接口的关键bug
当前refreshTokenValidation方法调用responseHandler时,传入的token和refreshToken为null,导致responseCookiesHandler不会执行,新生成的令牌无法写入Cookie,这是守卫失效的核心原因。修正代码:
// 刷新接口中生成新令牌后,传入responseHandler const { token: newToken, refreshToken: newRefreshToken } = await JWT_HELPER.generateJWTandJWTRef({ userId, userInfo }); return response .status(200) .json( RESPONSE_HANDLER.responseHandler( true, "Token updated", null, newToken, newRefreshToken, response ) );
3. 排查Cookie配置问题
- Domain配置:后端
ResponseHandlerClass中Cookie的domain为空,生产环境需设置为前端实际域名(如.example.com适配子域名),否则浏览器可能拒绝存储Cookie。 - Secure属性:开发环境(HTTP)下需把
secure改为false,仅HTTPS环境才启用secure: true。 - SameSite属性:若前后端为不同子域名,建议把
sameSite: "strict"改为sameSite: "lax",避免跨站请求时Cookie不被携带。
4. 验证流程
- 登录后在浏览器开发者工具
Application > Cookies中确认JWT、JWTREF已存储,且属性符合预期。 - 触发路由守卫时,查看
Network面板的请求头,确认包含Cookie: JWT=xxx; JWTREF=xxx。 - 查看刷新接口的响应头,确认存在新的Set-Cookie,更新JWT和JWTREF的值。
内容的提问来源于stack exchange,提问作者Enrique GF
相关产品推荐
相关产品推荐

