将OAuth2访问令牌配置为typescript-angular2客户端

时间:2017-05-26 19:07:48

标签: angular swagger-codegen oidc-client-js

我不完全了解如何从promise(oidc-client-js)提供OAuth2访问令牌到使用Swagger-CodeGen生成的API代码。

提供常量值很容易,但如何在下面更改以从oidc-client-js获取用户的访问令牌?我想知道“正确”的方式。将这个标记粘贴在全局变量中就足够了。

@NgModule({
  imports: [
    CommonModule,
    ApiModule.forConfig(() => new Configuration({
      accessToken: 'my-access-token' //this can also be a () => string function
    }))
  ],

在使用OnInit的普通组件中,我可以从oidc-client的UserManager实例的promise中获取令牌。让这两件装在一起让我感到困惑。一个似乎是静态配置,另一个需要订阅单身人士的承诺。

this.userSubscription = this.authService.getUser().subscribe((user) => {
    if (user) {
        this.access_token = user.access_token;
    }
});

对我做错事情的任何更正也将受到赞赏。这是我使用Angular的第一个原型。

更新

在应用Ben的建议并花时间了解APP_INITIALIZER(标记为实验且非常稀疏的imo)之后,感觉就像是矫枉过正。我结束了以下Configuration类的自定义提供程序,它被注入到使用Swagger-CodeGen生成的TypeScript-Angular2服务代码中:

providers: [
  AuthService,
  AuthGuardService,
  {
    provide: Configuration,
    useFactory: (authSvc: AuthService) => new Configuration({accessToken: authSvc.getAccessToken.bind(authSvc)}),
    deps: [AuthService],
    multi: false
  }
]

我更改了AuthService以在服务上存储用户的最新access_token。从Swagger-CodeGen生成的代码调用getAccessToken()方法,并返回最新的jwt,以便在HTTP头中使用。感觉很干净,很有效。如果(以及为什么)这是解决我问题的错误方法,请告诉我。

2 个答案:

答案 0 :(得分:1)

您需要使用APP_INITIALIZER来引导您的API令牌,请查看我的回答Pass web application context to Angular2 Service以查看如何执行此操作的示例。

答案 1 :(得分:0)

我认为这是个招摇的代码错误,属性签名应该是

accessToken?: string | (() => Promise<string>);

或者简单地

accessToken?: (() => Promise<string>);

原因是访问令牌到期,因此每次进行呼叫时, 客户端应检查令牌是否已过期,如果已过期,则请求新令牌(令牌刷新),这意味着HTTP查询,因此,promise是处理访问令牌的最佳选择。如果您检查Firebase的Javascript API,您会发现User.getIdToken()返回一个promise,因为它首先检查current是否过期,如果是则请求新的过期。

因此,我正在使用的解决方案是Angular的HTTP拦截器:

import { Injectable } from '@angular/core';
import {
  HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';
import { AngularFireAuth } from '@angular/fire/auth';
import * as firebase from 'firebase/app';
import { from } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

import { environment } from '../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class UsersApiAuthInterceptorService implements HttpInterceptor {

  constructor(private afAuth: AngularFireAuth) { }

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    if (req.url.startsWith(environment.usersAPIBasePath) && this.afAuth.auth.currentUser) {
      return from(this.afAuth.auth.currentUser.getIdToken()).pipe(mergeMap(token => {
        console.log('UsersApiAuthInterceptorService got token', token);
        const authReq = req.clone({
          setHeaders: {
            Authorization: `Bearer ${token}`
          }
        });
        return next.handle(authReq);
      }));
    }
    else {
      return next.handle(req);
    }
  }
}

我对该解决方案不满意的是,它将拦截所有HTTPClient调用,这就是为什么我必须添加if (req.url.startsWith(environment.usersAPIBasePath) ...的原因,但是如果所有HTTPClient调用都将发送到您的API,则可以删除该部分有条件的。

这是该应用程序的提供商进入app.module.ts的方式:

  providers: [
    ...
    { provide: BASE_PATH, useValue: environment.usersAPIBasePath },
    { provide: HTTP_INTERCEPTORS, useClass: UsersApiAuthInterceptorService, multi: true },
  ],