Веб-токены JSON (JWT) — это популярная стратегия аутентификации, используемая в веб-приложениях для безопасной передачи информации между сторонами в виде объектов JSON.
Аутентификация JWT работает следующим образом: когда пользователь входит в систему, сервер генерирует JWT и отправляет его клиенту. Затем клиент сохраняет JWT, обычно в локальном хранилище или в файле cookie, и включает его в заголовок последующих запросов к серверу. Затем сервер проверяет подпись JWT, декодирует полезную нагрузку и использует информацию в полезной нагрузке для аутентификации и авторизации пользователя.
На мой взгляд, реализовать стратегию аутентификации JWT несложно, за исключением одной сложной части — стратегии токена обновления. Поскольку веб-токен JSON имеет срок действия, у клиента должен быть механизм для запроса нового токена после того, как запрос вернет статус 401 «Неавторизованный». По сути это будет работать так:
Когда клиент получает ответ о статусе 401, он должен запросить новый токен доступа, а затем повторить исходный ответ с новым токеном.
Такое поведение может быть сложно реализовать, особенно если вы обрабатываете состояние аутентификации с помощью библиотеки хранилища, такой как NgRx или Redux.
В этой статье я покажу, как реализовать эту стратегию с помощью Angular и NgRx, и поделюсь репозиторием шаблонов GitHub, который включает в себя проект с JWT-аутентификацией, реализованный в бэкэнде NestJs, вместе с настройкой Nx Monorepo, готовой для повышения вашего нового полноценный проект.
Я не буду делиться здесь всем кодом, только основные части, вместо этого, если вы хотите заглянуть глубже, вам следует просмотреть репозиторий GitHub.
Во-первых, нужно определить форму состояния авторизации, и это просто, это будут просто два строковых поля, подобные этому:
interface AuthInfo { accessToken: string; refreshToken: string; }
Затем нам нужно будет использовать функцию Angular HTTP Interceptors, чтобы поймать ошибку запроса 401, вы должны принять во внимание, что следующий код — это способ, которым я бы его реализовал, но если у вас есть другое мнение, не стесняйтесь, дайте мне знать.
По сути, внутри HTTP-перехватчика нам нужно будет выбрать информацию об аутентификации в хранилище, и когда он имеет токен, отличный от того, что был в исходном запросе, это означает, что запрос токена обновления завершен и он был успешным, и тогда мы можем повторить запрос с новым токеном доступа, код будет выглядеть следующим образом:
function refreshTokenInterceptor(req, next) { return next(req).pipe( catchError((err) => { if (err.status === 401) { // refresh the token return of(store.dispatch(AuthActions.refreshTokenRequest())).pipe( switchMap(() => store.select(AuthFeature.selectAuthInfo)), // only emits when the store has the new accessToken first( (info) => info.accessToken !== req.headers.get("authorization")?.split(" ")[1] ), switchMap((info) => { // clone the request and return a new observable to the switch map operator return next( req.clone({ headers: req.headers.set( "Authorization", `Bearer ${info.accessToken}` ), }) ); }) ); } return of(err) }) ) }
Взглянув на вкладку сети браузера, вы заметите, что стратегия токена обновления работает должным образом.
Надеюсь, эта статья окажется полезной, как и репозиторий шаблонов GitHub.