Как обновить токен JWT с помощью Angular Http Interceptor? - PullRequest
0 голосов
/ 04 октября 2018

Я пытаюсь обновить токен 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.

Что я делаю неправильно

...