创建一个用于在Angular中重定向授权的中间件:组件、解析器还是守卫?

huangapple go评论46阅读模式
英文:

Creating a middleware for redirect auth in Angular: Component, Resolver or Guard?

问题

我正在努力在我的Angular应用程序中实现外部认证系统,我想确保我遵循最佳的方法。

认证系统的工作方式是,通过给定的重定向URL,如果认证成功,它将重定向用户并在我的情况下在查询参数中带有令牌到一个我创建的名为AuthComponent的组件,然后必须使用该令牌通过另一个服务进行身份验证,如果此身份验证成功,用户将能够在Angular应用程序中导航,否则必须重定向到/error组件。

我尝试过将所有逻辑放在组件内部,但实际上不需要加载该组件,所以我正在寻找更好的方法来实现它,也许可以使用解析器或守卫。

以下是组件的代码:

@Component({
  selector: 'app-auth',
  templateUrl: './auth.component.html',
  styleUrls: ['./auth.component.scss']
})
export class AuthComponent {
  router = inject(Router)
  route = inject(ActivatedRoute)
  authService = inject(AuthService)

  ngOnInit() {
    this.route.queryParams.subscribe(params => {
      const token = params['token'];
      if (token) {
        this.authService.authenticate(token).pipe(
          map(response => {
            if (response && response.status === 200) {
              this.router.navigate(['/']); // 认证成功
            } else {
              this.router.navigate(['/error']); // 认证失败
            }
          })
        );
      } else {
        this.router.navigate(['/error']); // 未找到令牌
      }
    });
  }
}

以下是服务的代码:

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  helper = inject(JwtHelperService)
  http = inject(HttpClient)

  authenticate(token: string): Observable<any> {
    return this.http.get<any>(`/api/auth/gab-auth/`, { params: { token: token }, observe: 'response' }).pipe(
      tap(response => {
        if (response.ok && response.body) {
          let accessToken = response.body['access_token'];
          if (accessToken) {
            localStorage.setItem('access_token', accessToken);
          }
        }
      })
    );
  }

  isAuthenticated(): boolean {
    const token = localStorage.getItem('access_token');
    return token != null && !this.helper.isTokenExpired(token);
  }
}

然后我有一个守卫,根据isAuthenticated值允许或不允许用户访问页面。

英文:

I am working on implementing an external auth system in my Angular application, and I want to ensure that I am following the best approach.

The auth system works in the way that by a given redirect URL if the authentication goes well, it will redirect the user with a token in query params in my case to a component I've created called AuthComponent, then that token has to be used to get authenticated via another service, if this authentication will go well, then the user will be able to navigate in the angular application, else had to be redirected to /error component.

What I've tried is to do all that logic inside the component, but I don't really need the component to be loaded, so I was looking for a better approach to do it, maybe by using a resolver or a guard.

Here is how the component looks like:

@Component({
  selector: &#39;app-auth&#39;,
  templateUrl: &#39;./auth.component.html&#39;,
  styleUrls: [&#39;./auth.component.scss&#39;]
})
export class AuthComponent {
  router = inject(Router)
  route = inject(ActivatedRoute)
  authService = inject(AuthService)

  ngOnInit() {
    this.route.queryParams.subscribe(params =&gt; {
      const token = params[&#39;token&#39;];
      if (token) {
        this.authService.authenticate(token).pipe(
          map(response =&gt; {
            if (response &amp;&amp; response.status === 200) {
              this.router.navigate([&#39;/&#39;]); // Authentication successful
            } else {
              this.router.navigate([&#39;/error&#39;]); // Authentication failed
            }
          })
        );
      } else {
        this.router.navigate([&#39;/error&#39;]); // No token found
      }
    });
  }
}

And here is the service:

@Injectable({
  providedIn: &#39;root&#39;
})
export class AuthService {
  helper = inject(JwtHelperService)
  http = inject(HttpClient)

  authenticate(token: string): Observable&lt;any&gt; {
    return this.http.get&lt;any&gt;(`/api/auth/gab-auth/`, { params: { token: token }, observe: &#39;response&#39; }).pipe(
      tap(response =&gt; {
        if (response.ok &amp;&amp; response.body) {
          let accessToken = response.body[&#39;access_token&#39;];
          if (accessToken) {
            localStorage.setItem(&#39;access_token&#39;, accessToken);
          }
        }
      })
    );
  }

  isAuthenticated(): boolean {
    const token = localStorage.getItem(&#39;access_token&#39;);
    return token != null &amp;&amp; !this.helper.isTokenExpired(token);
  }
}

Then I have a guard that allows or not the user to reach the pages based on isAuthenticated value.

答案1

得分: 0

将代码中的注释部分翻译如下:

添加保护根路由的守卫

const routes: Routes = [
  {
    path: 'login',
    loadComponent: () => import('./login/login.component').then(m => m.LoginComponent),
  },
  {
    path: '',
    component: AppContainerComponent,
    canActivate: [isLoggedIn],
    children: [
      {
        path: 'jobs',
        canMatch: [isModuleAllowed],
        loadChildren: () => import('./jobs/jobs-routes'),
      },
      {
        path: '',
        component: MainMenuComponent,
        pathMatch: 'full',
      },
    ]
  },
  {
    path: '**',
    redirectTo: '',
  },
];

以及登录守卫

export const isLoggedIn: CanActivateFn = (): Observable<boolean | UrlTree> => {
  const router = inject(Router);
  return inject(LoginService).isLogin().pipe(
    map(logged => logged || router.parseUrl('/login')),
  );
};

登录服务检查登录状态并缓存状态实现不在问题范围内)。

请注意,这只是代码的注释部分的翻译,不包括代码本身。

英文:

Add guard to protected root routes:

const routes: Routes = [
  {
    path: &#39;login&#39;,
    loadComponent: () =&gt; import(&#39;./login/login.component&#39;).then(m =&gt; m.LoginComponent),
  },
  {
    path: &#39;&#39;,
    component: AppContainerComponent,
    canActivate: [isLoggedIn],
    children: [
      {
        path: &#39;jobs&#39;,
        canMatch: [isModuleAllowed],
        loadChildren: () =&gt; import(&#39;./jobs/jobs-routes&#39;),
      },
       {
        path: &#39;&#39;,
        component: MainMenuComponent,
        pathMatch: &#39;full&#39;,
      },
    ]
  },
  {
    path: &#39;**&#39;,
    redirectTo: &#39;&#39;,
  },
];

And login guard:

export const isLoggedIn: CanActivateFn = (): Observable&lt;boolean | UrlTree&gt; =&gt; {
  const router = inject(Router);
  return inject(LoginService).isLogin().pipe(
    map(logged =&gt; logged || router.parseUrl(&#39;/login&#39;)),
  );
};

Login service checks login state against api and caches the state (implementation is out of question scope).

答案2

得分: 0

I have solved it by using a guard instead of a component. First of all, I've gotten rid of the AuthComponent but I still keep the route for it by replacing the component with an empty child (this allows using the guard without any component, else with a redirectTo is executed before the guard).

我的解决方法是使用守卫而不是组件。首先,我摆脱了AuthComponent,但仍然保留了该路由,通过用空的child替换component(这允许在没有任何组件的情况下使用守卫,否则会在守卫之前执行redirectTo)。

My app-routing for the auth now looks like this:

我的身份验证的app-routing现在如下所示:

{
  path: 'auth', canActivate: [tokenGuard], children: []
}

While the tokenGuard:

tokenGuard如下:

export const tokenGuard: CanActivateFn = (route, _state): Observable<boolean | UrlTree> => {
  const router = inject(Router)
  const auth = inject(AuthService)

  const token = route.params['token'];

  if (!token) {
    return of(router.parseUrl('/error')); // No token found
  }

  return auth.authenticate(token).pipe(
    map(response => {
      if (response && response.status === 200) {
        return router.parseUrl('/'); // Authentication successful
      } else {
        return router.parseUrl('/error'); // Authentication failed
      }
    })
  );
};

Note: I've provided the translation for the code parts you requested, without any additional content or answers to translation questions.

英文:

How I have solved it by using a guard instead of a component?

First of all, I've gotten rid of the AuthComponent but I still keep the route for it by replacing the component with an empty child (this allows to use the guard without any component, else with a redirectTo is executed before the guard).

My app-routing for the auth now looks like this:

  {
    path: &#39;auth&#39;, canActivate: [tokenGuard], children: []
  }

While the tokenGuard:

export const tokenGuard: CanActivateFn = (route, _state): Observable&lt;boolean | UrlTree&gt; =&gt; {
  const router = inject(Router)
  const auth = inject(AuthService)

  const token = route.params[&#39;token&#39;];

  if (!token) {
    return of(router.parseUrl(&#39;/error&#39;)); // No token found
  }

  auth.authenticate(token).pipe(
    map(response =&gt; {
      if (response &amp;&amp; response.status === 200) {
        return router.parseUrl(&#39;/&#39;); // Authentication successful
      } else {
        return router.parseUrl(&#39;/error&#39;); // Authentication failed
      }
    })
  );

  return of(false);
};

huangapple
  • 本文由 发表于 2023年6月12日 17:47:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76455424.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定