DEV Community

Cover image for Token based Authentication using .NET Core Identity
Marian Salvan
Marian Salvan

Posted on • Edited on

Token based Authentication using .NET Core Identity

Introduction

Token-based authentication is a security mechanism where users authenticate once and receive an access token, which is then used to access protected resources without needing to re-enter credentials. Access tokens have a limited lifespan, and when they expire, a refresh token can be used to obtain a new access token without requiring the user to log in again.

Tokens should be securely stored on the client—typically in HTTP-only cookies for better security or local storage (though this carries risks). If a token is compromised or a user logs out, the system can revoke the token, preventing further unauthorized access and enhancing security and session control.

In the previous session, we configured a basic .NET Core web API to use .NET Core Identity and generated the default Identity tables in PostgreSQL. In this session, we will build on that setup to illustrate how to implement token-based authentication.

This guide will contain the following sections:

  1. Initial configuration
  2. User registration
  3. Tokens management
  4. User login
  5. Refresh tokens lifecycle

Initial configuration

Step 1 - Add JwtBearer nuget package:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Step 2 - Add configuration

  • If you are running the service locally, then add the following section to your appsettings.json
{
  //other setttings
  "Authentication": {
    "TokenSecret": "thisisthesecretforgeneratingakey(mustbeatleast32bitlong)",
    "RefreshTokenSecret": "thisisthesecretforgeneratingakey(mustbeatleast32bitlong)",
    "Issuer": "http://localhost:8080",
    "Audience": "my-web-api-client"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • If you are running the service with Docker Compose, add the following configuration to the environment sections of you API:
#api configuration
environment:
  - Authentication__TokenSecret=thisisthesecretforgeneratingakey(mustbeatleast32bitlong)
  - Authentication__RefreshTokenSecret=thisisthesecretforgeneratingakey(mustbeatleast32bitlong)
  - Authentication__Issuer=http://localhost:8080
  - Authentication__Audience=my-web-api-client
  #other environment variables
Enter fullscreen mode Exit fullscreen mode

Step 3 - Register the JwtConfiguration in the code

  • Settings record - the values for this record are specified above. The TokenSecret and RefreshTokenSecret are secret keys used to generate and validate access tokens and refresh tokens, respectively, ensuring security. The Issuer specifies the server that generates the tokens, while the Audience defines the intended recipient, ensuring only authorized clients can use the tokens
namespace DemoBackend.Configuration
{
    public record AuthenticationSettings
    {
        public required string TokenSecret { get; init; }
        public required string RefreshTokenSecret { get; init; }
        public required string Issuer { get; init; }
        public required string Audience { get; init; }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Extension method for adding authorization
using DemoBackend.Configuration;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

namespace DemoBackend.Extensions
{
    public static class AuthenticationExtensions
    {
        public static void AddJwtAuthentication(this IServiceCollection services, IConfiguration configuration)
        {
            var authSettings = configuration.GetSection("Authentication").Get<AuthenticationSettings>();

            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ClockSkew = TimeSpan.Zero,
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateIssuerSigningKey = true,
                        ValidIssuer = authSettings.Issuer,
                        ValidAudience = authSettings.Audience,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(authSettings.TokenSecret))
                    };
                });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Adding the configuration to Program.cs
var builder = WebApplication.CreateBuilder(args);

//register the authentication settings
builder.Services.Configure<AuthenticationSettings>(builder.Configuration.GetSection("Authentication"));

//register the authentication settings
builder.Services.AddJwtAuthentication(builder.Configuration);

//other registrations

var app = builder.Build();

//other registrations

app.UseAuthentication();

app.UseAuthorization();

//other registrations
Enter fullscreen mode Exit fullscreen mode

Step 4 - UserManagementController

Now that we have everything set up, we can create a new controller to handle the user account workflow. UserManager is a class that can be found in .NET Core Identity that offers most of the methods you will need to operate on an UserEntity.

using DemoBackend.Configuration;
using DemoBackend.Constants;
using DemoBackend.Data;
using DemoBackend.Helpers;
using DemoBackend.Requests;
using DemoBackend.Responses;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Security.Claims;
using LoginRequest = DemoBackend.Requests.LoginRequest;
using RegisterRequest = DemoBackend.Requests.RegisterRequest;

namespace DemoBackend.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class UserManagementController : ControllerBase
    {
        private readonly UserManager<UserEntity> _userManager;
        private readonly AuthenticationSettings _authSettings;

        public UserManagementController(UserManager<UserEntity> userManager, IOptions<AuthenticationSettings> authSettings)
        {
            _userManager = userManager;
            _authSettings = authSettings.Value;
        }

      //implementation
     }
}
Enter fullscreen mode Exit fullscreen mode

User registration

Step 1 - registration request

namespace DemoBackend.Requests
{
    public record RegisterRequest
    {
        public required string Email { get; set; }
        public required string Password { get; set; }
        public required string ConfirmPassword { get; set; }
        public required string Role { get; set; }
        public required int Age { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2 - registration method

Register functionality implemented inside the UserManagementController

    [HttpPost("register")]
    public async Task<IActionResult> Register([FromBody] RegisterRequest registerRequest)
    {
        try
        {
            if (!registerRequest.Password.Equals(registerRequest.ConfirmPassword))
            {
                return BadRequest("Passwords do not match.");
            }

            if (!registerRequest.Role.Equals(ApplicationRoles.Admin) && !registerRequest.Role.Equals(ApplicationRoles.User))
            {
                return BadRequest("You must provide either ADMIN or USER roles");
            }

            var user = new UserEntity
            {
                UserName = registerRequest.Email,
                Email = registerRequest.Email,
                Age = registerRequest.Age
            };

            //create user
            var result = await _userManager.CreateAsync(user, registerRequest.Password);

            if (!result.Succeeded)
            {
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError(error.Code, error.Description);
                }
                return BadRequest(ModelState);
            }

            //assign proper rolo to the user
            if (!string.IsNullOrEmpty(registerRequest.Role))
            {
                await _userManager.AddToRoleAsync(user, registerRequest.Role);
            }

            return Ok("User registered successfully.");
        }
        catch (Exception ex)
        {
            return StatusCode(500, $"Internal server error: {ex.Message}");
        }
    }
Enter fullscreen mode Exit fullscreen mode

Possible roles for a user:

namespace DemoBackend.Constants
{
    public static class ApplicationRoles
    {
        public const string Admin = "ADMIN";
        public const string User = "USER";
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3 - testing registration

You can register a new user using the following curl:

curl --location 'http://localhost:8080/api/usermanagement/register' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "testuser@example.com",
  "password": "Test@1234",
  "confirmPassword": "Test@1234",
  "role": "USER",
  "age": 25
}'
Enter fullscreen mode Exit fullscreen mode

If successful, the new user will be added into AspNetUsers table:
Image description

Tokens management

Before diving into other methods, we first need to focus on token generation. As mentioned in the introduction, we need two tokens: a short-lived access token and a long-lived refresh token.

The access token is used to grant users access to restricted resources. Since it expires fairly quickly, we need a refresh token—this token will be sent to a specialized refresh method that issues a new valid access token.

The refresh token has a longer lifespan and is stored in the database. Below is a helper class containing the three essential methods for this workflow: GenerateAccessToken, GenerateRefreshToken and ValidateRefreshToken.

using DemoBackend.Configuration;
using DemoBackend.Constants;
using DemoBackend.Data;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace DemoBackend.Helpers
{
    public static class TokenProviderHelper
    {
        public static (string, DateTime) GenerateAccessToken(AuthenticationSettings authenticationSettings, UserEntity user, string role)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(authenticationSettings.TokenSecret));
            var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

            var claimsForToken = new List<Claim>
            {
                new(ApplicationClaims.Id, user.Id),
                new(ApplicationClaims.Email,!string.IsNullOrEmpty(user.Email) ? user.Email : string.Empty),
                new(ApplicationClaims.Age, user.Age.ToString()),
                new(ApplicationClaims.Role, role.ToUpper()),
                new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            };

            var expirationDate = DateTime.UtcNow.AddMinutes(15);

            var jwtSecurityToken = new JwtSecurityToken(
                          authenticationSettings.Issuer,
                          authenticationSettings.Audience,
                          claimsForToken,
                          DateTime.UtcNow,
                          expirationDate,
                          signingCredentials);

            var tokenToReturn = new JwtSecurityTokenHandler()
               .WriteToken(jwtSecurityToken);

            return (tokenToReturn, expirationDate);
        }

        public static string GenerateRefreshToken(AuthenticationSettings authenticationSettings)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(authenticationSettings.RefreshTokenSecret));
            var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
            var expirationDate = DateTime.UtcNow.AddHours(12);

            var jwtSecurityToken = new JwtSecurityToken(
                          authenticationSettings.Issuer,
                          authenticationSettings.Audience,
                          null,
                          DateTime.UtcNow,
                          expirationDate,
                          signingCredentials);

            var tokenToReturn = new JwtSecurityTokenHandler()
               .WriteToken(jwtSecurityToken);

            return tokenToReturn;
        }

        public static bool ValidateRefreshToken(AuthenticationSettings authenticationSettings, string refreshToken)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(authenticationSettings.RefreshTokenSecret));

            var validationParameters = new TokenValidationParameters()
            {
                IssuerSigningKey = securityKey,
                ValidIssuer = authenticationSettings.Issuer,
                ValidAudience = authenticationSettings.Audience,
                ValidateIssuerSigningKey = true,
                ValidateIssuer = true,
                ValidateAudience = true,
                ClockSkew = TimeSpan.Zero
            };

            var tokenHandler = new JwtSecurityTokenHandler();
            var result = tokenHandler.ValidateToken(refreshToken, validationParameters, out _);

            return result != null;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice that when we generate a new access token, we add a list of claims to the token. These claims are pieces of data about the user embedded in the access token. They can be used by the issuer of the login request (e.g., a web application may use the email and ID for subsequent requests).

In this demo app, the available claims are as follows:

namespace DemoBackend.Constants
{
    public static class ApplicationClaims
    {
        public const string Id = "userId";
        public const string Email = "userEmail";
        public const string Age = "userAge";
        public const string Role = "userRole";
    }
}
Enter fullscreen mode Exit fullscreen mode

User login

Step 1 - login request

namespace DemoBackend.Requests
{
    public record LoginRequest
    {
        public required string Email { get; set; }
        public required string Password { get; set; }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2 - login method

If the login process is successful, this method will return the access token and the refresh token for that user.

  [HttpPost("login")]
  public async Task<IActionResult> Login([FromBody] LoginRequest loginRequest)
  {
      try
      {
          var user = await _userManager.FindByEmailAsync(loginRequest.Email);
          if (user == null)
          {
              return Unauthorized("Invalid email or password.");
          }

          var result = await _userManager.CheckPasswordAsync(user, loginRequest.Password);
          if (!result)
          {
              return Unauthorized("Invalid email or password.");
          }

          //fetch the user role so it can be addded on claims list inside the toke
          var userRoles = await _userManager.GetRolesAsync(user);
          var role = userRoles.FirstOrDefault() ?? string.Empty;

          //generate access token and refresh token
          var (token, expiration) = TokenProviderHelper.GenerateAccessToken(_authSettings, user, role.ToUpperInvariant());
          var refreshToken = TokenProviderHelper.GenerateRefreshToken(_authSettings);

          //save refresh token in the database
          //we will cover this in the refresh token section bellow
          var success = await _tokensRepository.UpsertUserRefreshTokenAsync(user.Id, refreshToken);
          if (!success)
          {
              return Unauthorized("Invalid refresh token.");
          }

          return Ok(new AuthenticationResponse
          { 
              AccessToken = token,
              AccessTokenExpirationTime = expiration,
              RefreshToken = refreshToken 
          });
      }
      catch (Exception ex)
      {
          return StatusCode(500, $"Internal server error: {ex.Message}");
      }
  }
Enter fullscreen mode Exit fullscreen mode

Step 3 - testing login

You can login with an existing user using the following curl:

curl --location 'http://localhost:8080/api/usermanagement/login' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "testuser@example.com",
  "password": "Test@1234"
}'
Enter fullscreen mode Exit fullscreen mode

If the login is successful, you will get a response that looks like the following:

{
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjZDEyNWVlZi1jMDUwLTQ3MDktYTZjZi0yNmYxNDRkMmMzNDUiLCJ1c2VyRW1haWwiOiJ0ZXN0dXNlckBleGFtcGxlLmNvbSIsInVzZXJBZ2UiOiIyNSIsInVzZXJSb2xlIjoiVVNFUiIsImp0aSI6ImFhZGUxN2MxLTU3MzYtNDE5ZS1iZjJmLTdlNGNmMjhkMDM2ZCIsIm5iZiI6MTczODE3MzM1NCwiZXhwIjoxNzM4MTc2OTU0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJhdWQiOiJteS13ZWItYXBpLWNsaWVudCJ9.GAdBDFYvXD59reeTFg5KTfcmF0gB3WnQOvwbCjkuxg0",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MzgxNzMzNTQsImV4cCI6MTczODIxNjU1NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiYXVkIjoibXktd2ViLWFwaS1jbGllbnQifQ.Iu176n8uCwzKOVmMdg4_8pMukbV0GqmkHGrVBXoVVm4",
    "accessTokenExpirationTime": "2025-01-29T18:55:54.3401878Z"
}
Enter fullscreen mode Exit fullscreen mode

Step 4 - decoding an access token

Now that we have a valid access token we can go to https://jwt.io/ and decode it:

Image description

The access token consists of 3 parts: a header, payload (which contains the claims) and a signature (signed using our super secret key provided in the configuration).

Refresh tokens lifecycle

Great, we now have an access token. If you take a look at the TokenProviderHelper class above, you’ll notice that the validity of this token is only 15 minutes. This means you can only use this token for 15 minutes to access restricted resources. The short validity helps minimize the damage in case the token is stolen, as it’s stored on the client side.

This is where refresh tokens come in. Refresh tokens are stored in the database after a successful login. These tokens are then passed to a separate method to issue a new access token. If we want to log out and delete the refresh token, we need to implement another method to clean up the token. These 2 methods can only be accessed by a logged in users (they are marked with the [Authorize] attribute and the access token needs to passed in the Authorization header when making the request).

Step 1 - Implement a TokenRepository for these operations

using DemoBackend.Constants;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

namespace DemoBackend.Data
{
    public interface ITokensRepository
    {
        Task<IdentityUserToken<string>?> GetUserRefreshTokenAsync(string refreshToken);
        Task<bool> UpsertUserRefreshTokenAsync(string userId, string refreshToken);
        Task<bool> RemoveUserRefreshTokenAsync(string userId);
    }

    public class TokensRepository(ApplicationDbContext applicationDbContext) : ITokensRepository
    {
        public async Task<IdentityUserToken<string>?> GetUserRefreshTokenAsync(string refreshToken)
        {
            return await applicationDbContext.UserTokens.SingleOrDefaultAsync(x => x.Value == refreshToken);
        }

        public async Task<bool> UpsertUserRefreshTokenAsync(string userId, string refreshToken)
        {
            var existingUserToken = await applicationDbContext.UserTokens
                .FindAsync(userId, UserTokenConstants.LocalProvider, UserTokenConstants.Refresh);

            if (existingUserToken == null)
            {
                applicationDbContext.UserTokens.Add(new IdentityUserToken<string>
                {
                    UserId = userId,
                    LoginProvider = UserTokenConstants.LocalProvider,
                    Name = UserTokenConstants.Refresh,
                    Value = refreshToken
                });
            }
            else
            {
                existingUserToken.Value = refreshToken;
                applicationDbContext.UserTokens.Update(existingUserToken);
            }

            return (await applicationDbContext.SaveChangesAsync()) == 1;
        }

        public async Task<bool> RemoveUserRefreshTokenAsync(string userId)
        {
            var userToken = await applicationDbContext.UserTokens
                .FindAsync(userId, UserTokenConstants.LocalProvider, UserTokenConstants.Refresh);

            if (userToken == null)
            {
                throw new ArgumentNullException(nameof(userId));
            }

            applicationDbContext.UserTokens.Remove(userToken);

            return (await applicationDbContext.SaveChangesAsync()) == 1;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We also need some custom constants to work with the table provided by Identity (AspNetUserTokens):

namespace DemoBackend.Constants
{
    public static class UserTokenConstants
    {
        public const string Refresh = "refresh";
        public const string LocalProvider = "local";
    }
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to register this Repository inside the DI container inProgram.csfile:


var builder = WebApplication.CreateBuilder(args);

//other registrations

//register TokensRepository
builder.Services.TryAddScoped<ITokensRepository, TokensRepository>();

//other registrations

Enter fullscreen mode Exit fullscreen mode

Also, add the dependency to the UserManagementController:

  [Route("api/[controller]")]
  [ApiController]
  public class UserManagementController : ControllerBase
  {
      private readonly UserManager<UserEntity> _userManager;
      private readonly AuthenticationSettings _authSettings;
      private readonly ITokensRepository _tokensRepository;

      public UserManagementController(UserManager<UserEntity> userManager, IOptions<AuthenticationSettings> authSettings,
          ITokensRepository tokensRepository)
      {
          _userManager = userManager;
          _authSettings = authSettings.Value;
          _tokensRepository = tokensRepository;
      }

      //implementation
}
Enter fullscreen mode Exit fullscreen mode

The login method mentioned above already integrates refresh token generation and saves it into the database. If you take a look at the AspNetUserTokens table, you will see the token generated during login:

Image description

Step 2 - Refresh token method

  • Method implementation:
   [Authorize]
   [HttpPost("refresh-token")]
   public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequest refreshTokenRequest)
   {
       try
       {
           if (!TokenProviderHelper.ValidateRefreshToken(_authSettings, refreshTokenRequest.RefreshToken))
           {
               return Unauthorized("Invalid refresh token.");
           }

           var savedToken = await _tokensRepository.GetUserRefreshTokenAsync(refreshTokenRequest.RefreshToken);
           if (savedToken == null)
           {
               return Unauthorized("Invalid refresh token.");
           }

           var user = await _userManager.FindByIdAsync(savedToken.UserId);
           if (user == null)
           {
               return Unauthorized("Invalid refresh token.");
           }

           //since we are logged in we can get the role from the token
           var role = HttpContext.User.FindFirstValue(ApplicationClaims.Role);

           //generate access token and refresh token
           var (token, expiration) = TokenProviderHelper.GenerateAccessToken(_authSettings, user, role);

           return Ok(new RefreshTokenResponse
           {
               AccessToken = token,
               AccessTokenExpirationTime = expiration,
           });
       } 
       catch (Exception ex)
       {
           return StatusCode(500, $"Internal server error: {ex.Message}");
       }
   }
Enter fullscreen mode Exit fullscreen mode
  • Method testing - you can use the following curl:
curl --location 'http://localhost:8080/api/usermanagement/refresh-token' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjZDEyNWVlZi1jMDUwLTQ3MDktYTZjZi0yNmYxNDRkMmMzNDUiLCJ1c2VyRW1haWwiOiJ0ZXN0dXNlckBleGFtcGxlLmNvbSIsInVzZXJBZ2UiOiIyNSIsInVzZXJSb2xlIjoiVVNFUiIsImp0aSI6IjgwOGIzNGNlLTg2NmYtNDM2YS04N2YyLWRjYmVkNDllYWRlOCIsIm5iZiI6MTczODE3NDkyNCwiZXhwIjoxNzM4MTc4NTI0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJhdWQiOiJteS13ZWItYXBpLWNsaWVudCJ9.fPhuRF5p3zWvVbXwqIn3RPK54WfuQn-8HIap3fS4wnE' \
--data '{
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MzgxNzQ5MjQsImV4cCI6MTczODIxODEyNCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwiYXVkIjoibXktd2ViLWFwaS1jbGllbnQifQ.uyfyRwLO9_Rfy_6U5s8Ymk8fXSnh4jEnk_9oD8qlYlg"
}'
Enter fullscreen mode Exit fullscreen mode

If the refresh has been successful, we get a new access token:

{
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjZDEyNWVlZi1jMDUwLTQ3MDktYTZjZi0yNmYxNDRkMmMzNDUiLCJ1c2VyRW1haWwiOiJ0ZXN0dXNlckBleGFtcGxlLmNvbSIsInVzZXJBZ2UiOiIyNSIsInVzZXJSb2xlIjoiVVNFUiIsImp0aSI6ImUwMmY2N2YzLTQxMzAtNGMxZC04NDkyLTEzOWUyNGUzZmQzMiIsIm5iZiI6MTczODE3NTAwMCwiZXhwIjoxNzM4MTc4NjAwLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJhdWQiOiJteS13ZWItYXBpLWNsaWVudCJ9.N28PvF8dXSzfusGvUloTo6xcayv_zHh0jpG9cTEGCww",
    "accessTokenExpirationTime": "2025-01-29T19:23:20.2245074Z"
}
Enter fullscreen mode Exit fullscreen mode

Step 3 - Revoke token method (logout)

  • Method implementation:
  [Authorize]
  [HttpPost("logout")]
  public async Task<IActionResult> Logout()
  {
      try
      {
          //since we are logged in we can get the id from the token
          var userId = HttpContext.User.FindFirstValue(ApplicationClaims.Id);

          if (string.IsNullOrEmpty(userId))
          {
              return Unauthorized("Invalid user.");
          }

          var success = await _tokensRepository.RemoveUserRefreshTokenAsync(userId);

          return success ? Ok("User logged out successfully.") : Unauthorized("Invalid user.");
      } 
      catch (Exception ex)
      {
          return StatusCode(500, $"Internal server error: {ex.Message}");
      }
  }
Enter fullscreen mode Exit fullscreen mode
  • Method testing - you can use the following curl:
curl --location --request POST 'http://localhost:8080/api/usermanagement/logout' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjZDEyNWVlZi1jMDUwLTQ3MDktYTZjZi0yNmYxNDRkMmMzNDUiLCJ1c2VyRW1haWwiOiJ0ZXN0dXNlckBleGFtcGxlLmNvbSIsInVzZXJBZ2UiOiIyNSIsInVzZXJSb2xlIjoiVVNFUiIsImp0aSI6IjgwOGIzNGNlLTg2NmYtNDM2YS04N2YyLWRjYmVkNDllYWRlOCIsIm5iZiI6MTczODE3NDkyNCwiZXhwIjoxNzM4MTc4NTI0LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJhdWQiOiJteS13ZWItYXBpLWNsaWVudCJ9.fPhuRF5p3zWvVbXwqIn3RPK54WfuQn-8HIap3fS4wnE'
Enter fullscreen mode Exit fullscreen mode

If the logout has been successful, we get a confirmation and the user's refresh token will be removed from the database.

Conclusions

In this section, we implemented token-based authentication using .NET Core Identity, covering essential functionalities like login, registration, and logout. We explored the two types of tokens used in this authentication system: access tokens, which are used to access protected resources, and refresh tokens, which allow users to obtain new access tokens without needing to log in again. In the next session, we will build upon this by adding authorization to this demo project.

Top comments (0)