Dodanie ClaimsIdentity do ClaimsPrincipal - JwtBearer

0

Cześć, sprawa jest taka.
Mój system składa się jakby z dwóch stopni autoryzacji (chociaż nie do końca).
Aplikacja kliencka najpierw łączy się z serwerem przekazując nazwę użytkownika i hasło (to nic innego jak ApiSecret i ApiKey). Następnie serwer po poprawnej autentykacji zwraca bearer token z podstawowymi informacjami (nazwa użytkownika, jego role, id...). Przy czym zaznaczam, że tego użytkownika traktujemy jako klienta API, a nie żywą osobę :)

Następnie aplikacja pokazuje okienko do logowania. I tu już wchodzi żywy user, który wpisuje sobie swoje dane do logowania do systemu: login: "gosia", hasło: "gosia123".
Te dane są przekazywane do API, które sprawdza, czy takiego użytkownika aplikacji można zalogować.

I w tym momencie pojawia mi się problem. Do tej pory widziałem to tak:
Jeśli użytkownika można zalogować do aplikacji, to tworzę nowe ClaimsIdentity i dodaję do Identities aktualnego ClaimsPrincipal.
Pomysł świetny, ale nie działa. Okazało się, że przy kolejnym requeście nie mam tych drugich Identities. Ale już nawet wiem dlaczego. Bo ClaimsPrincipal jest tworzony na podstawie otrzymanego tokena. Tyle, że ta wiedza nie rozwiązuje mojego problemu.

Co powinienem zrobić, żeby dodać do ClaimsPrincipal dodatkowe ClaimsIdentity i, żeby były one zachowane między requestami? (do momentu, kiedy użytkownik postanowi wylogować się z aplikacji)

0

OK, po sporej ilości przeczytanych materiałów, udało mi się opracować rozwiązanie. Podaję dla .NET Core 2:

W ConfigureServices musimy dodać autentykację Cookie. Całość powinna wyglądać mniej więcej tak:

services
                .AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

                })
                .AddJwtBearer(cfg =>
                {
                     //standardowe ustawienia
                })
                .AddCookie(AuthTypes.CLIENT_AUTHENTICATION_TYPE, cfg =>
                    {
                        //ustawienia cookie, a najważniejsze jest poniższe zdarzenie
                        cfg.Events.OnValidatePrincipal = (CookieValidatePrincipalContext ctx) =>
                        {
                            ClaimsPrincipal mainUser = ctx.HttpContext.User; //pobieramy ClaimsPrincipal pochodzące z JwtBearer
                            ClaimsPrincipal cookieUser = ctx.Principal; //pobieramy ClaimsPrincipal odczytane z Cookie

                            Debug.Assert(mainUser.Identities.Count() == 1);

                            //do głównego ClaimsPrincipal (pobranego z JwtBearer) dodajemy Identity, które w nim nie występują - tutaj trochę uproszczone
                            var claimsToAdd = cookieUser.Identities.Where(id => id.AuthenticationType != mainUser.Identities.ElementAt(0).AuthenticationType);
                            mainUser.AddIdentities(claimsToAdd);
                            return Task.CompletedTask;
                        };
                    }

                );

AuthTypes.CLIENT_AUTHENTICATION_TYPE - to po prostu string z jakąś nazwą dla tego typu autentykacji.

Następnie w tej samej metodzie musimy dać coś takiego:

services.AddMvc(config =>
            {
                var defaultPolicy = new AuthorizationPolicyBuilder(new[] { JwtBearerDefaults.AuthenticationScheme, AuthTypes.CLIENT_AUTHENTICATION_TYPE })
                    .RequireAuthenticatedUser()
                    .Build();
                config.Filters.Add(new AuthorizeFilter(defaultPolicy));
            });

Ważne jest przekazanie tej tablicy. Teraz autoryzacja będzie brała pod uwagę JwtBearer, natomiast cookie odczyta nam zapisane ClaimsPrincipal. A zapisujemy to tak (z poziomu kontrolera):

var authProps = new AuthenticationProperties
                {
                    IsPersistent = true,
                    IssuedUtc = DateTimeOffset.Now
                };
                await HttpContext.SignInAsync(AuthTypes.CLIENT_AUTHENTICATION_TYPE, User, authProps);

User to oczywiście ClaimsPrincipal z dodatkowymi ClaimsIdentity.

I tyle :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1