Prevent users to have multiple sessions with JWT Tokens

ⅰ亾dé卋堺 提交于 2021-02-07 10:13:19

问题


I am building an application which uses JWT bearer authentication in ASP.NET Core. I need to prevent users to have multiple sessions open at the same time. I am wondering if there is way using Microsoft.AspNetCore.Authentication.JwtBearer middleware to list out all the tokens of an user and then verify if there are other tokens issued for that user in order to invalidate the incoming authentication request.

If the claims are able to be validated on the server, I guess that in order to do that, the server has a record of those claims and the user who owns them. Right?

Any ideas How Can I achieve this?


回答1:


I have achieved my goal, saving in the db the timestamp when the user login, adding that timestamp in the payload of the token and then adding an additional layer of security to validate the JWT against the db, returning 401 if the timestamp doesn't match. This is the code implemented with .net Core 2.0 if someone need it.

Controller:

    [HttpPost]
    [Route("authenticate")]
    public async Task<IActionResult> AuthenticateAsync([FromBody] UserModel user)
    {
        try
        {
            .......

            if (userSecurityKey != null)
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
                var tokenDescriptor = new SecurityTokenDescriptor
                {
                    Subject = new ClaimsIdentity(new Claim[]
                    {
                       // This claim allows us to store information and use it without accessing the db
                        new Claim("userSecurityKey", userDeserialized.SecurityKey.ToString()),
                        new Claim("timeStamp",timeStamp),
                        new Claim("verificationKey",userDeserialized.VerificationKey.ToString()),
                        new Claim("userName",userDeserialized.UserName)

                    }),
                    Expires = DateTime.UtcNow.AddDays(7),
                    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
                        SecurityAlgorithms.HmacSha256Signature)
                };
                var token = tokenHandler.CreateToken(tokenDescriptor);
                var tokenString = tokenHandler.WriteToken(token);

               // Updates timestamp for the user if there is one
                VerificationPortalTimeStamps userTimeStamp = await _context.VerificationPortalTimeStamps.AsNoTracking().FirstOrDefaultAsync(e => e.UserName == userDeserialized.UserName);

                if (userTimeStamp != null)
                {
                    userTimeStamp.TimeStamp = timeStamp;
                    _context.Entry(userTimeStamp).State = EntityState.Modified;
                   await _context.SaveChangesAsync();
                }
                else
                {
                    _context.VerificationPortalTimeStamps.Add(new VerificationPortalTimeStamps { TimeStamp = timeStamp, UserName = userDeserialized.UserName });
                    await _context.SaveChangesAsync();
                }


                // return basic user info (without password) and token to store client side                   
                return Json(new
                {
                    userName = userDeserialized.UserName,
                    userSecurityKey = userDeserialized.SecurityKey,
                    token = tokenString
                });
            }

            return Unauthorized();

        }
        catch (Exception)
        {
            return Unauthorized();
        }
    }

Then, to configure the JWT Bearer Authentication with .Net Core 2.0

Startup.cs:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider provider)
    {
        .................     

        app.UseAuthentication();

        app.UseMvc();

        ...........

    }

To configure the JWT Bearer authentication:

public IServiceProvider ConfigureServices(IServiceCollection services)
    {

        ............


        var key = Configuration["AppSettings:Secret"];

        byte[] keyAsBytes = Encoding.ASCII.GetBytes(key);

        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
            .AddJwtBearer(o =>
            {
                o.RequireHttpsMetadata = false;
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(keyAsBytes),
                    ValidateIssuer = false,
                    ValidateAudience = false,
                    ValidateLifetime = true

                };

                o.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                    {
                        if (Configuration["AppSettings:IsGodMode"] != "true")
                            context.Response.StatusCode = 401;


                        return Task.FromResult<object>(0);
                    }
                };
                o.SecurityTokenValidators.Clear();
                o.SecurityTokenValidators.Add(new MyTokenHandler());
            });

        services.AddMvc()                
            .AddJsonOptions(opt =>
            {
                opt.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });


        var provider = services.BuildServiceProvider();


        return provider;
    }

Then, we implement the custom validation in the controller as follows:

    [Authorize]
    [HttpGet]
    [Route("getcandidate")]
    public async Task<IActionResult> GetCandidateAsync()
    {

        try
        {
            .....

            //Get user from the claim
            string userName = User.FindFirst("UserName").Value;

            //Get timestamp from the db for the user
            var currentUserTimeStamp = _context.VerificationPortalTimeStamps.AsNoTracking().FirstOrDefault(e => e.UserName == userName).TimeStamp;

           // Compare timestamp from the claim against timestamp from the db
            if (User.FindFirst("timeStamp").Value != currentUserTimeStamp)
            {
                return NotFound();
            }

            ...........

        }
        catch (Exception)
        {
            return NotFound();
        }
    }


来源:https://stackoverflow.com/questions/45114492/prevent-users-to-have-multiple-sessions-with-jwt-tokens

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!