You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular项目用oidc认证时出现token未定义断言错误求助

Angular OIDC认证断言错误:ASSERTION ERROR: token must be defined

问题详情

项目采用OIDC(OpenID)实现身份认证,修改过bootstrap组件为MainRootComponent,第一个运行的组件是HomeMainComponent(包含OIDC核心逻辑),控制台抛出断言错误:

ASSERTION ERROR: token must be defined [Expected=> null != undefined <=Actual]

错误截图

相关代码

Home-main 组件代码

import { Component, Injector, OnInit } from '@angular/core';
import { AppComponentBase } from '../utils';
import { StorageService } from '../services/storage.service';
import { Title } from '@angular/platform-browser';
import { UserManagementService } from '../pages/user-management/user-management.service';
import { AbpService } from '../config/abp/licalization.service';
import { OAuthService } from 'angular-oauth2-oidc';
import { DataService } from '../services/data.service';
import { environment } from 'src/environments/environment.prod';
import { filter, map } from 'rxjs';
import { authCodeFlowConfig } from '../config/authCodeFlowConfig';
import { IApplicationConfigurationDto } from '../models/identityTypes';

@Component({
  selector: 'app-home-main',
  templateUrl: './home-main.component.html',
  styleUrls: ['./home-main.component.scss'],
})
export class HomeMainComponent extends AppComponentBase implements OnInit {

  loadFirstLoginProject: boolean = false;
  favIcon: HTMLLinkElement = document.querySelector('#favIcon');
  favIconAddress: string = environment.localization.favIcon_Project;

  constructor(inject: Injector, private storageService: StorageService, private titlePage: Title, private userManagementService: UserManagementService, private _abpService: AbpService,
    private dataService: DataService, private _oAuthService: OAuthService) {
    super(inject)
    this.getConfigProject();
    this._oAuthService.configure(authCodeFlowConfig);
    this._oAuthService.loadDiscoveryDocumentAndLogin();
    this._oAuthService.setupAutomaticSilentRefresh();
    this._oAuthService.events.pipe(filter((e) => e.type === 'token_received')).subscribe((_) => {
      if (!this.loadFirstLoginProject) {
        window.location.reload();
      }
    });
    const lang = this.getCurrentLang();
    if (lang === 'ku') {
      document.body.classList.add('ku-language');
    }
    this.titlePage.setTitle(environment.localization.nameTitleProject)
    this.favIcon.href = this.favIconAddress;
  }

  getConfigProject() {
    if (this.storageService.retrieve('access_token')) {
      this.loadFirstLoginProject = true;
    }
  }

  public OnInit(): void {
    this.dataService.getApplication().pipe(map((response: IApplicationConfigurationDto) => {
      this.translate.setDefaultLang(response.setting.values['Abp.Localization.DefaultLanguage'].replaceAll('&quot;', ''));
      this.translate.use(response.setting.values['Abp.Localization.DefaultLanguage'].replaceAll('&quot;', ''));
      this.securityService.subjectUpdateUserByToken.next(response);
      this._abpService.init(response);
      this._abpService.setCurrentLang(response.setting.values['Abp.Localization.DefaultLanguage'].replaceAll('&quot;', ''));
      sessionStorage.setItem('currentLang', response.setting.values['Abp.Localization.DefaultLanguage']);
      this.securityService.updateSubjectUserByToken(response);
      this.securityService.currentUser = response.currentUser;
      localStorage.setItem('photoUserImage', this.convertAddressImageUser(''));
      if (this._oAuthService.hasValidAccessToken()) {
        this.userManagementService.getAllUsers('', 0, 999).subscribe(x => {
          this.securityService.userList = x.items.map(x => {
            x.fullName = `${x.name} ${x.surname}`
            return x;
          });
          this.securityService.currentUser = response.currentUser;
        }, error => {
          console.log(error)
        });
        this.securityService.dataApplicationService = response;
        return true
      }

      return false;
    }, err => err));
  }

}

package.json 依赖配置

{
  "private": true,
  "dependencies": {
    "@angular/animations": "^16.2.0",
    "@angular/common": "^16.2.0",
    "@angular/compiler": "^16.2.0",
    "@angular/core": "^16.2.0",
    "@angular/forms": "^16.2.0",
    "@angular/platform-browser": "^16.2.0",
    "@angular/platform-browser-dynamic": "^16.2.0",
    "@angular/router": "^16.2.0",
    "@ngx-translate/core": "^15.0.0",
    "@ngx-translate/http-loader": "^8.0.0",
    "angular-oauth2-oidc": "^15.0.1",
    "bootstrap": "^5.3.2",
    "font-awesome": "^4.7.0",
    "html-to-image": "^1.11.11",
    "html-to-pdfmake": "^2.5.1",
    "jalali-moment": "^3.3.11",
    "jspdf": "^2.5.1",
    "lodash": "^4.17.21",
    "moment": "^2.29.4",
    "ngx-persian": "^2.0.1",
    "ngx-spinner": "^16.0.2",
    "ngx-toastr": "^17.0.2",
    "primeicons": "^6.0.1",
    "primeng": "^16.9.1",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.13.0"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^16.2.1",
    "@angular/cli": "~16.2.1",
    "@angular/compiler-cli": "^16.2.0",
    "@angular/localize": "^16.2.12",
    "@types/jasmine": "~4.3.0",
    "jasmine-core": "~4.6.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "typescript": "~5.1.3"
  }
}

app.module.ts 配置

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { MainRootComponent } from './main-root/main-root.component';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { SharedModule } from './shared/shared.module';
import { APP_BASE_HREF } from '@angular/common';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { StorageService } from './services/storage.service';
import { DataService } from './services/data.service';
import { SecurityService } from './services/security.service';
import { ToastrModule, ToastrService } from 'ngx-toastr';
import { OAuthModule } from 'angular-oauth2-oidc';
import { MessageService } from 'primeng/api';
@NgModule({
  declarations: [
    AppComponent,
    MainRootComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    SharedModule,
    HttpClientModule,
    ToastrModule.forRoot(),
    OAuthModule.forRoot(),
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    }),
  ],
  bootstrap: [MainRootComponent],
  providers: [
    { provide: APP_BASE_HREF, useValue: '/' },
    StorageService,
    ToastrService,
    MessageService,
    DataService,
    SecurityService
  ],
})
export class AppModule { }
export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
  return new TranslateHttpLoader(http);
}

错误原因分析

  1. 生命周期钩子错误:组件中定义的OnInit()方法不符合Angular规范,正确的生命周期钩子应为小写开头的ngOnInit(),导致初始化逻辑未被执行,OIDC认证与后续业务逻辑时序混乱。
  2. OIDC初始化时机过早:在构造函数中直接调用loadDiscoveryDocumentAndLogin()和setupAutomaticSilentRefresh(),此时组件未完成初始化,可能在token未获取时触发依赖token的操作。
  3. HTTP请求未订阅:dataService.getApplication()的Observable未调用subscribe(),导致请求未执行,后续依赖该接口返回值的逻辑无数据可用,间接引发token校验错误。
  4. 静默刷新逻辑冲突:setupAutomaticSilentRefresh()在无有效token时尝试刷新,触发库内部的断言检查,抛出token未定义错误。

修复方案

1. 修正生命周期钩子

将public OnInit(): void改为public ngOnInit(): void,确保Angular能正确调用初始化逻辑。

2. 调整OIDC初始化时序

将OIDC相关逻辑从构造函数移到ngOnInit中,并在认证成功后再执行业务初始化:

constructor(
  inject: Injector,
  private storageService: StorageService,
  private titlePage: Title,
  private userManagementService: UserManagementService,
  private _abpService: AbpService,
  private dataService: DataService,
  private _oAuthService: OAuthService
) {
  super(inject);
  this.getConfigProject();
  const lang = this.getCurrentLang();
  if (lang === 'ku') {
    document.body.classList.add('ku-language');
  }
  this.titlePage.setTitle(environment.localization.nameTitleProject);
  this.favIcon.href = this.favIconAddress;
}

ngOnInit(): void {
  // 配置OIDC
  this._oAuthService.configure(authCodeFlowConfig);
  
  // 加载发现文档并登录,认证成功后执行后续逻辑
  this._oAuthService.loadDiscoveryDocumentAndLogin().then(() => {
    if (this._oAuthService.hasValidAccessToken()) {
      this.initApplicationConfig();
    }
  });

  this._oAuthService.setupAutomaticSilentRefresh();
  
  this._oAuthService.events.pipe(filter(e => e.type === 'token_received')).subscribe(() => {
    if (!this.loadFirstLoginProject) {
      window.location.reload();
    }
  });
}

// 提取业务初始化逻辑,确保在认证成功后执行
private initApplicationConfig() {
  this.dataService.getApplication().pipe(map((response: IApplicationConfigurationDto) => {
    const defaultLang = response.setting.values['Abp.Localization.DefaultLanguage'].replaceAll('&quot;', '');
    this.translate.setDefaultLang(defaultLang);
    this.translate.use(defaultLang);
    
    this.securityService.subjectUpdateUserByToken.next(response);
    this._abpService.init(response);
    this._abpService.setCurrentLang(defaultLang);
    sessionStorage.setItem('currentLang', response.setting.values['Abp.Localization.DefaultLanguage']);
    
    this.securityService.updateSubjectUserByToken(response);
    this.securityService.currentUser = response.currentUser;
    localStorage.setItem('photoUserImage', this.convertAddressImageUser(''));
    
    if (this._oAuthService.hasValidAccessToken()) {
      this.userManagementService.getAllUsers('', 0, 999).subscribe({
        next: x => {
          this.securityService.userList = x.items.map(item => ({
            ...item,
            fullName: `${item.name} ${item.surname}`
          }));
          this.securityService.currentUser = response.currentUser;
        },
        error: err => console.log(err)
      });
      this.securityService.dataApplicationService = response;
      return true;
    }
    return false;
  })).subscribe(); // 必须调用subscribe才会执行HTTP请求
}

3. 检查OIDC配置

确认authCodeFlowConfig中的clientId、redirectUri、issuer等参数与OIDC服务端配置一致,错误配置会导致无法获取有效token。

4. 增加token校验防护

在所有依赖token的操作前,先通过this._oAuthService.hasValidAccessToken()校验token有效性,避免在无token时触发错误逻辑。

内容的提问来源于stack exchange,提问作者davood beheshti

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 23:29:52