【问题标题】:Okta Angular OpenID - auth guard preventing tokens from being consumed during callbackOkta Angular OpenID - 身份验证保护防止令牌在回调期间被消耗
【发布时间】:2017-11-13 06:41:13
【问题描述】:

我正在学习这个教程,这是一个很棒的教程!它展示了如何使用 Okta 进行身份验证。

https://developer.okta.com/blog/2017/04/17/angular-authentication-with-oidc

本教程有一个分配给根路由的HomeComponent,并根据用户是否登录显示或不显示元素。因此,在app.component.ts 中,您可以捕获要存储的令牌在构造函数中使用以下部分从 url 存储:

this.oauthService.loadDiscoveryDocument().then(() => {
    this.oauthService.tryLogin({});
}

在auth.guard.ts 中,您可以:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    if (this.oauthService.hasValidIdToken()) {
        return true;
    }

    this.router.navigate(['/home']);
    return false;
}

使用此路由配置:

const appRoutes: Routes = [
    { path: 'search', component: SearchComponent, canActivate: [AuthGuard] },
    { path: 'edit/:id', component: EditComponent, canActivate: [AuthGuard]},
    { path: 'home', component: HomeComponent},
    { path: '', redirectTo: 'home', pathMatch: 'full' },
    { path: '**', redirectTo: 'home' }
];

我的问题

我的设置稍有不同,而且一切的时间安排都不正确。 - 我有一个LoginComponent,如果您未通过身份验证,您将被重定向到该地址。 - 我将根路由重定向到AuthGuarded 路由。 - 当我使用 Okta 登录时,this.oauthService.tryLogin({}) 没有及时运行以阻止 AuthGuard 将我重定向到 LoginComponent。这会导致包含令牌的 url 部分在我尝试使用它们之前消失在存储中。

这是我所拥有的:

app.component.ts

constructor(
    ...
    private oauthService: OAuthService) {
    ...
    this.oauthService.loadDiscoveryDocument().then(() => {
      this.oauthService.tryLogin({});
    });
}

auth.guard.ts

canActivate(
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
  if (!this.oauthService.hasValidIdToken()) {
    this.router.navigate(['/login']);
  }
  return true;
}

app-routing.module.ts

const routes: Routes = [
  { path: '', redirectTo: '/projects', pathMatch: 'full' },
  { path: 'login', component: LoginComponent },
  { path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] },
  { path: 'help', component: HelpComponent, canActivate: [AuthGuard] },
  { path: 'settings', component: SettingsComponent, canActivate: [AuthGuard] },
  { path: 'contact', component: ContactComponent },
  { path: '**', component: NotFoundComponent }
];

projects-routing.module.ts

const routes: Routes = [
{ 
  path: 'projects', 
  component: ProjectsComponent, 
  canActivate: [AuthGuard],
  children: [
    ...
  ]
}

如您所见,当我去我的 Okta 站点输入我的用户名和密码时,我被重定向到我的应用程序的根目录,从 loadDiscoveryDocument() 返回的 Promise 已订阅,但 @987654344 @ 将我重定向回登录页面,在我可以让 OAuthService 为我收集并将其存储在存储之前,从 url 中丢失了 id_token 等。

我的问题

有没有办法让它工作而无需更改我的应用程序的路由结构?根据登录状态,我不需要充当“LoginComponent”和“HomeComponent”的“HomeComponent”。

【问题讨论】:

  • 你好杰克。如果您将 this.oauthService.tryLogin({}); 从构造函数中移到 ngOnInit() 方法中(并在您的类上实现 OnInit),是否有帮助?
  • Matt,它没有帮助,因为在解决承诺并调用 tryLogin 之前,是身份验证守卫将我重定向到登录组件。我唯一能想到的就是创建一个回调组件,让 okta 重定向到该组件,保留我的令牌,然后使用角度路由器服务重定向到 root。我只是希望有一个更清洁的解决方案。不过,理论上,在设置我的身份时使用的回调组件会起作用,是吗?
  • 我刚刚意识到,这可能有助于表明项目模块是定义项目路由的地方,并且 /projects 受到auth.guard 的保护
  • 你能用 Okta 的 Auth SDK 代替吗?这允许您创建自己的登录表单而不是重定向。这在您引用的同一篇博客文章中有所介绍。 developer.okta.com/blog/2017/04/17/…
  • 我会试一试。无论如何,这就是我接下来要做的事情。感谢您的建议。

标签: angular authentication redirect okta okta-api


【解决方案1】:

不幸的是,我无法使用重定向的登录按钮来使用我的应用程序路由的组织方式。但是使用 JavaScript SDK,我有了更多的灵活性,并且感谢 @MattRaible 在 Okta 开发人员页面上频繁发布的博客文章,我能够想出一些对我来说很有效的东西。花了很多时间调试 sdk 来弄清楚可以得到我想要的操作的顺序,所以希望这对那里的人有所帮助:

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

import { OAuthService } from 'angular-oauth2-oidc/dist';

declare let OktaAuth: any;

@Injectable()
export class AuthenticationService {

  discoveryDocumentLoaded: boolean;

  constructor(
    private oauthService: OAuthService,
    private router: Router) { }

  init() {
    this.oauthService.redirectUri = window.location.origin;
    this.oauthService.clientId = '<client-id>';
    this.oauthService.scope = 'openid profile email';
    this.oauthService.oidc = true;
    this.oauthService.issuer = '<preview-path>';
    this.oauthService.loadDiscoveryDocument()
      .then(() => {
        this.discoveryDocumentLoaded = true;
        this.oauthService.tryLogin({});
      });
  }

  logOut() {
    this.oauthService.logOut();
    this.router.navigate(['/login']);
  }

  loginWithPassword(username: string, password: string) {
    this.oauthService.createAndSaveNonce().then(nonce => {
      const authClient = new OktaAuth({
        url: '<preview-path>'
      });
      authClient.signIn({
        username: username,
        password: password
      })
        .then(response => {
          if (response.status === 'SUCCESS') {
            authClient.token.getWithoutPrompt({
              clientId: '<client-id>',
              responseType: ['id_token', 'token'],
              scopes: ['openid', 'profile', 'email'],
              sessionToken: response.sessionToken,
              nonce: nonce,
              redirectUri: window.location.origin
            })
              .then(tokens => {
                localStorage.setItem('access_token', tokens[1].accessToken);
                this.oauthService.processIdToken(tokens[0].idToken, tokens[1].accessToken);
                this.router.navigate(['/']);
              })
              .catch(console.error);
          } else {
            throw new Error('We cannot handle the ' + response.status + ' status');
          }
        })
        .fail(console.error);
    });
  }

  loadUserProfile() {
    const returnFunc = () => this.oauthService.loadUserProfile()
      .catch(console.log);

    if (this.discoveryDocumentLoaded) {
      return returnFunc();
    } else {
      return this.oauthService.loadDiscoveryDocument()
        .then(returnFunc);
    }
  }

  isLoggedIn() {
    return this.oauthService.hasValidIdToken() && this.oauthService.getIdentityClaims()
  }

}

这是我在应用程序中使用服务的方式:

app.component.ts

export class AppComponent implements OnInit {
    ...
    constructor(
        ...
        private _auth: AuthenticationService) {
        ...
    }

    ngOnInit() {
        this._auth.init();
    }
    ...
}

profile.component.ts

import { Component, OnInit } from '@angular/core';

import { OktaProfile } from 'app/okta-profile';
import { AuthenticationService } from 'app/authentication.service';

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.scss']
})
export class ProfileComponent implements OnInit {

  profile: OktaProfile;

  constructor(private _auth: AuthenticationService) { }

  ngOnInit() {
    this._auth.loadUserProfile()
      .then(oktaProfile => this.profile = <OktaProfile>oktaProfile);
  }

}

【讨论】:

  • 我在您的代码中看到的几件事:import { OAuthService } from 'angular-oauth2-oidc/dist'; 应该是 import { OAuthService } from 'angular-oauth2-oidc'; 另外,init() 是如何调用的?如果是ngOnInit 并且您实现了OnInit,我会理解,但我不使用此设置。当然,最重要的是你让它工作。干得好!
  • 好吧,我会从最后删除/dist'。 init() 在组件的 OnInit 处理程序中被调用。我也担心这个,因为这是一项服务,所以必须提前知道在ngOnInit 方法中调用此方法。但我认为这种做法在这一点上是众所周知的,因为它正在调用外部服务。我只是展示了服务而不是使用它的组件。但我会将其添加到我上面的答案中......
  • 我的 ionic 应用程序有同样的问题,这个解决方案对我不起作用。请帮忙,这个问题贴在这里link
猜你喜欢
  • 1970-01-01
  • 2015-03-04
  • 1970-01-01
  • 2013-06-28
  • 1970-01-01
  • 2021-01-11
  • 2016-12-07
  • 1970-01-01
  • 2019-05-21
相关资源
最近更新 更多