How to securely store the Access-Token of a Discord(OAuth2) User?

Tags: , , , ,



I’m struggling to find a way to securely save an Access-Token, which my web application retrieved from the DiscordAPI after the user authorized the application.

I’m creating a web-interface for a Discord Bot. Here it is important, that not everyone can use it. Only server-moderators and such on a specific Discord server should be allowed to access most parts of the website. For this I’m using the OAuth2 stuff from Discord to retrieve an Access-Token with which I can get user info, such as their unique ID. The ID is then used to check what sort of roles they have on the Discord server.

Now the part of getting the Access-Token is already scripted and seems to work okay. I can use the token to query data from the Discord API etc.

My main concern is security here. I’ve read through several different posts, but everyone seems to have a different view on this.

One “solution” I often read about (even on the Auth2 website) is to save the token in a cookie.

Here I’m not sure if that is secure at all. I can’t simply store the token only on the Server, cause the user has to stay logged during the tokens lifetime. But storing it as a cookie opens it up to attacks (which I’m not familiar enough with to secure against).

I currently have this line when receiving the token:

res.cookie('authToken', response.access_token);

I also adjust the line to the following, as this is stated to remove all ways of reading out the cookies through scripts (is that enough?):

res.cookie('authToken', response.access_token, { httpOnly: true });

And when accessing other parts of the web interface, I check if the cookie exists and try to ask Discord about the user info with it. If the user info returns correctly I assume the user is properly authenticated:

router.get('/', catchAsync(async (req, res, next) => {  
    if(req.cookies.authToken === undefined) {

       // Render index view
       res.render('index', { 
           authenticated: false
       });
   }
   else {
        // Grab the token
        const localToken = req.cookies.authToken;
        // Use the token to query user information
        const response = await discordapi.GetDiscordUserDetails(localToken);
        if(response.id) {
            // Open the Index page and pass the username over
            res.render('index', {
                authenticated: true,
                username: response.username,
                userid: response.id,
                avatarid: response.avatar
            });
        } else {
            res.render('index', { 
                authenticated: false
            });
        }
   }
}));

(The “authenticated” boolean I am passing only changes visibility of the login button in the html (express handlebars) doc. You can’t do anything else with it.)

Now with my limited knowledge about this, I pretty much assume this is the worst way of doing it and I want to improve (this).

Another solution would be storing the Access-Token in a Database on the Web-Server itself. So basically never even showing it to the user in any way. However, then I need a way of matching this token with the user, so they still need some sort of cookie with information which I can use to get the token. I assume it would be optimal if the token would be encrypted in the database and the user has a key to decrypt it. However then I’m not sure if that is secure again, cause if you read out the cookie, you can still get access to the token, or at least act like you are that specific user.

So is keeping the cookies 100% secure the only way? I’m very new to this and even though this is most likely only a small web interface for one simple discord server, I still want do this correctly.

I also read that handling the token as some sort of password and wrapping it into another layer of “sessions” is a way of doing it, but that sounds overlay complicated and I would actually not know where to begin with this.

I hope someone can shed some light on this, as security is something I’m really afraid of.

Thanks for your time!

Answer

  • BORING STORY:

I would like to start with the premise that I have seen online banking applications sending authentication tokens as plain text appended as a query string in a POST.

With that being said, most of the security considerations a developer has to make must aim to protect the back-end structure and not to prevent users from being hacked client-side. (Obviously, you will make everything you can to make that last hypothesis less plausible, but there is a limit, morally and technologically. I mean, if I connect to a non-secure network and someone intercepts my communication and manages to decode it, I think it would be more my fault than others.)

Anyway, I will stop being philosophical, but a last, already known, observation, there will be never a 100% secure solution for anything.

  • THE REAL ANSWER:

Using httpOnly cookies is the most simple and secure way to transmit and store authentication tokens, but if that is not enough, there are some other security layers that can be implemented. This are only some ideas, there could be many more!

  1. Reduce tokens lifetime and close session server-side after some inactivity time. You will have to keep a record with the active sessions, each one with its starting time and active token, etc.

  2. IP check. If a session started with an IP address from the US, and five minutes later the IP seems to be from Philippines, probably you have to take some action.

  3. Use external authentication services like AWS Cognito. But it will do nothing that you can not do on your own.

  4. Implement Multi-Factor Authentication.

  5. Similar to the IP check, you could calculate a hash using a user agent string as a seed, and store it. Check it when you have doubts about client identity.

  6. Talking about hashes, you can store the token and send a hash to the user. The only improvement is that it prevents someone from calling the Discord API directly (if the API has no kind of filter). Passwords, for example are always stored as hashes.

  7. Any combination of the previous.

The list could go on infinitely, and after some point, in my opinion, you will be only losing time and resources unnecessarily. Just remember the banks sending the tokens in the URL, and you will feel a lot better.



Source: stackoverflow