我不完全了解如何从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头中使用。感觉很干净,很有效。如果(以及为什么)这是解决我问题的错误方法,请告诉我。
答案 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 },
],