Invalidating JSON Web Tokens

前端 未结 28 2395
夕颜
夕颜 2020-11-22 06:17

For a new node.js project I\'m working on, I\'m thinking about switching over from a cookie based session approach (by this, I mean, storing an id to a key-value store conta

相关标签:
28条回答
  • 2020-11-22 07:02

    Keep an in-memory list like this

    user_id   revoke_tokens_issued_before
    -------------------------------------
    123       2018-07-02T15:55:33
    567       2018-07-01T12:34:21
    

    If your tokens expire in one week then clean or ignore the records older than that. Also keep only the most recent record of each user. The size of the list will depend on how long you keep your tokens and how often users revoke their tokens. Use db only when the table changes. Load the table in memory when your application starts.

    0 讨论(0)
  • 2020-11-22 07:03

    I did it the following way:

    1. Generate a unique hash, and then store it in redis and your JWT. This can be called a session
      • We'll also store the number of requests the particular JWT has made - Each time a jwt is sent to the server, we increment the requests integer. (this is optional)

    So when a user logs in, a unique hash is created, stored in redis and injected into your JWT.

    When a user tries to visit a protected endpoint, you'll grab the unique session hash from your JWT, query redis and see if it's a match!

    We can extend from this and make our JWT even more secure, here's how:

    Every X requests a particular JWT has made, we generate a new unique session, store it in our JWT, and then blacklist the previous one.

    This means that the JWT is constantly changing and stops stale JWT's being hacked, stolen, or something else.

    0 讨论(0)
  • 2020-11-22 07:04

    Haven't tried this yet, and it is uses a lot of information based on some of the other answers. The complexity here is to avoid a server side data store call per request for user information. Most of the other solutions require a db lookup per request to a user session store. That is fine in certain scenarios but this was created in an attempt to avoid such calls and make whatever required server side state to be very small. You will end up recreating a server side session, however small to provide all the force invalidation features. But if you want to do it here is the gist:

    Goals:

    • Mitigate use of a data store (state-less).
    • Ability to force log out all users.
    • Ability to force log out any individual at any time.
    • Ability to require password re-entry after a certain amount of time.
    • Ability to work with multiple clients.
    • Ability to force a re-log in when a user clicks logout from a particular client. (To prevent someone "un-deleting" a client token after user walks away - see comments for additional information)

    The Solution:

    • Use short lived (<5m) access tokens paired with a longer lived (few hours) client stored refresh-token.
    • Every request checks either the auth or refresh token expiration date for validity.
    • When the access token expires, the client uses the refresh token to refresh the access token.
    • During the refresh token check, the server checks a small blacklist of user ids - if found reject the refresh request.
    • When a client doesn't have a valid(not expired) refresh or auth token the user must log back in, as all other requests will be rejected.
    • On login request, check user data store for ban.
    • On logout - add that user to the session blacklist so they have to log back in. You would have to store additional information to not log them out of all devices in a multi device environment but it could be done by adding a device field to the user blacklist.
    • To force re-entry after x amount of time - maintain last login date in the auth token, and check it per request.
    • To force log out all users - reset token hash key.

    This requires you to maintain a blacklist(state) on the server, assuming the user table contains banned user information. The invalid sessions blacklist - is a list of user ids. This blacklist is only checked during a refresh token request. Entries are required to live on it as long as the refresh token TTL. Once the refresh token expires the user would be required to log back in.

    Cons:

    • Still required to do a data store lookup on the refresh token request.
    • Invalid tokens may continue to operate for access token's TTL.

    Pros:

    • Provides desired functionality.
    • Refresh token action is hidden from the user under normal operation.
    • Only required to do a data store lookup on refresh requests instead of every request. ie 1 every 15 min instead of 1 per second.
    • Minimizes server side state to a very small blacklist.

    With this solution an in memory data store like reddis isn't needed, at least not for user information as you are as the server is only making a db call every 15 or so minutes. If using reddis, storing a valid/invalid session list in there would be a very fast and simpler solution. No need for a refresh token. Each auth token would have a session id and device id, they could be stored in a reddis table on creation and invalidated when appropriate. Then they would be checked on every request and rejected when invalid.

    0 讨论(0)
  • 2020-11-22 07:04

    If you are using axios or a similar promise-based http request lib you can simply destroy token on the front-end inside the .then() part. It will be launched in the response .then() part after user executes this function (result code from the server endpoint must be ok, 200). After user clicks this route while searching for data, if database field user_enabled is false it will trigger destroying token and user will immediately be logged-off and stopped from accessing protected routes/pages. We don't have to await for token to expire while user is permanently logged on.

    function searchForData() {   // front-end js function, user searches for the data
        // protected route, token that is sent along http request for verification
        var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 
    
        // route will trigger destroying token when user clicks and executes this func
        axios.post('/my-data', {headers: {'Authorization': validToken}})
         .then((response) => {
       // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
           if (response.data.user_enabled === false) {  // user_enabled is field in the db
               window.localStorage.clear();  // we destroy token and other credentials
           }  
        });
         .catch((e) => {
           console.log(e);
        });
    }
    
    0 讨论(0)
  • 2020-11-22 07:05

    I'm a bit late here, but I think I have a decent solution.

    I have a "last_password_change" column in my database that stores the date and time when the password was last changed. I also store the date/time of issue in the JWT. When validating a token, I check if the password has been changed after the token was issued and if it was the token is rejected even though it hasn't expired yet.

    0 讨论(0)
  • 2020-11-22 07:05

    If you want to be able to revoke user tokens, you can keep track of all issued tokens on your DB and check if they're valid (exist) on a session-like table. The downside is that you'll hit the DB on every request.

    I haven't tried it, but i suggest the following method to allow token revocation while keeping DB hits to a minimum -

    To lower the database checks rate, divide all issued JWT tokens into X groups according to some deterministic association (e.g., 10 groups by first digit of the user id).

    Each JWT token will hold the group id and a timestamp created upon token creation. e.g., { "group_id": 1, "timestamp": 1551861473716 }

    The server will hold all group ids in memory and each group will have a timestamp that indicates when was the last log-out event of a user belonging to that group. e.g., { "group1": 1551861473714, "group2": 1551861487293, ... }

    Requests with a JWT token that have an older group timestamp, will be checked for validity (DB hit) and if valid, a new JWT token with a fresh timestamp will be issued for client's future use. If the token's group timestamp is newer, we trust the JWT (No DB hit).

    So -

    1. We only validate a JWT token using the DB if the token has an old group timestamp, while future requests won't get validated until someone in the user's group will log-out.
    2. We use groups to limit the number of timestamp changes (say there's a user logging in and out like there's no tomorrow - will only affect limited number of users instead of everyone)
    3. We limit the number of groups to limit the amount of timestamps held in memory
    4. Invalidating a token is a breeze - just remove it from the session table and generate a new timestamp for the user's group.
    0 讨论(0)
提交回复
热议问题