Firebase — проверка API-запросов на наличие действительного токена идентификатора в .net 5

Я использую аутентификацию firebase из приложения флаттера, которое получает jwt из внешнего интерфейса, например:

accessToken = await user!.getIdToken(true);

Теперь я хочу, чтобы мое серверное приложение .net 5 AspNetCore проверяло токен идентификатора.

Я добавляю SDK администратора firebase в свое приложение .net следующим образом:

In startup.cs:

FirebaseApp.Create();

В документах Firebase показано, как проверить токен идентификатора:

FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
    .VerifyIdTokenAsync(idToken);
string uid = decodedToken.Uid;

Но куда идет этот код? Это не то, что я хочу делать вручную для каждой конечной точки контроллера в моем приложении AspNetCore. Можно ли сделать так, чтобы промежуточное ПО выполнялось автоматически при каждом запросе API?

РЕДАКТИРОВАТЬ: Eeek, я даже не думаю, что администратор Firebase - это то, что я хочу. Я просто хочу проверить токен идентификатора jwt, созданный на клиенте с использованием Firebase Auth для Flutter, максимально возможным способом firebasey. Это не для админов, это для обычных пользователей. Я думал, что это администратор Firebase. Какое направление мне выбрать? Я нашел в Интернете множество статей о различных стратегиях, поэтому не понимаю, какая из них правильная. Этого достаточно? https://dominique-k.medium.com/using-firebase-jwt-tokens-in-asp-net-core-net-5-834c43d4aa00 Это не кажется очень зажигательным и кажется слишком упрощенным? Я думаю, что приведенный ниже ответ Дхармараджа является наиболее подходящим способом, но я не уверен, откуда idToken получает значение (как получить из заголовков запроса? В противном случае это просто ноль).


person BeniaminoBaggins    schedule 23.06.2021    source источник


Ответы (3)


Слово Admin в названии Firebase Admin SDK подразумевает, что вы используете этот SDK в доверенных средах, таких как ваш компьютер для разработки или сервер, которым вы управляете, например, в случае с вашим кодом ASP.NET.

Административный SDK может проверять токены любого пользователя. На самом деле он ничего не знает о типах пользователей, он просто проверяет, действителен ли токен.

person Frank van Puffelen    schedule 23.06.2021
comment
Спасибо, это звучит подходящим для моей серверной части ASP.NET. Я просто не уверен, как это реализовать, чтобы он запускался при каждом запросе. - person BeniaminoBaggins; 24.06.2021
comment
Я не уверен, с чем у вас проблемы, но я могу сказать вам, что делает сама Firebase. Пакеты SDK отправляют токен идентификатора в заголовке Authorization каждого запроса, который он отправляет серверным службам. Службы получают токен и декодируют его так же, как это делает Admin SDK, а затем либо передают информацию (например, правила безопасности), либо проверяют, авторизован ли пользователь для операции (например, для вызовов от имени пользователя к внутренним API-интерфейсам Auth SDK). - person Frank van Puffelen; 24.06.2021
comment
Да, спасибо, это похоже на то, что я хочу, но в примере кода в моем вопросе, который также находится в официальных документах, если кто-то скопирует это в свой код, у него будет ошибка времени компиляции, потому что переменная idToken вырвана из тонкого воздуха. - person BeniaminoBaggins; 25.06.2021
comment
Да, между этими двумя фрагментами может быть ошибка в имени переменной. Если вы считаете, что проблема именно в этом, внизу страницы должна быть кнопка обратной связи, чтобы сообщить об этом. - person Frank van Puffelen; 25.06.2021

Вы добавляете функции, которые аутентифицируют запросы в промежуточном программном обеспечении. Я не уверен, как это происходит в .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 (из заголовков или тела) и вернуть ошибку в случае какой-либо проблемы.

Если токен действителен, вы можете добавить UID пользователя и любую другую информацию, которая вам требуется, в объект контекста.

person Dharmaraj    schedule 23.06.2021
comment
Спасибо. Я не знаю, как заполнить idToken - person BeniaminoBaggins; 23.06.2021
comment
@BenaminoBaggins, можете ли вы объяснить, что вы подразумеваете под заселением? - person Dharmaraj; 23.06.2021
comment
Чтобы это имело ценность. Думаю, мне нужно прочитать заголовки запросов API, как предлагает ваш ответ. сейчас погуглю как это сделать - person BeniaminoBaggins; 23.06.2021

По сути, я следовал этому ответу SO для материалов, не связанных с firebase, и это руководство для материалов Firebase и все это работает, у меня есть доступ к пользователю, токену доступа, утверждениям, ко всему, через extendedContext.userResolverService, и токен идентификатора проверяется во всех конечных точках моего веб-контроллера, которые используют [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();
        }
    }
}

запуск.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();

Затем добавьте авторизацию в конечную точку контроллера следующим образом:

[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;
                    }
                }
            }
        }
person BeniaminoBaggins    schedule 26.06.2021