Uwierzytelnianie za pomocą JWT – ASP.NET Core 2.0

Mój poprzedni post dotyczył uwierzytelniania za pomocą Json Web Token w ASP.Net Core 1.1, jednak niedawno wyszedł .NET Core 2.0 i sposób użycia JWT jest już troszkę inny.

Zanim przejdę do opisania tych zmian, najpierw parę słów o .NET Core 2.0.

Przede wszystkim nowy .NET Core jest dużo szybszy od swojego poprzednika, teraz jest w stanie obsłużyć aż 20 tysięcy requestów na minutę. Olbrzymim plusem jest również zwiększona ilość dostępnych paczek dostępnych na nuget.org. W nowszej wersji został zmieniony również pliki .csproj. Od teraz możemy używać tylko jednej paczki Microsoft.AspNetCore.All, która umożliwia swobodne korzystanie z ASP.NET Core oraz Entity Framework Core. Jeśli chcesz zdobyć więcej informacji na temat .NET Core 2.0, wejdź na Announcing ASP.NET Core 2.0.

Wszystkim, którzy chcieliby zacząć swoją przygodę z .NET Core 2.0, gorąco zachęcam!

Aby zacząć migrację na .NET Core 2.0 należy w pierwszej kolejności zainstalować nową wersję SDK oraz zaktualizować Visual Studio. Następnie najlepiej poruszać się według poradnika Microsoftu, gdzie wszystko jest opisane krok po kroku. A więc, teraz możemy zacząć modyfikację mechanizmu Json Web Token.

Pierwszą różnicą jest fakt, że już nie konfigurujemy JWT w metodzie Configure() w pliku Startup.cs. W .NET Core 2.0 robimy to w metodzie ConfigureSevices(), czyli wtedy, gdy dodajemy dany serwis. Również sama konfiguracja trochę się różni:

services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg => {
    cfg.TokenValidationParameters = new TokenValidationParameters()
    {
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration[“Token:key”])),
        ValidIssuer = Configuration[“Token: issuer”],
        ValidAudience = Configuration[“Token:Audience”],
        ValidateIssuerSigningKey = true,
        ValidateLifetime = true
    };
});

Najpierw w metodzie ConfigureServices() konfigurujemy, iż będziemy korzystać z uwierzytelniania, a następnie specyfikujemy, że będzie to uwierzytelnianie za pomocą Json Web Token wraz z odpowiednią konfiguracją. W wersji 1.X property AutomaticAuthenticate oraz AutomaticChallenge były skierowane ku pojedynczemu schematowi autentykacji. W wersji 2.0 zostały one przeniesione do klasy AuthenticationOptions, gdzie możemy je ustalać według naszych potrzeb. Powinniśmy skonfigurować te property jeśli chcemy korzystać z atrybutu Authorize, który udostępnia dane dla zalogowanych użytkowników bądź też blokuje dla niezalogowanych.

W metodzie Configure należy dodać:

app.UseAuthentication();

co umożliwi korzystanie z uwierzytelniania JWT w aplikacji.

I to już! Nasz JWT w ASP.NET Core 2.0 jest skonfigurowany, od teraz możemy używać go tak, jak w ASP.NET Core 1.1. Jeśli chcesz dowiedzieć się więcej szczegółów na temat działania JWT oraz sposobie implementacji, przeczytaj mój poprzedni wpis Authentication in ASP.NET Core with JWT.

Advertisements

Authentication in ASP.NET Core with jwt

Dzisiejszy post dotyczy logowania używającego Json Web Token.  Mechanizm logowania polega na wygenerowaniu niepowtarzalnego tokena, na podstawie którego użytkownik może zostać zidentyfikowany. Następnie, przy innych wykonywanych requestach do API jest weryfikowana poprawność tokena, który otwiera dostęp do pozostałych funkcjonalności danego systemu. Celem jwt jest potwierdzenie, że dane zostały wysłane z prawdziwego, prawidłowego źródła.

Skoro już wiadomo, jak przebiega schemat uwierzytelniania, to jak naprawdę wygląda wygenerowany token?

Otóż token składa się z trzech osobnych części oddzielonych kropką.

 Header.Payload.Signature

Header zawiera informację, w jaki sposób podpis tokenu powinien być szyfrowany oraz deklaracje typu JWT.

Następną częścią tokenu jest Payload, który zawiera informację na temat danych – claims’ów, które chcemy przekazać za pośrednictwem tokena.

Ostatnim komponentem jest signature, czyli podpis, do utworzenia którego są potrzebne dwie poprzednie części tokenu – header oraz payload, które są szyfrowane za pomocą szyfru ustalonego w headerze oraz sekretnego klucza, ustawianego dla naszej aplikacji.

Aby zacząć implementacje JWT w projekcie asp.net core używającego Identity, należy zainstalować paczkę Microsoft.AspNetCore.Authentication.JwtBearer, używając Nuget’a bądź Package Manager Console.  Następnie w pliku Startup.cs, w metodzie Configure(), należy skonfigurować JwtBearerMiddleware z wybranymi opcjami, co pokazuje poniższy kod:

 app.UseJwtBearerAuthentication(new JwtBearerOptions
 {
     AutomaticAuthenticate = true,
     AutomaticChallenge = true,
     TokenValidationParameters = new TokenValidationParameters
     {
         IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Token:Key"])),
         ValidAudience = Configuration["Token:Audience"],
         ValidateIssuerSigningKey = true,
         ValidateLifetime = true,
         ValidIssuer = Configuration["Token:Issuer"]
     }
 });
 

Tworzymy tutaj klucz dostępu, który jest kluczem symetrycznym wygenerowanym z ustawionego przez nas w pliku konfiguracyjnym, klucza sekretnego, którego walidacja pozwoli zalogować się do aplikacji.

Następnym etapem jest stworzenie metody w kontrolerze, mającej za zadanie zalogowanie danego użytkownika.

[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginUserDto loginUserDto )
{
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }

    var user = await _userManager.FindByNameAsync(loginUserDto.Email);

    if (user == null || _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, loginUserDto .Password) != PasswordVerificationResult.Success)
    {
        return BadRequest();
    }

    var token = CreateJWTToken(user);

    return Ok(new
    {
        token = new JwtSecurityTokenHandler().WriteToken(token),
    });
}

Do mojej akcji kontrollera będę mogła odwołać się za pomocą metody HTTP POST “{{url}}/api/auth/login”.

W metodzie zaczynamy od sprawdzenia, czy ModelState jest poprawny, jeśli nie jest, zwracamy status 400 – Bad Request. Następnie, przy pomocy Identity, sprawdzamy, czy użytkownik z podanym adresem email istnieje w bazie danych. Jeśli użytkownik nie istnieje, bądź jego zahaszowana wersja hasła nie zgadzaja się z istniejącą w bazie danych, zwracamy Bad Request.

Więc skoro już mamy naszego użytkownika, najwyższy czas wygenerować token:

private JwtSecurityToken CreateJWTToken(User user)
{
    return new JwtSecurityToken(
        claims: GetTokenClaims(user),
        expires: DateTime.UtcNow.AddMinutes(15),
        signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Token:Key"])), SecurityAlgorithms.HmacSha256)
     );
}

Zauważ, że do stworzenia parametru signingCredentials używamy bezpiecznego oraz sekretnego klucza zapisanego w pliku konfiguracyjnym, tego samego, którego używaliśmy do skonfigurowania JWT w naszej aplikacji.

Za pomocą parametru “expires” ustawiamy, jak długo ma być aktywny nasz token, w tym przypadku jest to 15 minut. Przypisujemy tokenowi dodatkowe claimsy, które chcemy przekazać w tokenie.

private IEnumerable<Claim> GetTokenClaims(User user)
{
    return new List<Claim>
    {
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Email, user.Email),
            new Claim(JwtRegisteredClaimNames.NameId, user.Id)
    };
}

W tym przypadku w claims’ach znajduje się email oraz id użytkownika oraz guid, który zapewni dodatkowy unikatowy identyfikator naszemu tokenowi.

Gdy mamy już wygenerowany token, możemy zwrócić status 200 😉

Sprawdźmy jakie będą efekty!

W tym celu należy uruchomić Postmana, należy zaznaczyć, że chcemy wykonać metodę POST pod endpoint api/auth/login. W body wpisujemy dane, za pomocą których chcemy zalogować się do aplikacji. Efektem wyjściowym powinien być wygenerowany token.

Przechwytywanie

Aby zablokować dostęp do innych funkcjonalności aplikacji należy kontrolerowi nadać atrybut [Authorize]. Aby dostać się wtedy do niedostępnej metody za pomocą Postman’a należy dodać Header o wartości bearer + wygenerowany token.

Przechwytywanie1

Hello world, hello blog, hello post!

I oto jest, mój pierwszy wpis na blogu! Długo się zastanawiałam nad założeniem bloga, ale w końcu jest 😀 Blog to świetne miejsce, aby utrwalić i pogłębić swoją dotychczasową wiedzę, bo jak inaczej się przekonasz, czy naprawdę rozumiesz dane zagadnienie niż je po prostu opisując? A jeśli boisz się, zastanawiasz się, co pomyślą inni, czy Twoje wpisy będą dość wystarczająco wartościowe, nie martw się! Liczy się Twój postęp, Twoja ciągle powiększająca się wiedza, a na pewno i inni docenią Twoje chęci i starania.

Mam nadzieję, że komuś udzieli się chęć programowania, a może i ktoś jeszcze zdecyduje się na założenie własnego bloga i podzielenie się zdobytą dotychczas wiedzą. 😉