Ø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打开时)也会对站点产生影响。它实际上在处理会话时清除了会话。上面的代码有效,我只需要调整插件。乌克...
万一我不是唯一忘记安装了哪些扩展的人,我将把这个问题留在这里,所以其他人可能不会浪费一整天调试不需要调试的东西。
我刚刚注意到您没有为回电注册路线:
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
| 归档时间: |
|
| 查看次数: |
1873 次 |
| 最近记录: |