You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Auth0、.NET 8、Angular完成现有API的安全加固?

背景

我正在接手一位其他开发者开发的应用,没有任何知识传递或文档。因此我不清楚之前的选型决策,也对部分内容不熟悉。虽然我很久以前做过网站应用的安全加固,但并非现代安全领域专家。

当前状态

API控制器基于用户ID(一个UID,推测是GUID)从MemoryCache中获取用户数据。这种方式存在漏洞,因为我只需发送包含用户ID的POST请求就能触发API方法。

目标

目标是避免API仅靠用户ID就能轻易被访问。

根据我目前的理解,API应基于临时令牌(JWT token)进行额外校验。因此我需要:

  • 能够在API请求中传递JWT令牌
  • 在API端获取请求中的JWT令牌
  • 对令牌进行校验

虽然我理解相关概念,但目前我不清楚如何将令牌加入API请求,也不确定是否从一开始就走错了方向。(搜索示例时,我找到很多Auth0登录配置的示例,但很少找到临时令牌使用的参考,少数找到的也对应不同版本/方案。)

技术现状

后端 - 认证

认证服务器:Auth0 / 后端:.NET 8.0 WebApi / 前端:Angular(当前版本17.3.7)
在后端的program.cs中,我们有如下代码:

var domain = $"https://{builder.Configuration["Auth0:Domain"]}/";
var audience = builder.Configuration["Auth0:ApiIdentifier"];

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
    options.Authority = domain;
    options.Audience = audience;
});

说明

我曾按照某教程添加了以下代码以解决其他问题,但并未奏效。我认为这与当前问题无直接关联,但仍提及以防存在交互影响。

#if DEBUG
builder.Services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", builder => builder
    .WithOrigins([front-end local URL])
    .AllowAnyMethod()
    .AllowAnyHeader()
    .AllowCredentials());
});
#endif

前端

Auth0Service

前端有一个编号为v2的“认证服务”(v1已全部注释…我找到的部分示例类似v1,但v2仅在StackOverflow的一个回答中见过部分实现)。以下是该类的代码:

import { Injectable } from '@angular/core';
import { AppState, AuthService, LogoutOptions, RedirectLoginOptions, User } from '@auth0/auth0-angular';
import { SessionActions } from '@[anonymised]/session-data-access';
import { Store } from '@ngrx/store';
import { Observable, combineLatestWith, filter, from, map, take, tap } from 'rxjs';

@Injectable({
  providedIn: 'root',
})
export class Auth0Service {
  constructor(
    private auth: AuthService,
    private store: Store,
  ) {
    this.auth.isAuthenticated$.subscribe(
      (value) => {console.log("Is authenticated?", value);} //Logs true?
    );

    this.auth.error$
      .pipe(
        // filter((e) => e instanceof GenericError && e.error === 'login_required'),
        map(() => console.log('error in auth.error')),
      )
      .subscribe();
  }

  public user$: Observable<User | null | undefined> = this.auth.user$;

  public loginWithRedirect(options?: RedirectLoginOptions<AppState> | undefined): void {
    console.log("loginWithRedirect", options);
    this.auth.loginWithPopup(options);
  }

  public handleAuthCallback(): void {
    this.getUser$.pipe(take(1)).subscribe();
  }

  private getUser$ = this.auth.isAuthenticated$.pipe(
    filter((i) => i === true),
    combineLatestWith(this.user$),
    map(([, user]) => {
      console.log("this.user$", this.user$);
      if (user) {
        console.log("User", user);
          /* Logs a User object with properties
          - email
          - mail_verified : true
          - https://cust.[domain containing company name, ends with .app extension]/token (note: I have never seen this domain anywhere but it's registered by the company) : [value is a custom token assigne to my account in Auth0 MetaData field, token was generate when creating my user in the application database]
          - name : [my application login]
          - nickname : [my application display name]
          - picture : [gravatar URL, I think it's an Auth0 stuff set automatically]
          - sub : [my Auth0 user_id in format "auth0|000000000000000000000000", I replaced the id with zeros for this comment
          - updated_at : [time stamp from approximately 45 minute ago]
          // I don't think there is any JWT token in here

          */
        this.store.dispatch(SessionActions.loadSteer({ steerId: user['https://cust.[domain]/token'] }));
      }
    }),
  );

  public getTokenSilently() {
    from(
      this.auth.getAccessTokenSilently().pipe(
        filter((i) => i !== undefined && i !== null),
        map(() => this.handleAuthCallback()),
      ),
    ).subscribe(
      (value)=>{console.log('getTokenSilently subscription', value);} //Logs "undefined".
    );
  }

  public logout(options?: LogoutOptions | undefined): Observable<void> {
    return this.auth.logout(options);
  }
}

getTokenSilently()在初始AppComponent的构造函数中被调用。一个名为[DomainOrCategory]Service的类(已匿名处理)通过如下方法将API请求传递给ApiService:

Save[entity](form: entityForm): Observable<[entity]Result> {
    return this.apiService.post<<[entity]Result >(this.post[entityUrl, sdg);
  }

发送API请求

在ApiService中:

public post<T>(endpoint: string, body?: any, options?: Options): Observable<T> {
//Options is empty when I send a POST, and I’m not sure if it’s supposed to hold anything
    const endpointUrl = this.buildApiEndpoint(endpoint);
    return this.postWithFullUrl(endpointUrl, body, options);
  }

  public postWithFullUrl<T>(endpoint: string, body?: any, options?: Options): Observable<T> {
    return this.httpClient.post<T>(endpoint, body, options);
  }

(httpClient来自Angular 17.3.9版本)

后端 - API授权过滤器

后端API使用一个授权过滤器:

[Authorization filter class] : System.Attribute, Microsoft.AspNetCore.MVC.Filters.IAsyncResourceFilter { (…)

该过滤器尝试从包含所有当前用户数据的MemoryCache中识别用户(当前用户不足20个,但调试时访问速度很慢)。

(…)
identifier = Guid.Parse(context.HttpContext.Request.Headers["steerid"]);
(…)
if (!service.MemoryCache.TryGetValue(UserKey, out ICollection<User> userCacheEntries))
{ 
   userCacheEntries = await userRepo.GetUsersAsync(cancellationToken);
   service.MemoryCache.Set(UserKey, userCacheEntries);
}
var user = userCacheEntries.SingleOrDefault(i => i.Id == identifier);

如果第一次获取失败,会以略微不同的方式重复该过程(此时userCacheEntries已赋值)。目前没有任何JWT令牌校验逻辑。


更新1

在前端,我注意到auth0-angular提供的AuthService类有一个idTokenClaims$属性:

this.auth.idTokenClaims$.subscribe(
  (value) => {console.log("idTokenClaims$", value);}
);

这段代码会输出一个包含多个键的对象。我目前不确定每个属性的含义,但其中很可能包含我需要的令牌。

我需要找到将其加入请求的方法,但不确定后端预期的令牌值是什么,也不清楚如何进行校验。

内容的提问来源于stack exchange,提问作者TTT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 03:03:10