Невозможно получить новейший токен до загрузки компонента в angular

Мне нужны соответствующие предложения для удовлетворения моей потребности, а именно:

Я реализовал систему аутентификации на основе jwt в своем угловом приложении. Когда я получаю свой новый токен и токен обновления, я создаю setTimeout, чтобы токен можно было обновить до истечения срока его действия. Но я также намеренно обновляю свой токен при загрузке приложения (в моем AppComponent), и моя проблема связана с этим случаем.

Я получаю информацию о вошедшем в систему пользователе из jwt, и проблема в том, что когда приложение загружается и выполняется обновление токена, токены сохраняются после отображения компонента, что приводит к тому, что приложение получает информацию о пользователях. предыдущий токен, а не вновь созданный.

Мы могли бы подумать об использовании преобразователей, но проблема в том, что при использовании преобразователя у меня будут запускаться два запроса токена обновления: один в моем AppComponent и один внутри преобразователя, а это не то поведение, которое мне нужно.

Итак, вот часть моего AppComponent:

     ngOnInit() {
        this.refreshToken();
        ...
      }
    
    private refreshToken() {
        this.authSvc.refreshToken().pipe(
          untilDestroyed(this),
          catchError(error => {
            if (error.status === 401) {
              this.router.navigate(['/login']);
            }
            return of();
          })
        ).subscribe();
      }

Вот часть AuthService:

    get user(): User | null {
      const token = localStorage.getItem('token');
      return token ? this.jwtHelper.decodeToken(token) : null;
    }

    refreshToken(): Observable<LoginResponse> {
        return this.http.post<LoginResponse>(
          `${environment.apiUrl}/token/refresh`,
          {refresh_token: localStorage.getItem('refresh_token')}, this.CONTEXT
        ).pipe(
          tap((res: LoginResponse) => this.storeTokens(res as LoginSuccess))
        );
      }
    
    storeTokens(data: LoginSuccess): void {
        localStorage.setItem('token', data.token);
        localStorage.setItem('refresh_token', data.refresh_token);
        this.scheduleTokenRefresh(data.token);
      }

А вот компонент, для которого мне нужны пользовательские данные (это будет не единственный компонент, которому эти данные потребуются):

export class HomeComponent implements OnInit {
  user!: User | null;
  constructor(private authSvc: AuthService) {
  }

  ngOnInit() {
    this.user = this.authSvc.user;
  }

Проблема, с которой я столкнулся, заключается в том, что компонент Home отображается до вызова storeTokens, поэтому, если пользовательские данные были обновлены на внутренней стороне с момента последнего токена, мой HomeComponent не узнает об этом, потому что он будет использовать предыдущий токен. ..

Я попробовал использовать ошибку распознавателя, она заставляет меня снова вызывать RefreshToken, но это не то, что я хочу. Мне нужно сохранить мойrefreToken внутри AppComponent и не вызывать его дважды. Вот преобразователь:

export const tokenRefreshedResolver: ResolveFn<boolean> = (route, state) => {
  return inject(AuthService).refreshToken().pipe(
    map(() => true),
    catchError(() => of(false))
  );
};

Каким будет подходящее решение?

Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
0
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я настоятельно рекомендую вам использовать поставщика APP_INITIALIZER для решения этой проблемы. Если вы с ним не знакомы, вот документация и вот мое краткое объяснение.

APP_INITIALIZER можно использовать для выполнения желаемых функций до загрузки приложения. Это означает, что вы сможете получить токен до того, как что-либо начнет отображаться, что позволит избежать вашей проблемы. Это намного лучше, чем обрабатывать его в AppComponent.

Вам необходимо добавить поставщика APP_INITIALIZER в AppModule, а затем реализовать его так, как вы хотите. Распространенным решением является использование для этого фабричной функции.

// AppModule
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { AuthService } from 'somewhere/in/you/project';

@NgModule({
  declarations: [
    AppComponent, AboutUsComponent,HomeComponent,ContactUsComponent
  ],
  imports: [
    HttpClientModule,
    BrowserModule,
    AppRoutingModule,
  ],
  // Add a custom provider
  providers: [ 
    {
      provide: APP_INITIALIZER,
      useFactory: appInit, // function returning a Promise or Observable
      deps: [AuthService] // dependencies needed for the factory func
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }


// FOR STANDALONE APPLICATION
// to it in bootstrapApplication instead of the AppModule
bootstrapApplication(AppComponent, {
  providers: [
    // ... 
    {
      provide: APP_INITIALIZER,
      useFactory: appInit,
      multi: true,
      deps: [AuthService],
    },
  ],
}).catch(err => console.error(err));

Реализовать фабричную функцию. Поместите его в отдельный файл или как функцию над объявлением NgModule. В основном в зависимости от того, насколько большим он может стать.

// Factory method file
import { AuthService } from 'somewhere/in/you/project';

// Deps defined in AppModule reflect into the parameters of your factory function. 
// You can add more if needed.
export function appInit(
  authService: AuthService,
): () => Observable<boolean> {
  // Since return type is a function, we need to return this way.
  return () => {
    authService.refreshToken().pipe(
      map(response => !!response) // Map it in some way that it returns boolean. 
      // Check if response exists, or contains some property, etc.
    )
  }
}

Большинство библиотек AUTH рекомендуют этот подход. Я уже использовал его с KeyCloak и Azure, и пользовательский интерфейс очень плавный. Однако вы можете столкнуться с немного длительным временем загрузки. Но очень просто реализовать экран загрузки прямо в index.html.

Похоже, это может быть решением моей проблемы, но есть проблема с реализацией, прежде всего, когда я сохраняю подпись appInit : () => Observable<boolean>, я получаю эту ошибку Type Observable<boolean> is not assignable to type () => Observable<boolean>, а когда я ее удаляю, я получаю эту ошибку в консоли TypeError: appInits is not a function. И, кстати, я использую автономное приложение, но у меня оно соответственно

naspy971 29.04.2024 10:51

Простите, извините, я по ошибке удалил одну строчку. Я отредактировал свой ответ, исправил проблему и предоставил код для автономного приложения.

mat.hudak 29.04.2024 10:59

Что ж, теперь, используя только authService.refreshToken() внутри возврата, он работает как шарм и решает мою проблему! Спасибо

naspy971 29.04.2024 11:06

Другие вопросы по теме