Home > OS >  How to add Logout method in .NET core 3.1 with storing jwt token in cookies
How to add Logout method in .NET core 3.1 with storing jwt token in cookies

Time:07-19

I'm new to c# and dot net. i am unable to understand how to implement logout method with jwt token here i'm attaching my whole code. i have implemented the authentication and authorization with jwt i'm accessing the credentials for login from the appsetting.json.

here is my appsetting.json where i have mentioned username,password and secret key

{"UserCred":{
  "Username":"test",
  "Password":"test"
},
  
  "AppSettings": {
    "Secret": "This is my secret key"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

DataAcess\UserRepository

using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using loginApi.Helpers;
using loginApi.Models;
using Microsoft.Extensions.Configuration;

namespace loginApi.DataAccess
{

    public class UserRepository : IUserRepository
    {
        
        private List<User> _users = new List<User>
        {
          
        };

        private readonly AppSettings _appSettings;
        private readonly IConfiguration _configuration;

        public UserRepository(IOptions<AppSettings> appSettings,IConfiguration configuration)
        {
            _appSettings = appSettings.Value;
            _configuration = configuration;

                 var _username = _configuration.GetSection("UserCred").GetSection("Username").Value;
                 var _Password = _configuration.GetSection("UserCred").GetSection("Password").Value;
                   
                     _users = new List<User> 
                { 
                    new User { Username = _username.ToString(), Password = _Password.ToString()}
                };
        }

        public AuthenticateResponse Authenticate(AuthenticateRequest model)
        {


            var user = _users.SingleOrDefault(x => x.Username == model.Username && x.Password == model.Password);

            
            if (user == null) return null;

           
            var token = generateJwtToken(user);

            return new AuthenticateResponse(user, token);
        }

        public IEnumerable<User> GetAll()
        {
            return _users;
        }

        public User GetByUserName(string username)
        {
            return _users.FirstOrDefault(x => x.Username== username);
        }

   

        private string generateJwtToken(User user)
        {
            
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new[] { new Claim("username", user.Username.ToString()) }),
                Expires = DateTime.UtcNow.AddDays(7),
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }
}

DataAccess\IUserRepository

using loginApi.Models;
using System.Collections.Generic;

namespace loginApi.DataAccess
{
    public interface IUserRepository
    {
        AuthenticateResponse Authenticate(AuthenticateRequest model);
        IEnumerable<User> GetAll();
        User GetByUserName(string username);
    }

}

controllers\UserController.cs

using Microsoft.AspNetCore.Mvc;
using loginApi.Models;
using loginApi.DataAccess;

namespace loginApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class UserController : ControllerBase
    {
        private IUserRepository _userRepository;

        public UserController(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        [HttpPost("Controller")]
        public IActionResult Authenticate(AuthenticateRequest model)
        {
            var response = _userRepository.Authenticate(model);

            if (response == null)
                return BadRequest(new { message = "Username or password is incorrect" });

            return Ok(response);
        }

    }
}

Helpers Folder :-

Helpers\AuthorizeAttribute.cs

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using loginApi.Models;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = (User)context.HttpContext.Items["User"];
        if (user == null)
        {
            
            context.Result = new JsonResult(new { message = "Unauthorized" }) { StatusCode = StatusCodes.Status401Unauthorized };
        }
    }
}

Helpers\JwtMiddleware.cs

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using loginApi.DataAccess;
using loginApi.Models;

namespace loginApi.Helpers
{
    public class JwtMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly AppSettings _appSettings;

        public JwtMiddleware(RequestDelegate next, IOptions<AppSettings> appSettings)
        {
            _next = next;
            _appSettings = appSettings.Value;
        }

        public async Task Invoke(HttpContext context, IUserRepository userRepository)
        {
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();

            if (token != null)
                attachUserToContext(context, userRepository, token);

            await _next(context);
        }

        private void attachUserToContext(HttpContext context, IUserRepository userRepository, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ClockSkew = TimeSpan.Zero
                }, out SecurityToken validatedToken);

                var jwtToken = (JwtSecurityToken)validatedToken;               
                var userName = jwtToken.Claims.First(x => x.Type == "username").Value;
                
                context.Items["User"] = userRepository.GetByUserName(userName);
            }
            catch
            {
                
            }
        }
    }
}

Models Folder

Models\AppSettings.cs

namespace loginApi.Models
{
    public class AppSettings
    {
        public string Secret { get; set; }
    }
}

Models\AuthenticateRequest.cs

using System.ComponentModel.DataAnnotations;

namespace loginApi.Models
{
    public class AuthenticateRequest
    {
        [Required]
        public string Username { get; set; }

        [Required]
        public string Password { get; set; }
    }
}

Models\AuthenticateResponse.cs

using loginApi.Models;

namespace loginApi.Models
{
    public class AuthenticateResponse
    {
       
        public string Username { get; set; }
        public string Token { get; set; }


        public AuthenticateResponse(User user, string token)
        {            
            Username = user.Username;
            Token = token;
        }
    }
}

Models\User.cs

using System.Text.Json.Serialization;

namespace loginApi.Models
{
    public class User
    {
        public string Username { get; set; }

        [JsonIgnore]
        public string Password { get; set; }
    }
}

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using loginApi.Helpers;
using loginApi.DataAccess;
using loginApi.Models;

namespace loginApi
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        // add services to the DI container
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors();
            services.AddControllers();
             services.AddSwaggerGen(); 

            // configure strongly typed settings object
            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
            services.Configure<User>(Configuration.GetSection("UserCred"));
            // configure DI for application services
            services.AddScoped<IUserRepository, UserRepository>();
        }

        // configure the HTTP request pipeline
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseRouting();

            // global cors policy
            app.UseCors(x => x
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());

            // custom jwt auth middleware
            app.UseMiddleware<JwtMiddleware>();

            app.UseEndpoints(x => x.MapControllers());

            app.UseSwagger();  
            app.UseSwaggerUI(c => {  
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V2");  
            }); 
        }
    }
}

CodePudding user response:

A JWT is not designed to be logged out en therefor there is no safe out of the box solution provide by the framework. For a more in depth explanation about logging out a JWT you can read this answer and continue to research those specific methods of logging out a JWT.

If there are no strict security policies required, then practically you can just remove the Authorization header from the HTTP response. In that way the front end will not be able to set the JWT anymore in the following requests.

For example, create a Logout() controller action and add the following code to the body: Response.Headers.Remove("Authorization");

CodePudding user response:

For login and logout, we will see how to get a JWT token with user claims and store it in the session storage key "JWToken", then it can apply the authentication filter by role. Assign it to the user and restrict to another user unauthorized user and how to logout users.

I see you have already finish the login step, so first add service.AddAuthentication, service.AddSession to Startup.cs file. Here's code following:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddControllersWithViews();
    services.AddRazorPages();


    #region "JWT Token For Authentication Login"    
    SiteKeys.Configure(Configuration.GetSection("AppSettings"));
    var key = Encoding.ASCII.GetBytes(SiteKeys.Token);

    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromMinutes(60);
    });


    services.AddAuthentication(auth =>
     {
         auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
         auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
     })
    .AddJwtBearer(token =>
      {
          token.RequireHttpsMetadata = false;
          token.SaveToken = true;
          token.TokenValidationParameters = new TokenValidationParameters
          {
              ValidateIssuerSigningKey = true,
              IssuerSigningKey = new SymmetricSecurityKey(key),
              ValidateIssuer = true,
              ValidIssuer = SiteKeys.WebSiteDomain,
              ValidateAudience = true,
              ValidAudience = SiteKeys.WebSiteDomain,
              RequireExpirationTime = true,
              ValidateLifetime = true,
              ClockSkew = TimeSpan.Zero
          };
      });

}

Then add JWToken in Authorization Bearer in the request header:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();

    #region "JWT Token For Authentication Login"    
    app.UseCookiePolicy();
    app.UseSession();
    app.Use(async (context, next) =>
    {
        var JWToken = context.Session.GetString("JWToken");
        if (!string.IsNullOrEmpty(JWToken))
        {
            context.Request.Headers.Add("Authorization", "Bearer "   JWToken);
        }
        await next();
    });
    app.UseAuthentication();
    app.UseAuthorization();

    #endregion
    app.UseEndpoints(endpoints =>
    {
        //Routing Area Admin    
        endpoints.MapAreaControllerRoute(
              name: "routeArea",
              areaName: "Admin",
              pattern: "Admin/{controller=Home}/{action=Index}/{id?}");
        endpoints.MapRazorPages();
        //Routing Front     
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Main}/{action=Index}/{id?}");
        endpoints.MapRazorPages();
    });

}

Then add connection in the appsetting.json file:

"ConnectionStrings": {
    "DefaultConnection": "Server=#####;Database=YourDataBase;user=YourUser;password=YourPW;Trusted_Connection=False;"
}

Last, the logout function for each user is here:

[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Logout()
{
    HttpContext.Session.Clear();
    return RedirectToAction("Index");
}
  • Related