auth0 总是在浏览器刷新时显示登录对话框

Øys*_*sen 6 javascript auth0 angular

我正在使用具有通用登录功能的新 auth0-spa-js 库。我按照https://auth0.com/docs/quickstart/spa/angular2/01-login上的指南进行了操作,但仍然 - 在浏览器重新加载时client.isAuthenticated()将始终返回 false 并将重定向到登录页面。

这是非常令人沮丧的。

编辑:删除了到 github 的链接,并根据要求直接在帖子中添加了我的代码

EDIT2:解决方案发布在这篇文章的底部

auth0 配置

应用

Allowed Callback URLs:  http://localhost:3000/callback
Allowed Web Origins:    http://localhost:3000
Allowed Logout URLs:    http://localhost:3000
Allowed Origins (CORS): http://localhost:3000
JWT Expiration          36000
Run Code Online (Sandbox Code Playgroud)

应用程序接口

Token expiration:       86400
Token Expiration For Browser Flows: 7200
Run Code Online (Sandbox Code Playgroud)

不知道这两个部分(应用程序/Api 配置)之间有什么区别,也不知道我在通过正常的通用登录流程时实际使用了哪些部分,但无论如何我都会发布它们。

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutes } from './app.routing';
import { AppComponent } from './app.component';
import { DashboardComponent } from './views/dashboard/dashboard.component';
import { CallbackComponent } from './shared/auth/callback/callback.component';

@NgModule({
  declarations: [
    AppComponent,
    DashboardComponent,
    CallbackComponent
  ],
  imports: [
    BrowserModule,
    AppRoutes,
    HttpClientModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Run Code Online (Sandbox Code Playgroud)

app.routing.ts

import { Routes, RouterModule } from '@angular/router';
import { CallbackComponent } from './shared/auth/callback/callback.component';
import { DashboardComponent } from './views/dashboard/dashboard.component';
import { AuthGuard } from './shared/auth/auth.guard';

const routes: Routes = [
  { path: '', pathMatch: 'full', component: DashboardComponent, canActivate: [AuthGuard] },
  { path: 'callback', component: CallbackComponent },
  { path: '**', redirectTo: '' }
];

export const AppRoutes = RouterModule.forRoot(routes);
Run Code Online (Sandbox Code Playgroud)

app.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from './shared/auth/auth.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'logic-energy';

  constructor(private auth: AuthService) { }

  ngOnInit() {
    // On initial load, check authentication state with authorization server
    // Set up local auth streams if user is already authenticated
    this.auth.localAuthSetup();
  }
}
Run Code Online (Sandbox Code Playgroud)

auth.service.ts

import { Injectable } from '@angular/core';
import createAuth0Client from '@auth0/auth0-spa-js';
import Auth0Client from '@auth0/auth0-spa-js/dist/typings/Auth0Client';
import { environment } from 'src/environments/environment';
import { from, of, Observable, BehaviorSubject, combineLatest, throwError } from 'rxjs';
import { tap, catchError, concatMap, shareReplay, take } from 'rxjs/operators';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  // Create an observable of Auth0 instance of client
  auth0Client$ = (from(
    createAuth0Client({
      domain: environment.auth.domain,
      client_id: environment.auth.clientId,
      redirect_uri: `${window.location.origin}/callback`
    })
  ) as Observable<Auth0Client>).pipe(
    shareReplay(1), // Every subscription receives the same shared value
    catchError(err => throwError(err))
  );
  // Define observables for SDK methods that return promises by default
  // For each Auth0 SDK method, first ensure the client instance is ready
  // concatMap: Using the client instance, call SDK method; SDK returns a promise
  // from: Convert that resulting promise into an observable
  isAuthenticated$ = this.auth0Client$.pipe(
    concatMap((client: Auth0Client) => from(client.isAuthenticated())),
    tap(res => this.loggedIn = res)
  );
  handleRedirectCallback$ = this.auth0Client$.pipe(
    concatMap((client: Auth0Client) => from(client.handleRedirectCallback()))
  );
  // Create subject and public observable of user profile data
  private userProfileSubject$ = new BehaviorSubject<any>(null);
  userProfile$ = this.userProfileSubject$.asObservable();
  // Create a local property for login status
  loggedIn: boolean = null;

  constructor(private router: Router) { }

  // When calling, options can be passed if desired
  // https://auth0.github.io/auth0-spa-js/classes/auth0client.html#getuser
  getUser$(options?): Observable<any> {
    return this.auth0Client$.pipe(
      concatMap((client: Auth0Client) => from(client.getUser(options))),
      tap(user => this.userProfileSubject$.next(user))
    );
  }

  localAuthSetup() {
    // This should only be called on app initialization
    // Set up local authentication streams
    const checkAuth$ = this.isAuthenticated$.pipe(
      concatMap((loggedIn: boolean) => {
        if (loggedIn) {
          // If authenticated, get user and set in app
          // NOTE: you could pass options here if needed
          return this.getUser$();
        }
        // If not authenticated, return stream that emits 'false'
        return of(loggedIn);
      })
    );
    checkAuth$.subscribe((response: { [key: string]: any } | boolean) => {
      // If authenticated, response will be user object
      // If not authenticated, response will be 'false'
      this.loggedIn = !!response;
    });
  }

  login(redirectPath: string = '/') {
    // A desired redirect path can be passed to login method
    // (e.g., from a route guard)
    // Ensure Auth0 client instance exists
    this.auth0Client$.subscribe((client: Auth0Client) => {
      // Call method to log in
      client.loginWithRedirect({
        redirect_uri: `${window.location.origin}/callback`,
        appState: { target: redirectPath }
      });
    });
  }

  handleAuthCallback() {
    // Only the callback component should call this method
    // Call when app reloads after user logs in with Auth0
    let targetRoute: string; // Path to redirect to after login processsed
    const authComplete$ = this.handleRedirectCallback$.pipe(
      // Have client, now call method to handle auth callback redirect
      tap(cbRes => {
        // Get and set target redirect route from callback results
        targetRoute = cbRes.appState && cbRes.appState.target ? cbRes.appState.target : '/';
      }),
      concatMap(() => {
        // Redirect callback complete; get user and login status
        return combineLatest(
          this.getUser$(),
          this.isAuthenticated$
        );
      })
    );
    // Subscribe to authentication completion observable
    // Response will be an array of user and login status
    // authComplete$.subscribe(([user, loggedIn]) => {
    authComplete$.subscribe(([user, loggedIn]) => {
      // Redirect to target route after callback processing
      this.router.navigate([targetRoute]);
    });
  }

  logout() {
    // Ensure Auth0 client instance exists
    this.auth0Client$.subscribe((client: Auth0Client) => {
      // Call method to log out
      client.logout({
        client_id: environment.auth.clientId,
        returnTo: `${window.location.origin}`
      });
    });
  }

  getTokenSilently$(options?): Observable<string> {
    return this.auth0Client$.pipe(
      concatMap((client: Auth0Client) => from(client.getTokenSilently(options)))
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

auth.guard.ts

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, CanActivate } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';
import { tap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {

  constructor(private auth: AuthService) {}

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean|UrlTree> | boolean {
    return this.auth.isAuthenticated$.pipe(
      tap(loggedIn => {
        if (!loggedIn) {
          this.auth.login(state.url);
        }
      })
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

回调.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';

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

  constructor(private auth: AuthService) { }

  ngOnInit() {
    this.auth.handleAuthCallback();
  }
}
Run Code Online (Sandbox Code Playgroud)

通过检查 devtools 中的网络选项卡,我可以看到进行了以下调用:

登录前:

  • authorize使用几个查询参数=>返回带有空正文的 HTTP 200。
  • login =>返回登录页面

登录后:

  • authorize =>返回 HTTP 302 空体
  • authorize再次(使用一组不同的参数)=>返回 HTTP 302 空正文
  • authorize再次(使用另一组不同的参数)=>返回 HTTP 302 空正文
  • callback =>返回 HTTP 302 空体
  • callback =>用我的回调 html 返回 HTTP 200

注意:每隔一次它就停在这里并且不会重定向到 root,这有点奇怪。

回调重定向后:

  • authorize =>返回 HTTP 200 空体
  • token =>接收有效令牌。我可以用 Base64 解码它,看起来还可以(除了nonce属性中的一些垃圾)

在浏览器中点击刷新,将重复此过程

我已经仔细检查了 auth0 配置。这在使用旧版 auth0-js 的 React 应用程序上按预期工作,我使用相同的 client_id 并配置了相同的 url。

我究竟做错了什么?是否有我必须执行但文档中未描述的手动步骤?我是否必须迁移到较旧的 auth0-js 库才能使其正常工作?

更新

我在 auth0-spa-js 中设置了一些断点,我看到当应用程序启动时,它尝试运行getTokenSilently(),但它总是拒绝带有"login_required".

即使在登录后,它首先调用 url 并拒绝(即使 http 请求返回 HTTP 200,因为响应的主体为空?),然后它尝试内部缓存并通过。

只要我不刷新,auth0 就会使用缓存中的令牌,但如果它尝试从 http 进行验证,它会立即抛出。

我看到的一件事是,每次运行以下代码都不getTokenSilently()是从缓存中获取:

stateIn = encodeState(createRandomString());
nonceIn = createRandomString();
code_verifier = createRandomString();
return [4 /*yield*/, sha256(code_verifier)];
Run Code Online (Sandbox Code Playgroud)

换句话说,它总是询问 auth0 后端是否基于完全随机的字符串进行了身份验证。如果这是允许它识别我和我的会话的原因,它不应该将其中的一些存储在浏览器中吗?

更新 2 / 解决方案

嗯...看起来可以防止cookie被存储的Chrome插件“Privacy Badger”实际上如果您通过其他浏览器浏览它(当chrome打开时)也会对站点产生影响。它实际上在处理会话时清除了会话。上面的代码有效,我只需要调整插件。乌克...

万一我不是唯一忘记安装了哪些扩展的人,我将把这个问题留在这里,所以其他人可能不会浪费一整天调试不需要调试的东西。

Dam*_*n C 3

我刚刚注意到您没有为回电注册路线:

const routes: Routes = [
  { path: '', pathMatch: 'full', component: DashboardComponent, canActivate: [AuthGuard] },
  { path: 'callback', component: CallbackComponent },
  { path: '**', redirectTo: '' }
];
Run Code Online (Sandbox Code Playgroud)

https://auth0.com/docs/quickstart/spa/angular2/01-login#handle-login-redirects