Okta Angular OpenID - auth guard防止令牌在回调期间被消耗

时间:2017-06-11 20:23:14

标签: angular authentication redirect okta okta-api

我遵循本教程,这是一个很棒的教程!它显示了如何使用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。 - 我将根路由重定向到AuthGuard路由。 - 当我使用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网站以输入我的用户名和密码时,我被重定向到我的应用程序的根目录,从Promise返回的loadDiscoveryDocument()已订阅,但是auth.guard将我重定向回登录页面,在我可以让id_token为我收集它并将其存储在存储中之前,从网址中丢失了OAuthService等。

我的问题

有没有办法让这个工作无需更改我的应用程序的路由结构?我不需要&#34; HomeComponent&#34;充当&#34; LoginComponent&#34;以及&#34; HomeComponent&#34;取决于登录状态。

1 个答案:

答案 0 :(得分: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);
  }

}