DEV Community

Cover image for Authentication with JSON Web Tokens in .NET Core 3.1
Patrick God
Patrick God

Posted on • Updated on

Authentication with JSON Web Tokens in .NET Core 3.1

This tutorial series is now also available as an online video course. You can watch the first hour on YouTube or get the complete course on Udemy. Or you just keep on reading. Enjoy! :)

Authentication (continued)

Token Authentication with JSON Web Tokens

The idea behind token authentication is simple.

At this stage of our application, the user can log in with her username and password. We verify the credentials and tell the user that the password is correct.

But if the user wants to call a function, where she needs to be authenticated, she would have to send the credentials again. That’s because the web service is stateless.

This means, we never know who sent a request. Unless we get some credentials with the request.

Instead of entering the credentials every single time with every request, we could store the username and password on the local or session storage of the browser and grab the information from there. But that’s highly insecure because everybody who has access to your computer could have a glance at your password.

That’s where tokens come in.

A token is nothing more than a long string that stores information - or claims - of the user. These claims do not consist of the password, but it could tell the server who the user is and what kind of rights the user might have.

The token is generated with a private key only the server knows. So it’s hard to fake such a token. And we can give that token an expiration date. So, even if someone would be able to steal your token, chances are that the token is invalid as soon as this certain someone tries to use it.

Since this token doesn’t consist of critical information in plain text, we can store it in the browser and automatically send it to the web service with every request.

The service then knows who the user is and may even send a new token back for your next request.

On the website jwt.io, we’re able to have a look at a JSON web token and even how the information is stored in it.

You see the header with the used algorithm and the payload with claims like the name of the user, for instance.

JWT Example

Okay, let’s use JSON web tokens now for our Web API.

JSON Web Tokens (JWT) preparations

There are some things we have to do first before we write the actual code.

We start with the appsettings.json. Here we enter the security key for the JWT authentication. Below the ConnectionStrings section, we can create a new section AppSettings and just enter a new key called Token and set any kind of string as value. For instance, my top secret key would be totally sufficient here. Just make sure that it has at least 16 characters.

"AppSettings": {
  "Token": "my top secret key"
},
Enter fullscreen mode Exit fullscreen mode

After that, we have to add some package reference to our application.

In Visual Studio Code you can do that by pressing Ctrl + Shift + P, enter Add Package to get the entry NuGet Package Manager: Add Package and then you can enter the desired package.

Add Package

We need a total of three package references.

The first one is Microsoft.IdentityModel.Tokens. This package includes types that provide support for security tokens and cryptographic operations like signing and verifying signatures. You can always choose the latest version.

After adding the package reference, Visual Studio Code wants to execute the restore command to resolve the new dependencies. Of course, we can do that.

Restore

By the way, instead of using the NuGet Package Manager, you could have entered dotnet add package Microsoft.IdentityModel.Tokens into the terminal to add the latest version. Both ways work. It’s up to you what you prefer.

The second package is System.IdentityModel.Tokens.Jwt. This one provides support for creating, serializing and validating JSON web tokens. Exactly what we need.

And the last one is Microsoft.AspNetCore.Authentication.JwtBearer. This is a .NET Core middleware that enables our application to receive bearer tokens.

A bearer token is just a general name for the token we have already discussed. Some servers use short strings as tokens, we will utilize structured JSON web tokens. Both can be called bearer tokens.

Alright. After adding these packages, your .csproj file should be a bit bigger now and include these new references.

<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="5.6.0"/>
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.6.0"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.2"/>
Enter fullscreen mode Exit fullscreen mode

Now we’re ready to write some code.

JSON Web Tokens (JWT) implementations

In the AuthRepository we start by adding a new private method called CreateToken() that returns a string and takes a User object as an argument.

private string CreateToken(User user)
{
    return string.Empty; //token;
}
Enter fullscreen mode Exit fullscreen mode

Let’s return an empty string for now, so that we can already call this method in the Login() method and set the response.Data accordingly if the user has entered the correct password.

public async Task<ServiceResponse<string>> Login(string username, string password)
{
    ServiceResponse<string> response = new ServiceResponse<string>();
    User user = await _context.Users.FirstOrDefaultAsync(x => x.Username.ToLower().Equals(username.ToLower()));
    if (user == null)
    {
        response.Success = false;
        response.Message = "User not found.";
    }
    else if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
    {
        response.Success = false;
        response.Message = "Wrong password.";
    }
    else
    {
        response.Data = CreateToken(user);
    }
    return response;
}
Enter fullscreen mode Exit fullscreen mode

Back to the CreateToken() method, we declare a List of Claims.

We will have to add some using directives while implementing this method.

The first claim type we add is the ClaimTypes.NameIdentifier. This will be the Id of the given user. The second one is ClaimTypes.Name, which will simply be the Username.

List<Claim> claims = new List<Claim>
{
    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
    new Claim(ClaimTypes.Name, user.Username)
};
Enter fullscreen mode Exit fullscreen mode

Next, we need a SymmetricSecurityKey. This is the secret key from our appsettings.json file. We need to be able to access the file, so we jump to the constructor of the AuthRepository and inject the IConfiguration. Make sure to add the Microsoft.Extensions.Configuration using directive.

private readonly IConfiguration _configuration;
public AuthRepository(DataContext context, IConfiguration configuration)
{
    _configuration = configuration;
    _context = context;
}
Enter fullscreen mode Exit fullscreen mode

Back to the CreateToken() method, we create an instance of the SymmetricSecurityKey class and give it a byte array. We do that with Encoding.UTF8.GetBytes() and then access the _configuration to get the proper section with our secret key as value.

SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8
    .GetBytes(_configuration.GetSection("AppSettings:Token").Value));
Enter fullscreen mode Exit fullscreen mode

With that key, we create new SigningCredentials and use the HmacSha512Signature algorithm for that.

SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
Enter fullscreen mode Exit fullscreen mode

Next is the SecurityTokenDescriptor. This object gets the information used to create the final token. We’ll give it the claims and an expiration date, for instance.

To set the claims, we set the Subject with a new ClaimsIdentity and give it the claims we created before.

Expires can be set to any date. How about the next day? So DateTime.Now.AddDays(1).

And finally, the SigningCredentials, which is our creds object.

SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(claims),
    Expires = DateTime.Now.AddDays(1),
    SigningCredentials = creds
};
Enter fullscreen mode Exit fullscreen mode

We are almost done.

Now we need a new JwtSecurityTokenHandler and use this tokenHandler and the tokenDescriptor to create the SecurityToken.

JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
Enter fullscreen mode Exit fullscreen mode

And finally, with tokenHandler.WriteToken(token); we return the JSON web token as a string.

private string CreateToken(User user)
{
    List<Claim> claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.Username)
    };

    SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8
        .GetBytes(_configuration.GetSection("AppSettings:Token").Value));

    SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

    SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),
        Expires = DateTime.Now.AddDays(1),
        SigningCredentials = creds
    };

    JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
    SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);
}
Enter fullscreen mode Exit fullscreen mode

Alright, take a deep breath and let’s test this in Postman. HTTP Method is POST, we use the login URL, the correct credentials and hit ‘Send’.

{
    "data": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJwYXRyaWNrIiwibmJmIjoxNTgyMTA1MDEyLCJleHAiOjE1ODIxOTE0MTIsImlhdCI6MTU4MjEwNTAxMn0.FaxvO8pqLLkBjplEW815-DzekgBwW94gBx--_3n4X5UAs6kRuM2zflpwQ0H2PnCgIuupJKq7EED5c_mC_DI8FQ",
    "success": true,
    "message": null
}
Enter fullscreen mode Exit fullscreen mode

There is our token!

We can grab this token now and have a deeper look at the JWT debugger on jwt.io.

Debug JWT

When you paste the token, you’re able to see the claims we have entered in the code.

And if you enter the correct key, the signature can be verified.

Great! So that’s how we get our JSON Web Token.

Authorize Attribute

To secure web service calls or even a complete controller, we can use the [Authorize] attribute on top of the controller class or on top of any method you want to secure. But before we can use this attribute, we have to add an authentication scheme to the web service. We do that in the Startup class.

In the ConfigureServices() method, we use AddAuthentication() with the JwtBearerDefaults.AuthenticationScheme and add some configuration options with AddJwtBearer().

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
});
Enter fullscreen mode Exit fullscreen mode

Regarding the options we initialize a new instance of TokenValidationParameters and set these parameters.

We want to validate the signing key, so we set ValidateIssuerSigningKey to true.

The IssuerSigningKey is again the one from our appsettings.json file. So a new SymmetricSecurityKey that gets the encoded AppSettings:Token value.

We don’t need to validate the issuer or the audience, hence we set ValidateIssuer and ValidateAudience to false.

So far the authentication scheme.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII
            .GetBytes(Configuration.GetSection("AppSettings:Token").Value)),
        ValidateIssuer = false,
        ValidateAudience = false
    };
});
Enter fullscreen mode Exit fullscreen mode

Additionally, we have to add the .NET Core AuthenticationMiddleware to the IApplicationBuilder to enable authentication capabilities.

To do that, we add the line app.UseAuthentication(); above app.UseAuthorization();. It’s important to add the line there.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    //app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}
Enter fullscreen mode Exit fullscreen mode

Alright. With that in place, we can make use of the authentication. To do that we add the [Authorize] attribute on top of the CharacterController class.

[Authorize]
[ApiController]
[Route("[controller]")]
public class CharacterController : ControllerBase
Enter fullscreen mode Exit fullscreen mode

If we now want to get all RPG characters with Postman using GET as HTTP method and the URL http://localhost:5000/character/getall, we get a 401 Unauthorized back.

GetAll Unauthorized

That’s exactly what we want! So now we have to add a token to the header of our request. Let’s login first to get the JSON web token.

Copy JWT

Just copy the token and then go back to the GetAll call.

Here we have to add a header. We add the key Authorization and add the token as value. The token itself is not enough. We have to add the term Bearer with a space in front of the token.

Now we’re finally able to get all RPG characters again.

GetAll with Bearer Token

There’s one little thing I’d like to add here.

To call any method of the CharacterController, we have to be authenticated. But we can make exceptions to the rule. For instance, if we add the attribute [AllowAnonymous] on top of the GetAll() method, we can call this method again without being authenticated.

[AllowAnonymous]
[HttpGet("GetAll")]
public async Task<IActionResult> Get()
Enter fullscreen mode Exit fullscreen mode

In Postman you can see that we can remove the Authorization key in the header and still get the RPG characters from the database.

Allow anonymous

Let’s remove the attribute again, because we only want authenticated users to get characters.

Actually, we want users to get their created characters. At the moment they can see all RPG characters.

So we have to make use of the relation between users and characters and we also have to get the claims from the authenticated user.

Read Claims & Get the User’s RPG Characters

One of the beauties of the ControllerBase class is that it provides a User object of type ClaimsPrincipal.

// Summary:
//     Gets the System.Security.Claims.ClaimsPrincipal for user associated with the
//     executing action.
public ClaimsPrincipal User { get; }
Enter fullscreen mode Exit fullscreen mode

This User object provides all the claims we added to the JSON web token.

Let’s get the NameIdentifier which was the Id of our authenticated user from the database.

In the Get() method of the CharacterController we define a new int variable.

With User.Claims we access the claims and then we iterate through them or find the one we want with FirstOrDefault() followed by a lambda expression where the Type of the claim equals ClaimTypes.NameIdentifier.

From that result, we grab the Value and parse it to an int.

int id = int.Parse(User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value);
Enter fullscreen mode Exit fullscreen mode

Now we have to pass this id to the CharacterService. So we make a little change to the GetAllCharacters() method and add the id as an argument in the corresponding interface as well as the service and maybe call this argument userId to avoid any confusion.

Task<ServiceResponse<List<GetCharacterDto>>> GetAllCharacters(int userId);
Enter fullscreen mode Exit fullscreen mode

Then we can call the method with the proper userId we got from the claims and make a slight modification to the service method.

public async Task<IActionResult> Get()
{
    int userId = int.Parse(User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value);
    return Ok(await _characterService.GetAllCharacters(userId));
}
Enter fullscreen mode Exit fullscreen mode

The only thing we have to change in the GetAllCharacters() method is how we access the Characters from the _context. Instead of returning all of the RPG characters, we make use of the Where() function and only return the characters that are related to the given user with the help of the userId.

List<Character> dbCharacters = await _context.Characters.Where(c => c.User.Id == userId).ToListAsync();
Enter fullscreen mode Exit fullscreen mode

Thanks to Entity Framework we can access the related User object and its Id and actually get the RPG characters that have the proper UserId set in the database table.

When we test that with Postman, we get no characters back. That’s absolutely correct because we haven’t set any relations, yet.

But we can fix that real quick.

In the Characters table, we simply set the UserId of Frodo to 1. Run the test again, and now we’re getting Frodo! Setting the UserId for Sam as well, we’re getting both our heroes back.

Great! This works. Of course, we want to add the relations with our code and not manually in the database every single time. Before we do that, let’s sum up what you have learned in this section.

Summary

Congratulations! You successfully implemented JSON Web Token Authentication in your Web API.

We started by creating the User model and adding this new User as a relation to our RPG characters. This is a one-to-many relation, which means that one user can have several characters.

Then we were already diving into the theory of authentication. You learned how authentication works in general with password hash values and password salts and why you should use hashed values and salts. It’s all about security.

We built the user registration and the user login and used a specific cryptography algorithm to hash and verify the entered passwords.

After that, we covered token authentication with JSON web tokens.

You now know what a bearer token is, what you find in that token - like the used algorithm and the payload - and how you can add any claims you want to add to that token.

So you learned how you can create a JSON web token and how to secure your Web API with the [Authorize] attribute. While doing that, you also learned how to read the claims from the JSON web token and use them to return the correct data to the user.

Great! Now it’s time to add some more relations to our application. What about some skills to use during fights, for instance? And, of course, let’s add the correct relation between users and characters as soon as a character has been generated.

We do all that in the next section.


That's it for the 8th part of this tutorial series. I hope it was useful for you. To get notified for the next part, simply follow me here on dev.to or subscribe to my newsletter. You'll be the first to know.

See you next time!

Take care.


Next up: Advanced Relationships with Entity Framework Core

Image created by cornecoba on freepik.com.


But wait, there’s more!

Top comments (16)

Collapse
 
kjrjessop profile image
Kyle

Hey Patrick, thanks for taking the time to make this awesome series, its one of the best I've found to date! The next sections can't come quick enough!

Any chance you could cover seeding data to a DB so that we can seed the setup data each time a new instance of the application is run? ie. the data like rpg classes which we wouldn't want to recreate each time, but rather just seed the standard 5 or 6 classes that one can choose?

Another idea might be to cover role based calls so that if I am an "admin" user, a call might return different/more data than if I was a "standard" user?

Thanks again and keep the content coming!

PS. In your video series maybe you could also include setup on a Mac? SQL is not available for Mac users so I wasn't able to easily connect to a local DB. It took a little research but I eventually got my SQL DB up and running on a docker image.

Collapse
 
_patrickgod profile image
Patrick God

Hi,

Thank you very much! :)

These are good ideas. I will remember them and probably add them as "bonus" chapters - at least regarding seeding the database and role based authentication.

I'm afraid I don't have a Mac. But maybe I'll add a chapter with SQLite. You can use SQLite on MacOS, hence you wouldn't have to use SQL Server with Docker. The code should stay the same, just the connection string would be different, I guess.

Let me think about this. ;)

Take care,
Patrick

Collapse
 
tintow profile image
Tintow

Hi Patrick,

This is a great series, thanks for the effort you are putting into this.

In this episode, CreateToken is first shown as being declared 'static' but I don't think it should be?

Many thanks!

Collapse
 
_patrickgod profile image
Patrick God

Hello again and thank you very much again. :)

And you are correct again, I removed the static keyword.

Stay healthy!
Patrick

Collapse
 
npiasecki profile image
Nicholas Piasecki

This is the most lucid, clear explanation I could find of how to do this. I'm a long-time .NET developer, but sometimes walking into a new framework there's just so much any abstractions that it's hard to know where to start. Thank you!

Collapse
 
_patrickgod profile image
Patrick God

Thank you very much, Nicholas! :)

Collapse
 
nasierjaffer profile image
nasierjaffer

I have learnt a ton from this series, thank you for selflessly sharing Patrick!

Would you mind showing us how we can go about resetting a users password? The username in my test application is a email address.

I have implemented the following mailkit solution and it works pretty well apart from minor bugs that was fixed in the version I am using - Version="2.10.1

github.com/ffimnsr-ext/article-dem...

Collapse
 
vpvpp profile image
vinod patil

Thank you Patrick for this awesome series..!!
I like the way you have devided the content step by step.

I am still newbie in asp . net core world and learn a lot from this series regarding web api and repository& service pattern.

God bless you...!!

Regards,
Vinod

Collapse
 
_patrickgod profile image
Patrick God

Thank you so much! Means a lot! Really glad the course resonates with you.
And please stay tuned! New updates and a complete new series on Blazor WebAssembly are coming soon. :)
Take care & stay safe,
Patrick

Collapse
 
aaiing profile image
AAI INGENIERIA

Hi Patrick! i trying contact you from Twitter DM and you have disabled sending message, i fowared in graphql-authentication and i need how to show graphql login response with only token and id_user. Help me this problem i pay for it, thanks a lot coleague.

Collapse
 
_patrickgod profile image
Patrick God

Hey,
Thanks for the wait. DMs work now. I do not quite understand your question. Feel free to elaborate, also on Twitter now if you like.
Take care,
Patrick

Collapse
 
metapone profile image
metapone

When you used AddJwtBearer in Startup.cs, the AppSettings token was converted by the GetBytes method of Encoding.ASCII instead of Encoding.UTF8 like in AuthController. But then I tried running it and it still worked. Could you explain to me why?

Collapse
 
aaiing profile image
AAI INGENIERIA

Patrick how much, to learn programming .NET CORE 3.1 GRAPHQL, stage login with JWT via private stream??

Collapse
 
_patrickgod profile image
Patrick God

Happy to help with that. Please send me a DM on Twitter. :)

Collapse
 
markers16123 profile image
markers16123 • Edited

Should we manually manage tokens in this scenario? Is there an article explaining an example of combining middleware with UserStore to work?

Collapse
 
padmanathanramasamy profile image
padmanathan-ramasamy

Please share the GIT location and can you implement role based authentication in this sample.