Я пытаюсь обновить токен JWT после истечения срока его действия, используя AuthInterceptor
и ErrorInterceptor
, два Angular HttpInterceptor
метода.Я отправляю токен через заголовки AuthInterceptor
запроса:
export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const Token = this.authService.getToken(); // localStorage.getItem('token', 'provider', ...)
if (Token) {
// if token exists (logged), send custom header values with request
const authReqRepeat = req.clone({
headers: req.headers
.set('Authorization', 'Bearer ' + Token.token)
.set('X-Auth-Provider', Token.provider)
});
return next.handle(authReqRepeat);
} else {
return next.handle(req);
}
}
}
RefreshToken
метод вызывается в ErrorInterceptor
, он генерирует новый объект токена, который будет использоваться в запросе, который не выполнен (из-за истечения срока действия токена):
export class ErrorInterceptor implements HttpInterceptor {
constructor(private alertService: AlertService, private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(catchError((err: HttpErrorResponse) => {
if (err.status === 401) {
this.authService.logout();
} else if (err.status === 403) {
// Token expired error, so I need to generate a new token
const Token = this.authService.getToken();
return this.authService
.refreshToken(Token.refreshToken)
.pipe(flatMap((newToken) => {
// store new access token on local storage
localStorage.setItem('token', newToken.token);
localStorage.setItem('expiration', newToken.expiresIn);
// retry request with new access token generated
return next.handle(req.clone({
headers: req.headers
.set('Authorization', 'Bearer ' + newToken.token)
.set('X-Auth-Provider', Token.provider)
}));
}));
} else {
if (err.error.message) {
this.alertService.error(err.error.message);
} else {
this.alertService.error('Não foi possível completar a requisição.');
}
return throwError(err);
}
}));
}
}
Мне пришлось использовать HttpBackEnd
, чтобы остановить бесконечный цикл через AuthInterceptor
после вызова нового запроса HttpErrorInterceptor
:
export class AuthService {
constructor (private http: HttpClient, private handler: HttpBackend) {}
refreshToken(token: string) {
const http = new HttpClient(this.handler);
return http.post<{token: string, expiresIn: string}>(`${BACKEND_URL}/refreshtoken`, {token: token});
}
}
Node.js запрос:
//routes
router.post('/refreshtoken', auth, user.refreshToken);
// controllers
module.exports = async(req, res, next) => {
try {
if (req.headers['x-auth-provider'] === 'jwt') {
// refresh token
const token = req.headers.authorization.split(' ')[1];
const decodedToken = await jwt.verify(token, process.env.JWT_Key);
req.userData = {id: decodedToken.userId, email: decodedToken.email, role: decodedToken.role};
}
next();
} catch (error) {
if (error.name && error.name === 'TokenExpiredError') {
res.status(403).json({success: false, message: 'Token inválido.'});
}
res.status(401).json({success: false, message: 'Autenticação falhou.'});
}
};
exports.refreshToken = wrap(async(req, res, next) => {
const user = await User.findOne({ refresh_token: req.body.token });
if (user) {
const newToken = await jwt.sign(
{ email: user.email, userId: user._id, role: user.role },
process.env.JWT_Key,
{ expiresIn: '15m' });
const expiresAt = moment().add(900, 'second');
res.status(200).json({success: true, token: newToken, expiresIn: JSON.stringify(expiresAt.valueOf())});
} else {
res.status(401).json({success: false, message: 'Autenticação falhou.'});
}
});
Кажется, это работает, но я что-то напутал, потому что мой сервер Node.js выдает некоторые ошибки: UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
.
Что я делаю неправильно