Firebase - 验证 api 请求在 .net 5 中具有有效的 ID 令牌
Firebase - Verifying api requests have a valid id token in .net 5
我正在使用 flutter 应用程序中的 firebase 身份验证,它从前端获取 jwt,如下所示:
accessToken = await user!.getIdToken(true);
现在我想让我的 .net 5 AspNetCore 后端应用程序验证 id 令牌。
我将 firebase admin SDK 添加到我的 .net 应用程序中,如下所示:
在startup.cs
中:
FirebaseApp.Create();
Firebase docs 显示如何验证 id 令牌:
FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
.VerifyIdTokenAsync(idToken);
string uid = decodedToken.Uid;
但是这段代码去哪儿了?我不想为我的 AspNetCore 应用程序中的每个控制器端点手动执行此操作。这可以作为中间件在每次 api 请求时自动发生吗?
编辑:Eeek 我什至不认为 Firebase Admin 是我想要的。我只想以最火爆的方式验证使用 Firebase Auth for Flutter 在客户端上创建的 jwt id 令牌。它不适合管理员,它适合普通用户。我以为 Firebase Admin 就是这样。我该往哪个方向走?我在网上找到了各种关于不同策略的文章,所以很困惑哪一个是正确的。这够了吗? https://dominique-k.medium.com/using-firebase-jwt-tokens-in-asp-net-core-net-5-834c43d4aa00 看起来不是很火,而且似乎过于简化了?我认为下面 Dharmaraj 的回答是最火爆的方式,但我不确定 idToken
从哪里获取值(如何从请求 headers 获取?否则它只是 null)。
您在中间件中添加了验证请求的功能。我不确定这在 .NET 中是如何进行的,但我查找了一些中间件片段并发现了这个:
public class Startup
{
public Startup()
{
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Run(MyMiddleware);
}
private Task MyMiddleware(HttpContext context)
{
// Add the code here and verify the IDToken
//FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
.VerifyIdTokenAsync(idToken);
//string uid = decodedToken.Uid;
return context.Response.WriteAsync("Hello World! ");
}
}
现在中间件将检查所有指定端点的令牌。您可以轻松地在其中读取 idToken(来自 headers 或 body),并且 return 出现任何问题时都会出现错误。
如果令牌有效,您可以将用户的 UID 和您需要的任何其他信息添加到上下文 object。
Firebase Admin SDK 名称中的“Admin”一词表示您在受信任的环境中使用此 SDK,例如您的开发机器或您控制的服务器 - 例如您的 ASP.NET代码。
Admin SDK 可以验证来自任何类型用户的令牌。事实上,它不知道用户的类型,它只是验证令牌是否有效。
我基本上遵循 for the non-firebase stuff and this tutorial 的 firebase 东西,一切正常,我可以通过 extendedContext.userResolverService 和 ID 访问用户、访问令牌、声明、所有内容我所有使用 [Authorize]
.
的 Web 控制器端点都验证了令牌
因为我的所有控制器中都有 id 令牌,所以我也可以从任何控制器手动调用它,但这不是必需的。
FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
.VerifyIdTokenAsync(idToken);
string uid = decodedToken.Uid;
userResolverService.cs:
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
public class UserResolverService
{
public readonly IHttpContextAccessor _context;
public UserResolverService(IHttpContextAccessor context)
{
_context = context;
}
public string GetGivenName()
{
return _context.HttpContext.User.FindFirst(ClaimTypes.GivenName).Value;
}
public string GetSurname()
{
return _context.HttpContext.User.FindFirst(ClaimTypes.Surname).Value;
}
public string GetNameIdentifier()
{
return _context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
}
public string GetEmails()
{
return _context.HttpContext.User.FindFirst("emails").Value;
}
}
扩展 DataContext(通过组合):
namespace Vepo.DataContext {
public class ExtendedVepoContext
{
public VepoContext _context;
public UserResolverService _userResolverService;
public ExtendedVepoContext(VepoContext context, UserResolverService userService)
{
_context = context;
_userResolverService = userService;
_context._currentUserExternalId = _userResolverService.GetNameIdentifier();
}
}
}
startup.cs:
public void ConfigureServices(IServiceCollection services)
{
....
services.AddHttpContextAccessor();
services.AddTransient<UserResolverService>();
services.AddTransient<ExtendedVepoContext>();
FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromFile("firebase_admin_sdk.json"),
});
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://securetoken.google.com/my-firebase-app-id";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://securetoken.google.com/my-firebase-app-id",
ValidateAudience = true,
ValidAudience = "my-firebase-app-id",
ValidateLifetime = true
};
});
还有startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, VepoContext context, ISearchIndexService searchIndexService)
{ ....
app.UseAuthentication();
app.UseAuthorization();
然后像这样将 auth 添加到控制器端点:
[HttpPost]
[Authorize]
public async Task<ActionResult<GroceryItemGroceryStore>> PostGroceryItemGroceryStore(GroceryItemGroceryStore groceryItemGroceryStore)
{...
你可以更进一步,在每次保存时与用户一起做一些事情,比如添加元数据:
添加元数据保存的实体:
public interface IDomainEntity<TId>
{
TId Id { get; set; }
DateTime SysStartTime { get; set; }
DateTime SysEndTime { get; set; }
string CreatedById { get; set; }
User CreatedBy { get; set; }
string UpdatedById { get; set; }
User UpdatedBy { get; set; }
}
我的数据上下文:
public class VepoContext : DbContext
{
public VepoContext(DbContextOptions<VepoContext> options)
: base(options)
{
}
public DbSet<User> User { get; set; }
public string _currentUserExternalId;
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var user = await User.SingleAsync(x => x.Id == _currentUserExternalId);
AddCreatedByOrUpdatedBy(user);
return (await base.SaveChangesAsync(true, cancellationToken));
}
public override int SaveChanges()
{
var user = User.Single(x => x.Id == _currentUserExternalId);
AddCreatedByOrUpdatedBy(user);
return base.SaveChanges();
}
public void AddCreatedByOrUpdatedBy(User user)
{
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IDomainEntity<int> entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedBy = user;
entity.UpdatedBy = user;
break;
case EntityState.Modified:
Entry(entity).Reference(x => x.CreatedBy).IsModified = false;
entity.UpdatedBy = user;
break;
}
}
}
}
我正在使用 flutter 应用程序中的 firebase 身份验证,它从前端获取 jwt,如下所示:
accessToken = await user!.getIdToken(true);
现在我想让我的 .net 5 AspNetCore 后端应用程序验证 id 令牌。
我将 firebase admin SDK 添加到我的 .net 应用程序中,如下所示:
在startup.cs
中:
FirebaseApp.Create();
Firebase docs 显示如何验证 id 令牌:
FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
.VerifyIdTokenAsync(idToken);
string uid = decodedToken.Uid;
但是这段代码去哪儿了?我不想为我的 AspNetCore 应用程序中的每个控制器端点手动执行此操作。这可以作为中间件在每次 api 请求时自动发生吗?
编辑:Eeek 我什至不认为 Firebase Admin 是我想要的。我只想以最火爆的方式验证使用 Firebase Auth for Flutter 在客户端上创建的 jwt id 令牌。它不适合管理员,它适合普通用户。我以为 Firebase Admin 就是这样。我该往哪个方向走?我在网上找到了各种关于不同策略的文章,所以很困惑哪一个是正确的。这够了吗? https://dominique-k.medium.com/using-firebase-jwt-tokens-in-asp-net-core-net-5-834c43d4aa00 看起来不是很火,而且似乎过于简化了?我认为下面 Dharmaraj 的回答是最火爆的方式,但我不确定 idToken
从哪里获取值(如何从请求 headers 获取?否则它只是 null)。
您在中间件中添加了验证请求的功能。我不确定这在 .NET 中是如何进行的,但我查找了一些中间件片段并发现了这个:
public class Startup
{
public Startup()
{
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Run(MyMiddleware);
}
private Task MyMiddleware(HttpContext context)
{
// Add the code here and verify the IDToken
//FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
.VerifyIdTokenAsync(idToken);
//string uid = decodedToken.Uid;
return context.Response.WriteAsync("Hello World! ");
}
}
现在中间件将检查所有指定端点的令牌。您可以轻松地在其中读取 idToken(来自 headers 或 body),并且 return 出现任何问题时都会出现错误。
如果令牌有效,您可以将用户的 UID 和您需要的任何其他信息添加到上下文 object。
Firebase Admin SDK 名称中的“Admin”一词表示您在受信任的环境中使用此 SDK,例如您的开发机器或您控制的服务器 - 例如您的 ASP.NET代码。
Admin SDK 可以验证来自任何类型用户的令牌。事实上,它不知道用户的类型,它只是验证令牌是否有效。
我基本上遵循 [Authorize]
.
因为我的所有控制器中都有 id 令牌,所以我也可以从任何控制器手动调用它,但这不是必需的。
FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
.VerifyIdTokenAsync(idToken);
string uid = decodedToken.Uid;
userResolverService.cs:
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
public class UserResolverService
{
public readonly IHttpContextAccessor _context;
public UserResolverService(IHttpContextAccessor context)
{
_context = context;
}
public string GetGivenName()
{
return _context.HttpContext.User.FindFirst(ClaimTypes.GivenName).Value;
}
public string GetSurname()
{
return _context.HttpContext.User.FindFirst(ClaimTypes.Surname).Value;
}
public string GetNameIdentifier()
{
return _context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
}
public string GetEmails()
{
return _context.HttpContext.User.FindFirst("emails").Value;
}
}
扩展 DataContext(通过组合):
namespace Vepo.DataContext {
public class ExtendedVepoContext
{
public VepoContext _context;
public UserResolverService _userResolverService;
public ExtendedVepoContext(VepoContext context, UserResolverService userService)
{
_context = context;
_userResolverService = userService;
_context._currentUserExternalId = _userResolverService.GetNameIdentifier();
}
}
}
startup.cs:
public void ConfigureServices(IServiceCollection services)
{
....
services.AddHttpContextAccessor();
services.AddTransient<UserResolverService>();
services.AddTransient<ExtendedVepoContext>();
FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.FromFile("firebase_admin_sdk.json"),
});
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://securetoken.google.com/my-firebase-app-id";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "https://securetoken.google.com/my-firebase-app-id",
ValidateAudience = true,
ValidAudience = "my-firebase-app-id",
ValidateLifetime = true
};
});
还有startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, VepoContext context, ISearchIndexService searchIndexService)
{ ....
app.UseAuthentication();
app.UseAuthorization();
然后像这样将 auth 添加到控制器端点:
[HttpPost]
[Authorize]
public async Task<ActionResult<GroceryItemGroceryStore>> PostGroceryItemGroceryStore(GroceryItemGroceryStore groceryItemGroceryStore)
{...
你可以更进一步,在每次保存时与用户一起做一些事情,比如添加元数据:
添加元数据保存的实体:
public interface IDomainEntity<TId>
{
TId Id { get; set; }
DateTime SysStartTime { get; set; }
DateTime SysEndTime { get; set; }
string CreatedById { get; set; }
User CreatedBy { get; set; }
string UpdatedById { get; set; }
User UpdatedBy { get; set; }
}
我的数据上下文:
public class VepoContext : DbContext
{
public VepoContext(DbContextOptions<VepoContext> options)
: base(options)
{
}
public DbSet<User> User { get; set; }
public string _currentUserExternalId;
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
var user = await User.SingleAsync(x => x.Id == _currentUserExternalId);
AddCreatedByOrUpdatedBy(user);
return (await base.SaveChangesAsync(true, cancellationToken));
}
public override int SaveChanges()
{
var user = User.Single(x => x.Id == _currentUserExternalId);
AddCreatedByOrUpdatedBy(user);
return base.SaveChanges();
}
public void AddCreatedByOrUpdatedBy(User user)
{
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IDomainEntity<int> entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedBy = user;
entity.UpdatedBy = user;
break;
case EntityState.Modified:
Entry(entity).Reference(x => x.CreatedBy).IsModified = false;
entity.UpdatedBy = user;
break;
}
}
}
}